{"openapi":"3.1.0","info":{"title":"Campaign Brain — Unified API","version":"1","description":"Merged OpenAPI specification across every mesh-registered Campaign Brain service. Paths are prefixed with `/{service_id}` to disambiguate; component schemas are namespaced with `{service_id}__` and internal `$ref`s are rewritten. This document is for documentation and discovery — call services directly via their own domains, or fetch the per-service spec at `/catalog/{service_id}/openapi.json`.","x-services-included":["cbai","cbairtable","cbapp","cbauth","cbbanking","cbbilling","cbcalendar","cbcampaign","cbcaptive","cbcast","cbconvo","cbcrm","cbcron","cbdatabase","cbdistricts","cbea","cbelections","cbemail","cbenrichment","cbesig","cbetl","cbfiles","cbforge","cbfront","cbgeo","cbgreenroom","cbgroundgame","cbharvest","cbhive","cbinbox","cbindex","cbinfra","cbingest","cbintel","cbissues","cbiterable","cbkb","cbkrnl","cblinks","cblist","cblogs","cbloom","cbmanim","cbmaps","cbmarketing","cbmobile","cbmodels","cbnetwork","cbnominate","cbnotify","cbonboard","cboverseer","cbpayments","cbpersonnel","cbprint","cbpublish","cbpulse","cbquickbooks","cbradio","cbradio-qa","cbradio2","cbrouter","cbscout","cbsearch","cbship","cbsme","cbsms","cbsnailmail","cbsocial","cbsquare","cbstitch","cbstt","cbsurveys","cbtags","cbtapwatch","cbtenant","cbtiles","cbtor","cbunifi","cbuserguide","cbvoice","cbvpn","cbweave","cbwebhook","cbworkflow","cbworkspaces"],"x-fetch-errors":{}},"servers":[{"url":"https://mcp.campaignbrain.dev","description":"cbmcp documentation host — try-it-out is not supported against the merged paths."}],"paths":{"/cbai/":{"get":{"tags":["cbai","Root"],"summary":"Service information","description":"Get basic service information and documentation links.\n\n**Returns:**\n- `service`: Service name\n- `version`: Current API version\n- `docs`: Link to interactive Swagger documentation\n- `health`: Link to health check endpoint","operationId":"root__get","responses":{"200":{"description":"Service information","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Root  Get"},"example":{"service":"CBAI","version":"0.1.0","docs":"/docs","health":"/api/v1/health"}}}}}}},"/cbai/api/v1/chat":{"post":{"tags":["cbai","Chat"],"summary":"Chat with an AI model","description":"Send a conversation to an AI model and receive a response.\n\n**Provider Selection:**\n- `ollama` (default): Fast, free, local inference using `gemma3:4b`\n- `claude`: Higher quality reasoning using `claude-sonnet-4-5-20250929`\n\nWhen `AGENT_LLAMA_SERVER_URL` is configured and the request's `model` is exactly\nthe pinned agent model id, local requests are served by the memory-pinned\nllama-server instead of Ollama -- same model, no cold-load latency (cbai#105).\nAll other local model ids use Ollama unchanged.\n\n**Streaming:**\nSet `stream: true` in the request body to receive real-time token-by-token output.\nStreaming responses have `text/plain` content type.\n\n**Multi-turn Conversations:**\nInclude previous messages in the `messages` array for context-aware responses.\nThe AI sees the full conversation history.\n\n**Optional attribution headers (cbai#85):**\n- `X-CB-Source` -- calling service name\n- `X-CB-Tenant-ID` -- tenant the request is on behalf of\n- `X-CB-User-ID` -- user the request is on behalf of\n- `X-CB-Request-ID` -- originating request ID, propagated through the chain\n- `X-CB-Parent-Source` -- service that called the immediate caller\n\nCurrently optional; persisted on the `usage_records` row for cost\nattribution and audit. Cbai logs a WARNING when the tenant/user/origin\ntriplet is incomplete.","operationId":"chat_api_v1_chat_post","parameters":[{"name":"provider","in":"query","required":false,"schema":{"$ref":"#/components/schemas/cbai__AIProvider","description":"AI provider to use. `ollama` for speed (local), `claude` for quality (API).","examples":["ollama","claude"],"default":"ollama"},"description":"AI provider to use. `ollama` for speed (local), `claude` for quality (API)."},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Request-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Request-Id"}},{"name":"X-CB-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"X-CB-Parent-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Parent-Source"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__ChatRequest"}}}},"responses":{"200":{"description":"Successful response from AI model","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__ChatResponse"},"example":{"content":"Hello! I'm doing well, thank you for asking. How can I help you today?","model":"mistral-small3.2:latest","provider":"ollama","usage":{"prompt_tokens":12,"completion_tokens":18}}}}},"400":{"description":"Invalid request (empty messages, invalid provider)","content":{"application/json":{"example":{"detail":"Messages list cannot be empty"}}}},"500":{"description":"Provider error (model unavailable, API error)","content":{"application/json":{"example":{"detail":"Ollama server not reachable"}}}},"503":{"description":"Provider unavailable -- Claude key not configured, ollama unreachable, or (cbai#119) the ollama chat in-flight cap is full / ollama itself is busy; those carry `Retry-After`.","content":{"application/json":{"examples":{"no_key":{"value":{"detail":"Claude API key not configured"}},"saturated":{"value":{"detail":"ollama chat saturated: 8 requests already in flight (cap 8) at http://10.8.0.2:11434; retry in 15s. See /api/v1/health/detailed -> ollama."}}}}}},"504":{"description":"Ollama accepted the request but did not answer within the read budget (`timeout`, else OLLAMA_TIMEOUT / OLLAMA_VISION_TIMEOUT). The body names the model, budget and host (cbai#119).","content":{"application/json":{"example":{"detail":"ollama did not answer within 90s (model=minicpm-v:latest, http://10.8.0.2:11434). The runner may be on CPU or queued behind other inferences -- check /api/v1/health/detailed -> ollama."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/chat/agent":{"post":{"tags":["cbai","Tool Use"],"summary":"Server-side Claude+tool agent loop with SSE streaming","description":"Run the full Claude tool-use loop server-side against a named tool catalog,\nstreaming progress to the caller via Server-Sent Events.\n\n**Required headers:**\n- `Accept: text/event-stream`\n- `X-API-Key: <platform key>`\n- `X-CB-Source: <calling service name>` (matched against the catalog's `allowed_sources`)\n\n**Optional attribution headers** (cbai#85 -- currently optional, will become\nrequired once fleet-wide propagation lands per Nominate-AI/infrastructure#47;\ncbai logs a single WARNING per request when the tenant/user/origin triplet\nis incomplete):\n\n- `X-CB-Tenant-ID: <tenant>` -- tenant the request is on behalf of\n- `X-CB-User-ID: <user>` -- user the request is on behalf of\n- `X-CB-Request-ID: <id>` -- originating request ID, propagated unchanged\n  through the call chain so a single user action is followable across\n  N service hops\n- `X-CB-Parent-Source: <service>` -- service that called the immediate caller\n\nThe five fields land on every span in the resulting trace tree (via\nbaggage enrichment, cbai#86) and on the `usage_records` row, enabling\nper-tenant cost rollups via `?tenant_id=` filters on the usage endpoints.\n\n**Event types** (all JSON-encoded in the `data` field):\n- `status` -- phase transition (`resolving_catalog`, `calling_claude`, `executing_tools`)\n- `text` -- streaming text delta from Claude (`{\"delta\": \"...\"}`)\n- `tool_call` -- Claude requested a tool (`{\"id\", \"name\", \"input\"}`)\n- `tool_result` -- tool executed (`{\"id\", \"result\", \"is_error\", \"elapsed_ms\"}`)\n- `error` -- unrecoverable error (`{\"error\": \"<code>\", \"detail\": \"...\"}`)\n- `done` -- loop completed (always emitted on normal completion). Aggregate\n  payload includes `tool_calls`, `tool_count`, `iterations`, `input_tokens`,\n  `output_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`,\n  `cache_hit_rate`, `elapsed_ms`, `stop_reason`, and the full `messages`\n  transcript.\n\nSee `docs/_archive/CHAT-AGENT-PROJECT.md` for the historical state machine\nand error codes (the spec moved to archive when #67 closed).","operationId":"chat_agent_api_v1_chat_agent_post","parameters":[{"name":"Accept","in":"header","required":true,"schema":{"type":"string","title":"Accept"}},{"name":"X-CB-Source","in":"header","required":true,"schema":{"type":"string","title":"X-Cb-Source"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Request-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Request-Id"}},{"name":"X-CB-Parent-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Parent-Source"}},{"name":"X-CB-Agent-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Agent-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__AgentChatRequest"}}}},"responses":{"200":{"description":"SSE stream of agent loop events","content":{"application/json":{"schema":{}},"text/event-stream":{}}},"415":{"description":"Missing or invalid Accept header"},"422":{"description":"Request validation error (e.g. missing X-CB-Source)"},"503":{"description":"Claude unavailable"}}}},"/cbai/api/v1/chat/tools":{"post":{"tags":["cbai","Tool Use"],"summary":"Chat with Claude using tools","description":"Chat with Claude while providing tools it can use to complete tasks.\n\n**Tool Use Flow:**\n1. Send messages with an array of tool definitions\n2. If Claude decides to use a tool, response has `stop_reason: \"tool_use\"`\n3. Execute the requested tools (from `tool_use` blocks in content)\n4. Send another request with `tool_result` blocks\n5. Repeat until `stop_reason: \"end_turn\"`\n\n**Provider:** Claude only. Other providers do not support tool use.\n\n**Streaming:**\nWhen `stream: true`, returns NDJSON (newline-delimited JSON).\nEach line is a complete JSON object with `type` field:\n- `{\"type\": \"text\", \"text\": \"...\"}`\n- `{\"type\": \"tool_use\", \"id\": \"...\", \"name\": \"...\", \"input\": {...}}`\n- `{\"type\": \"message_complete\", \"stop_reason\": \"...\", \"usage\": {...}}`\n\n**Multi-turn Example:**\n```python\n# First request\nresponse = await client.post(\"/api/v1/chat/tools\", json={\n    \"messages\": [{\"role\": \"user\", \"content\": \"What's the weather in Paris?\"}],\n    \"tools\": [weather_tool]\n})\n\n# If stop_reason == \"tool_use\", execute tools and continue\nif response[\"stop_reason\"] == \"tool_use\":\n    tool_results = execute_tools(response[\"content\"])\n    response = await client.post(\"/api/v1/chat/tools\", json={\n        \"messages\": [\n            {\"role\": \"user\", \"content\": \"What's the weather in Paris?\"},\n            {\"role\": \"assistant\", \"content\": response[\"content\"]},\n            {\"role\": \"user\", \"content\": tool_results}\n        ],\n        \"tools\": [weather_tool]\n    })\n```\n\n**Optional attribution headers (cbai#85):** same five `X-CB-*` headers as\n`/chat` (`X-CB-Source`, `X-CB-Tenant-ID`, `X-CB-User-ID`, `X-CB-Request-ID`,\n`X-CB-Parent-Source`) -- persisted on the usage row for cost attribution and\naudit. For server-side multi-turn loops with tool execution, prefer\n`/api/v1/chat/agent` instead.","operationId":"chat_with_tools_api_v1_chat_tools_post","parameters":[{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Request-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Request-Id"}},{"name":"X-CB-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"X-CB-Parent-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Parent-Source"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__ToolChatRequest"}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__ToolChatResponse"},"examples":{"tool_use":{"summary":"Claude wants to use a tool","value":{"id":"msg_01XFDUDYJgAACzvnptvVoYEL","content":[{"type":"text","text":"I'll check the weather for you."},{"type":"tool_use","id":"toolu_01A09q90qw90lq917835lq9","name":"get_weather","input":{"location":"San Francisco, CA"}}],"model":"claude-sonnet-4-5-20250929","stop_reason":"tool_use","usage":{"input_tokens":150,"output_tokens":50}}},"end_turn":{"summary":"Conversation complete","value":{"id":"msg_02BKEwMzLpAACnqrtuWxZFM","content":[{"type":"text","text":"The weather in San Francisco is 72°F and sunny."}],"model":"claude-sonnet-4-5-20250929","stop_reason":"end_turn","usage":{"input_tokens":200,"output_tokens":25}}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"examples":{"empty_tools":{"summary":"No tools provided","value":{"detail":"At least one tool is required"}},"invalid_tool_choice":{"summary":"Invalid tool_choice","value":{"detail":"tool_choice.name required when type='tool'"}}}}}},"500":{"description":"Provider error","content":{"application/json":{"example":{"detail":"Claude API error: rate limit exceeded"}}}},"503":{"description":"Claude unavailable","content":{"application/json":{"example":{"detail":"Claude API key not configured. Tool use requires Claude."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/embed":{"post":{"tags":["cbai","Embeddings"],"summary":"Generate text embeddings","description":"Convert text into a dense vector representation for semantic operations.\n\n**Model:** `nomic-embed-text` via Ollama (768 dimensions)\n\n**Use Cases:**\n- Semantic search (find similar documents)\n- RAG pipelines (retrieve relevant context)\n- Document clustering\n- Duplicate detection\n\n**Vector Properties:**\n- Dimensions: 768\n- Normalized: Ready for cosine similarity\n- Compatible with pgvector, Pinecone, Weaviate, etc.\n\n**Performance:**\n- Local inference (no API calls)\n- ~10-50ms per embedding\n- Batching supported for multiple texts","operationId":"embed_api_v1_embed_post","parameters":[{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Request-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Request-Id"}},{"name":"X-CB-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"X-CB-Parent-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Parent-Source"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__EmbeddingRequest"}}}},"responses":{"200":{"description":"Successfully generated embedding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__EmbeddingResponse"},"example":{"embedding":[0.0234,-0.0891,0.0456,0.0123,-0.0567],"model":"nomic-embed-text","dimensions":768}}}},"500":{"description":"Embedding generation failed","content":{"application/json":{"example":{"detail":"Ollama model 'nomic-embed-text' not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/health":{"get":{"tags":["cbai","Health"],"summary":"Check all provider availability","description":"Get the current availability status of all AI providers.\n\n**Providers Checked:**\n- `ollama`: Connectivity to local Ollama server (port 11434)\n- `claude`: Anthropic API key configured and valid\n- `mistral`: Mistral API key configured and valid\n- `unstructured`: Unstructured API endpoint reachable\n\n**Response:**\nEach provider returns `true` (available) or `false` (unavailable).\n\n**Use Cases:**\n- Health check endpoints for load balancers\n- Service monitoring and alerting\n- Pre-flight checks before API calls\n- Debugging connectivity issues\n\n**Note:**\nA provider being \"available\" means it can accept requests.\nIt does not guarantee all models are loaded or all operations will succeed.","operationId":"health_api_v1_health_get","responses":{"200":{"description":"Health check completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__app__models__types__HealthResponse"},"examples":{"all_healthy":{"summary":"All providers available","value":{"ollama":true,"claude":true,"mistral":true,"unstructured":true}},"partial":{"summary":"Some providers unavailable","value":{"ollama":true,"claude":false,"mistral":true,"unstructured":false}}}}}}}}},"/cbai/api/v1/health/detailed":{"get":{"tags":["cbai","Health"],"summary":"Per-provider detailed health (cbai#93)","description":"Rich per-provider health snapshot. Use this when `/api/v1/health` says\n`claude: false` and you need to know **why**.\n\nFor Claude, exposes:\n- `reason` -- failure class: `credit_low`, `invalid_api_key`,\n  `permission_denied`, `rate_limit`, `transient_5xx`, etc.\n- `since` -- when the current outage started (ISO-8601 UTC).\n- `consecutive_failures` -- how many probes have failed in a row.\n- `active_key_slot` -- `primary` or `alt` (if a fallback key is configured\n  and the primary tripped a definitive billing/auth error, we transparently\n  switched to the alt key).\n- `has_alt` -- whether `CLAUDE_API_KEY_ALT` / `CB_ANTHROPIC_API_KEY_ALT` is wired.\n- `next_check_at` -- during backoff, when we'll next probe Anthropic. The\n  health check intentionally skips upstream calls within this window to\n  avoid the per-minute log spam seen on 2026-05-08.\n\nUse this endpoint for alert routing (e.g. cbcron pages on\n`claude.reason == 'credit_low'` differently than `transient_5xx`).\n\nFor Ollama (cbai#119), `available` means the daemon answers; **`degraded`**\nmeans a normal caller budget will not survive it. `reason` says why:\n- `cpu_offload: <model> has N% of its weights in VRAM ...` -- the DC0 runner\n  restarted into full GPUs and fell to CPU (the 2026-08-31 outage: a one-word\n  `gemma3:4b` reply took 40-400s, `minicpm-v` never finished).\n- `saturated: N/N chats in flight` -- the in-flight cap is full; new chats get\n  503 + Retry-After rather than queueing behind serialized inferences.\nAlso exposes `inflight`, `max_inflight`, and `loaded_models` (`ollama ps`).\nPage on `ollama.degraded == true`; the bool `/api/v1/health` cannot see it.","operationId":"health_detailed_api_v1_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HealthDetailedResponse"}}}}}}},"/cbai/api/v1/imagelocate":{"post":{"tags":["cbai","Vision"],"summary":"Locate a target region (e.g. logo) in an image via Claude Vision","description":"Locate the target (currently 'logo') in an input image and return tight bboxes\nwith confidence + role hints. Used by cbloom's V2 brand pipeline (cbloom#102)\nto pull clean logo crops out of website screenshots.\n\n**Input:** base64 PNG/JPEG/WebP via `image_b64` + `media_type`.\n\n**Output:** `bboxes` is a list of {bbox, confidence, kind} entries ranked by\nconfidence. Empty list = \"no target detected\" -- caller should surface a\nmanual-upload affordance to the user rather than producing a bad crop.\n\n**Cost:** one Claude Vision call (~$0.001 per typical screenshot).\n\n**Determinism:** Claude Vision is non-deterministic; bboxes can shift by a\nfew pixels between runs. The cbloom pipeline crops with a small padding\nmargin to absorb this.","operationId":"imagelocate_api_v1_imagelocate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__ImageLocateRequest"}}},"required":true},"responses":{"200":{"description":"Region location completed (may be an empty bbox list)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__ImageLocateResponse"},"examples":{"located":{"summary":"Logo located","value":{"bboxes":[{"bbox":[120,40,360,120],"confidence":0.93,"kind":"primary"}],"image_width":1280,"image_height":720,"model":"claude-sonnet-4-5-20250929","usage":{"input_tokens":1450,"output_tokens":80}}},"not_found":{"summary":"No target detected","value":{"bboxes":[],"image_width":1280,"image_height":720,"model":"claude-sonnet-4-5-20250929","usage":{"input_tokens":1450,"output_tokens":12}}}}}}},"400":{"description":"Invalid request (undecodable image_b64)","content":{"application/json":{"example":{"detail":"Could not decode image_b64: ..."}}}},"503":{"description":"Claude unavailable","content":{"application/json":{"example":{"detail":"Claude API not configured (CLAUDE_API_KEY missing)"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/ocr":{"post":{"tags":["cbai","OCR"],"summary":"Extract text from documents (Mistral)","description":"Extract text and structure from PDFs and images using Mistral's OCR model.\n\n**Model:** `mistral-ocr-latest`\n\n**Supported Formats:**\n- PDF documents (multi-page supported)\n- Images: PNG, JPEG, WebP, GIF\n\n**Input Methods:**\n1. `document_url`: a public URL. Mistral fetches it; cbai does not.\n2. `document_base64`: Base64-encoded document data\n\nProvide exactly ONE of these input methods.\n\n**Output:**\n- `text`: Extracted text content\n- `pages`: Page-by-page extraction for multi-page documents\n- `images`: Extracted images (base64 encoded)\n- `metadata`: Document info (page count, dimensions, etc.)\n\n**Best For:**\n- PDFs with complex layouts\n- Scanned documents\n- Documents with tables and figures\n- Multi-column layouts","operationId":"ocr_api_v1_ocr_post","parameters":[{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Request-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Request-Id"}},{"name":"X-CB-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"X-CB-Parent-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Parent-Source"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__OCRRequest"}}}},"responses":{"200":{"description":"Successfully extracted text","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__OCRResponse"},"example":{"text":"Extracted document text...","pages":[{"page":1,"text":"Page 1 content..."},{"page":2,"text":"Page 2 content..."}],"metadata":{"page_count":2,"provider":"mistral"}}}}},"400":{"description":"Invalid request (no input or invalid format)","content":{"application/json":{"example":{"detail":"Either document_url or document_base64 is required"}}}},"500":{"description":"OCR processing failed","content":{"application/json":{"example":{"detail":"Mistral API error: Document too large"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/ocr/unstructured":{"post":{"tags":["cbai","OCR"],"summary":"Extract text from documents (Unstructured)","description":"Extract text and structural elements using the Unstructured API.\n\n**Endpoint:** Configurable via `UNSTRUCTURED_API_URL`\n\n**Supported Formats:**\n- PDF, DOCX, PPTX, XLSX\n- HTML, Markdown, RST\n- Images: PNG, JPEG\n- Plain text, RTF, ODT\n\n**Input:**\n`document_base64`: the document itself, base64-encoded.\n\nThere is no URL or path input. cbai does not fetch caller-supplied URLs or open\nserver-side files (cbai#129), so a request naming `document_url`, `file_url` or\n`file_path` is refused with 422.\n\n**Element Extraction:**\nUnlike simple OCR, Unstructured identifies document elements:\n- Titles, headers, paragraphs\n- Lists, code blocks\n- Images with captions\n\n**Best For:**\n- Office documents (Word, PowerPoint, Excel)\n- Structured documents with clear elements\n- Content that needs element-level processing\n\n**NOT for tabular source. Use `/api/v1/ocr` (Mistral) for grids.**\n\nThis endpoint used to claim \"tables (with structure preserved)\". That is not\nwhat it does, and the claim caused a real misroute during the 2026-09 Mistral\noutage, when this was the only OCR road still up.\n\nMeasured by cbrural on an 11-station radio rate card (one row per station,\nfive daypart columns, each cell `$30/$60`), same document both roads:\n\n    mistral        2,015 chars  130 pipes  38 newlines   0 mangled prices\n    unstructured   1,669 chars   10 pipes  26 newlines   $1216 $1181 $11815\n\nMistral returns a markdown table, one row per station. Unstructured returns\nthe whole grid as one paragraph: row boundaries gone, cell separators lost\n(`$12$16`), digits run together across cells (`$1181`), and in one case a\ncallsign came back as the word \"we\". A row with no callsign and a price of\n`$1181` is not a degraded read, it is a confident wrong one, and a human\nreviewer cannot tell.\n\nSecond independent instance of the same property: cbrural's `office-text.ts`\nmeasured it joining every row of a WORKBOOK into one space-separated run and\ndropping the `$`, which is why spreadsheets there go through SheetJS instead.","operationId":"ocr_unstructured_api_v1_ocr_unstructured_post","parameters":[{"name":"elements","in":"query","required":false,"schema":{"type":"boolean","description":"Return element-level structure (Title / NarrativeText / Table / …) instead of flattened text. Unstructured's whole value over `/api/v1/ocr` is that structure; the default stays flat only for backward compatibility with callers written against it.","default":false,"title":"Elements"},"description":"Return element-level structure (Title / NarrativeText / Table / …) instead of flattened text. Unstructured's whole value over `/api/v1/ocr` is that structure; the default stays flat only for backward compatibility with callers written against it."},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Request-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Request-Id"}},{"name":"X-CB-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"X-CB-Parent-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Parent-Source"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__UnstructuredOCRRequest"}}}},"responses":{"200":{"description":"Successfully extracted content","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbai__OCRResponse"},{"$ref":"#/components/schemas/cbai__UnstructuredOCRResponse"}],"title":"Response Ocr Unstructured Api V1 Ocr Unstructured Post"},"example":{"text":"Combined document text...","elements":[{"type":"Title","text":"Document Title"},{"type":"NarrativeText","text":"Paragraph content..."},{"type":"Table","text":"| Col1 | Col2 |..."}],"metadata":{"page_count":3,"provider":"unstructured"}}}}},"400":{"description":"Invalid request","content":{"application/json":{"example":{"detail":"Unsupported file format"}}}},"500":{"description":"Processing failed","content":{"application/json":{"example":{"detail":"Unstructured API unreachable"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/summarize":{"post":{"tags":["cbai","Text Processing"],"summary":"Summarize text content","description":"Condense long-form content into a concise summary.\n\n**Provider Selection:**\n- `use_claude: false` (default): Uses Ollama for fast, local summarization\n- `use_claude: true`: Uses Claude for higher-quality, nuanced summaries\n\n**Length Control:**\nThe `max_length` parameter suggests target length (in words/sentences).\nThe AI treats this as guidance, not a strict limit.\n\n**Best Practices:**\n- Use Ollama for quick summaries of straightforward content\n- Use Claude for nuanced content requiring understanding (legal, technical, etc.)\n- Longer content may take 5-15 seconds to process\n\n**Fallback Behavior:**\nIf Claude is requested but unavailable, the request fails (no automatic fallback).","operationId":"summarize_api_v1_summarize_post","parameters":[{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Request-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Request-Id"}},{"name":"X-CB-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"X-CB-Parent-Source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Parent-Source"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__SummarizeRequest"}}}},"responses":{"200":{"description":"Successfully summarized content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__SummarizeResponse"},"example":{"summary":"The article discusses the recent advances in AI language models, focusing on three key developments: improved reasoning capabilities, reduced hallucinations, and better instruction following.","provider":"ollama"}}}},"400":{"description":"Invalid request (empty content)","content":{"application/json":{"example":{"detail":"Content cannot be empty"}}}},"500":{"description":"Summarization failed","content":{"application/json":{"example":{"detail":"Failed to generate summary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/topics":{"post":{"tags":["cbai","Text Processing"],"summary":"Extract main topics from content","description":"Identify the 3-5 main topics or themes in content.\n\n**Output Format:**\nReturns a list of topic strings, each being a concise phrase (2-5 words).\n\n**Use Cases:**\n- Content categorization\n- Tag generation\n- Document organization\n- Content analysis pipelines\n\n**Provider:**\nUses Claude if available (better at nuanced topic identification),\nfalls back to Ollama for local processing.\n\n**Tips:**\n- Works best with 100+ words of content\n- Very short content may return fewer topics\n- Topics are ordered by relevance/prominence","operationId":"extract_topics_api_v1_topics_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__TopicsRequest"}}},"required":true},"responses":{"200":{"description":"Successfully extracted topics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__TopicsResponse"},"example":{"topics":["Machine Learning Advances","Natural Language Processing","AI Safety Concerns","Industry Applications"],"provider":"claude"}}}},"400":{"description":"Invalid request","content":{"application/json":{"example":{"detail":"Content too short for topic extraction"}}}},"500":{"description":"Topic extraction failed","content":{"application/json":{"example":{"detail":"Failed to extract topics"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/traces":{"get":{"tags":["cbai","Traces"],"summary":"List recent traces","description":"Paginated list of trace tree summaries (one entry per trace_id),\nordered by most recent root span start time.\n\nEach `/api/v1/chat/agent` invocation produces one trace whose root\nspan is `invoke_agent`. Per-iteration `chat` spans and per-tool\n`execute_tool {name}` spans nest under it.\n\nSpans are stored alongside usage records in the same SQLite file.\nUse `GET /api/v1/traces/{trace_id}` to drill into one tree.","operationId":"list_traces_api_v1_traces_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"start_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Time"}},{"name":"end_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Time"}},{"name":"root_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact match on the root span's name. Use 'invoke_agent' to exclude auto-instrumented mistralai / mesh-emitted roots that share the same DB (cbai#83).","title":"Root Name"},"description":"Exact match on the root span's name. Use 'invoke_agent' to exclude auto-instrumented mistralai / mesh-emitted roots that share the same DB (cbai#83)."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"OTel StatusCode name: OK / ERROR / UNSET. Case-insensitive. Use 'ERROR' to find failing runs.","examples":["ERROR"],"title":"Status"},"description":"OTel StatusCode name: OK / ERROR / UNSET. Case-insensitive. Use 'ERROR' to find failing runs."},{"name":"min_duration_ms","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Only traces whose root span lasted at least this long.","title":"Min Duration Ms"},"description":"Only traces whose root span lasted at least this long."},{"name":"max_duration_ms","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Only traces whose root span lasted at most this long.","title":"Max Duration Ms"},"description":"Only traces whose root span lasted at most this long."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__TraceListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/traces/{trace_id}":{"get":{"tags":["cbai","Traces"],"summary":"Get a full trace tree","description":"Return the full nested span tree for a given trace ID.\n\nThe shape is:\n\n```json\n{\n  \"span\": { ... root span ... },\n  \"children\": [\n    {\"span\": {...}, \"children\": [...]},\n    ...\n  ]\n}\n```\n\nFor a `/chat/agent` request you'll typically see:\n  - `invoke_agent` (root)\n    - `chat` (one per Claude iteration)\n      - `execute_tool {name}` (one per tool call)","operationId":"get_trace_api_v1_traces__trace_id__get","parameters":[{"name":"trace_id","in":"path","required":true,"schema":{"type":"string","title":"Trace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__SpanTree"}}}},"404":{"description":"Trace not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/usage":{"get":{"tags":["cbai","Usage"],"summary":"Query usage records","description":"Query API usage records with optional filtering and pagination.\n\n**Filters:**\n- `start_time`: Records after this time (inclusive)\n- `end_time`: Records before this time (exclusive)\n- `operation`: Filter by operation types (chat, embed, summarize, topics, ocr)\n- `provider`: Filter by providers (ollama, claude, mistral, unstructured)\n- `model`: Filter by model names\n- `success`: Filter by success status\n\n**Pagination:**\n- `limit`: Maximum records to return (1-1000, default 100)\n- `offset`: Number of records to skip (default 0)\n\n**Response:**\nReturns paginated results with total count for building pagination UI.","operationId":"get_usage_api_v1_usage_get","parameters":[{"name":"start_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter records after this time (inclusive)","title":"Start Time"},"description":"Filter records after this time (inclusive)"},{"name":"end_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter records before this time (exclusive)","title":"End Time"},"description":"Filter records before this time (exclusive)"},{"name":"operation","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by operation types","title":"Operation"},"description":"Filter by operation types"},{"name":"provider","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by providers","title":"Provider"},"description":"Filter by providers"},{"name":"model","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by model names","title":"Model"},"description":"Filter by model names"},{"name":"success","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by success status","title":"Success"},"description":"Filter by success status"},{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tenant (cbai#85)","title":"Tenant Id"},"description":"Filter by tenant (cbai#85)"},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by user (cbai#85)","title":"User Id"},"description":"Filter by user (cbai#85)"},{"name":"origin_request_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by originating request ID -- returns the full chain (cbai#85)","title":"Origin Request Id"},"description":"Filter by originating request ID -- returns the full chain (cbai#85)"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by calling service (cbai#85)","title":"Source"},"description":"Filter by calling service (cbai#85)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum records to return","default":100,"title":"Limit"},"description":"Maximum records to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of records to skip","default":0,"title":"Offset"},"description":"Number of records to skip"}],"responses":{"200":{"description":"Paginated usage records","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__UsageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/usage/series":{"get":{"tags":["cbai","Usage"],"summary":"Get time-bucketed usage series","description":"Get a usage trend series bucketed by day or hour (cbai#111) -- the data behind\ncost/throughput trend charts.\n\n**Params:**\n- `interval`: `day` (default) or `hour`\n- `start_time`/`end_time`: window bounds\n- `tenant_id`/`user_id`/`source`: optional filters (same semantics as summary)\n\n**Response:** `{interval, points:[{bucket, requests, tokens_total, cost_usd,\ncost_saved_usd, errors, success_rate}]}`, oldest bucket first.\n\nRegistered before `/usage/{request_id}` so the literal path wins over the\npath-parameter route.","operationId":"get_usage_series_api_v1_usage_series_get","parameters":[{"name":"interval","in":"query","required":false,"schema":{"enum":["day","hour"],"type":"string","description":"Bucket size","default":"day","title":"Interval"},"description":"Bucket size"},{"name":"start_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of period","title":"Start Time"},"description":"Start of period"},{"name":"end_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of period","title":"End Time"},"description":"End of period"},{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tenant","title":"Tenant Id"},"description":"Filter by tenant"},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by user","title":"User Id"},"description":"Filter by user"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by calling service","title":"Source"},"description":"Filter by calling service"}],"responses":{"200":{"description":"Time-bucketed usage series","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__UsageSeries"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/usage/summary":{"get":{"tags":["cbai","Usage"],"summary":"Get usage summary","description":"Get aggregated usage statistics for a time period.\n\n**Time Range:**\n- `start_time`: Start of period (optional, defaults to earliest record)\n- `end_time`: End of period (optional, defaults to now)\n\n**Response includes:**\n- `totals`: Overall request count, tokens, latency stats, success rate\n- `by_operation`: Breakdown by operation type (chat, embed, etc.)\n- `by_provider`: Breakdown by provider (ollama, claude, etc.)","operationId":"get_usage_summary_api_v1_usage_summary_get","parameters":[{"name":"start_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of period","title":"Start Time"},"description":"Start of period"},{"name":"end_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of period","title":"End Time"},"description":"End of period"},{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tenant (cbai#85)","title":"Tenant Id"},"description":"Filter by tenant (cbai#85)"},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by user (cbai#85)","title":"User Id"},"description":"Filter by user (cbai#85)"},{"name":"origin_request_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by originating request ID -- aggregate over a single chain (cbai#85)","title":"Origin Request Id"},"description":"Filter by originating request ID -- aggregate over a single chain (cbai#85)"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by calling service (cbai#85)","title":"Source"},"description":"Filter by calling service (cbai#85)"},{"name":"group_by","in":"query","required":false,"schema":{"anyOf":[{"enum":["model","tenant","user","source"],"type":"string"},{"type":"null"}],"description":"Extra breakdown dimension (cbai#111). Adds a `by_group` leaderboard keyed by the chosen dimension (model / tenant / user / source) -- the 'where is the money going' view. Rows with a null value bucket under '(none)'.","title":"Group By"},"description":"Extra breakdown dimension (cbai#111). Adds a `by_group` leaderboard keyed by the chosen dimension (model / tenant / user / source) -- the 'where is the money going' view. Rows with a null value bucket under '(none)'."}],"responses":{"200":{"description":"Aggregated usage statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__UsageSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/api/v1/usage/{request_id}":{"get":{"tags":["cbai","Usage"],"summary":"Get usage record by request ID","description":"Get a single usage record by its request ID (UUID).\n\nUse this to look up details of a specific API request.","operationId":"get_usage_by_id_api_v1_usage__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Single usage record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__UsageRecord"}}}},"404":{"description":"Record not found","content":{"application/json":{"example":{"detail":"Usage record not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}},"delete":{"tags":["cbai","Usage"],"summary":"Delete usage record","description":"Delete a single usage record by its request ID.\n\nReturns 204 on success, 404 if not found.","operationId":"delete_usage_api_v1_usage__request_id__delete","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"Record not found","content":{"application/json":{"example":{"detail":"Usage record not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__HTTPValidationError"}}}}}}},"/cbai/health":{"get":{"tags":["cbai","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__sysforge__core__health__HealthResponse"}}}}}}},"/cbai/health/detailed":{"get":{"tags":["cbai","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__sysforge__core__health__HealthResponse"}}}}}}},"/cbai/model-cards.json":{"get":{"tags":["cbai","Model Cards"],"summary":"Get Ollama model catalog","description":"Machine-readable catalog of available Ollama models for AI-driven selection.\n\n**Purpose**: Enable AI systems to intelligently select the optimal model\nfor any task based on capabilities, size constraints, and value rating.\n\n**Response Structure**:\n- `version`: Schema version for compatibility\n- `categories`: Category descriptions (thinking, coding, vision, etc.)\n- `size_tiers`: Hardware requirements by tier (tiny, small, medium, large)\n- `models`: Complete model cards with metadata\n- `recommendations`: Quick picks for common scenarios\n\n**Model Selection Algorithm**:\n1. Filter by `category` matching your task type\n2. Filter by `size_tier` based on available hardware/VRAM\n3. Sort by `value_rating` (descending) for best cost/performance\n4. Check `capabilities` list for specific requirements\n5. Use first match or fall back to `recommendations`\n\n**Value Rating Scale**:\n- 5 = Exceptional value (small, efficient, punches above weight)\n- 4 = Great value (good balance of size and capability)\n- 3 = Standard (reasonable for the capability level)\n- 2 = Premium (large, better performance but resource-heavy)\n- 1 = Maximum (largest models, only for peak performance needs)","operationId":"model_cards_model_cards_json_get","responses":{"200":{"description":"Model catalog retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__ModelCardsResponse"},"example":{"version":"1.0.0","last_updated":"2025-12-01","categories":{"thinking":"Chain-of-thought, math, logic, complex problem solving","coding":"Code generation, repair, completion, agentic coding"},"size_tiers":{"tiny":"<3B active params - Edge/mobile, 4-8GB VRAM","small":"3-8B params - Consumer GPU, 8-16GB VRAM"},"models":[{"id":"deepseek-r1:8b","name":"DeepSeek-R1 8B","category":"thinking","size":"8B","capabilities":["chain-of-thought","math","reasoning"],"value_rating":5,"size_tier":"small","recommendation":"Best small reasoning model"}],"recommendations":{"8gb_vram_general":"gemma3:4b","reasoning":"deepseek-r1:8b"}}}}}}}},"/cbai/prime":{"get":{"tags":["cbai","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__PrimeResponse"}}}}}}},"/cbai/status":{"get":{"tags":["cbai","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbai__StatusResponse"}}}}}}},"/cbairtable/api/tenants":{"post":{"tags":["cbairtable","Tenants"],"summary":"Create tenant","description":"Create a new tenant.","operationId":"create_tenant_api_tenants_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__TenantCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}},"get":{"tags":["cbairtable","Tenants"],"summary":"List tenants","description":"List all tenants.","operationId":"list_tenants_api_tenants_get","parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbairtable__Tenant"},"title":"Response List Tenants Api Tenants Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}":{"get":{"tags":["cbairtable","Tenants"],"summary":"Get tenant","description":"Get a tenant by ID.","operationId":"get_tenant_api_tenants__tenant_id__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}},"patch":{"tags":["cbairtable","Tenants"],"summary":"Update tenant","description":"Update a tenant.","operationId":"update_tenant_api_tenants__tenant_id__patch","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__TenantUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}},"delete":{"tags":["cbairtable","Tenants"],"summary":"Delete tenant","description":"Soft-delete a tenant (or hard-delete with ?hard=true).","operationId":"delete_tenant_api_tenants__tenant_id__delete","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"hard","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Hard"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/airtable/credentials/{credential_id}/bases":{"get":{"tags":["cbairtable","Airtable"],"summary":"List bases","description":"List all Airtable bases accessible with a credential.","operationId":"list_bases_api_tenants__tenant_id__airtable_credentials__credential_id__bases_get","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/airtable/credentials/{credential_id}/bases/{base_id}/schema":{"get":{"tags":["cbairtable","Airtable"],"summary":"Get base schema","description":"Get full schema for a base (all tables + fields). Cached in DuckDB.","operationId":"get_base_schema_api_tenants__tenant_id__airtable_credentials__credential_id__bases__base_id__schema_get","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force refresh from Airtable API","default":false,"title":"Refresh"},"description":"Force refresh from Airtable API"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/airtable/credentials/{credential_id}/bases/{base_id}/tables":{"get":{"tags":["cbairtable","Airtable"],"summary":"List tables","description":"List tables in an Airtable base.","operationId":"list_tables_api_tenants__tenant_id__airtable_credentials__credential_id__bases__base_id__tables_get","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/airtable/credentials/{credential_id}/bases/{base_id}/tables/{table_id}/records":{"get":{"tags":["cbairtable","Airtable"],"summary":"List records","description":"List records from an Airtable table.","operationId":"list_records_api_tenants__tenant_id__airtable_credentials__credential_id__bases__base_id__tables__table_id__records_get","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"string","title":"Table Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"formula","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Airtable formula filter","title":"Formula"},"description":"Airtable formula filter"},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Fields to return","title":"Fields"},"description":"Fields to return"},{"name":"max_records","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Maximum records to return","title":"Max Records"},"description":"Maximum records to return"},{"name":"view","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"View name or ID","title":"View"},"description":"View name or ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}},"post":{"tags":["cbairtable","Airtable"],"summary":"Create records","description":"Create one or more records in an Airtable table.\n\nEach record is a dict of field name → value.","operationId":"create_records_api_tenants__tenant_id__airtable_credentials__credential_id__bases__base_id__tables__table_id__records_post","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"string","title":"Table Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Records"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/airtable/credentials/{credential_id}/bases/{base_id}/tables/{table_id}/records/{record_id}":{"get":{"tags":["cbairtable","Airtable"],"summary":"Get record","description":"Get a single record by ID.","operationId":"get_record_api_tenants__tenant_id__airtable_credentials__credential_id__bases__base_id__tables__table_id__records__record_id__get","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"string","title":"Table Id"}},{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}},"patch":{"tags":["cbairtable","Airtable"],"summary":"Update record","description":"Update a single record's fields.","operationId":"update_record_api_tenants__tenant_id__airtable_credentials__credential_id__bases__base_id__tables__table_id__records__record_id__patch","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"string","title":"Table Id"}},{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Fields"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}},"delete":{"tags":["cbairtable","Airtable"],"summary":"Delete record","description":"Delete a single record.","operationId":"delete_record_api_tenants__tenant_id__airtable_credentials__credential_id__bases__base_id__tables__table_id__records__record_id__delete","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"string","title":"Table Id"}},{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/airtable/credentials/{credential_id}/bases/{base_id}/tables/{table_id}/records/{record_id}/attachments/{field_name}":{"post":{"tags":["cbairtable","Airtable"],"summary":"Upload attachment to record","description":"Upload a file attachment to a record's attachment field.\n\nTwo input modes:\n- URL: {\"url\": \"https://...\", \"filename\": \"model.csv\"}\n- Content: {\"content_base64\": \"<base64>\", \"filename\": \"model.csv\"}\n\nThe file is stored permanently on Airtable's CDN.","operationId":"upload_attachment_api_tenants__tenant_id__airtable_credentials__credential_id__bases__base_id__tables__table_id__records__record_id__attachments__field_name__post","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"string","title":"Table Id"}},{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}},{"name":"field_name","in":"path","required":true,"schema":{"type":"string","title":"Field Name"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/airtable/credentials/{credential_id}/bases/{base_id}/tables/{table_id}/schema":{"get":{"tags":["cbairtable","Airtable"],"summary":"Get table schema","description":"Get full schema for a table including field types.","operationId":"get_table_schema_api_tenants__tenant_id__airtable_credentials__credential_id__bases__base_id__tables__table_id__schema_get","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"string","title":"Table Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/attachments/bases/{base_id}/process":{"post":{"tags":["cbairtable","Attachments"],"summary":"Process attachment download queue","description":"Download queued attachments from Airtable CDN to local storage.","operationId":"process_attachments_api_tenants__tenant_id__attachments_bases__base_id__process_post","parameters":[{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"batch_size","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Max attachments to process","default":100,"title":"Batch Size"},"description":"Max attachments to process"},{"name":"concurrency","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"description":"Parallel download workers","default":5,"title":"Concurrency"},"description":"Parallel download workers"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/attachments/bases/{base_id}/stats":{"get":{"tags":["cbairtable","Attachments"],"summary":"Attachment queue stats","description":"Get attachment queue statistics by status.","operationId":"attachment_stats_api_tenants__tenant_id__attachments_bases__base_id__stats_get","parameters":[{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/credentials":{"post":{"tags":["cbairtable","Credentials"],"summary":"Store credential","description":"Store an Airtable PAT for this tenant.\n\nThe PAT is encrypted at rest and only decrypted when making Airtable API calls.\n\n```json\n{\"pat\": \"patXXX.YYYY\", \"base_id\": \"appABC123\", \"label\": \"Production\"}\n```","operationId":"create_credential_api_tenants__tenant_id__credentials_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__CredentialCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__CredentialSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}},"get":{"tags":["cbairtable","Credentials"],"summary":"List credentials","description":"List credentials for this tenant. PATs are never included in the response.","operationId":"list_credentials_api_tenants__tenant_id__credentials_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbairtable__CredentialSummary"},"title":"Response List Credentials Api Tenants  Tenant Id  Credentials Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/credentials/{credential_id}":{"delete":{"tags":["cbairtable","Credentials"],"summary":"Delete credential","description":"Delete a credential and its encrypted PAT permanently.","operationId":"delete_credential_api_tenants__tenant_id__credentials__credential_id__delete","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/query/bases/{base_id}/sql":{"post":{"tags":["cbairtable","Query"],"summary":"Execute raw SQL","description":"Execute read-only SQL against a tenant's synced DuckDB.\n\nSupports SELECT, WITH, and EXPLAIN statements. Cross-table JOINs allowed.\nConnection is read-only at the DuckDB engine level (defense-in-depth).","operationId":"execute_sql_api_tenants__tenant_id__query_bases__base_id__sql_post","parameters":[{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__SQLRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/query/bases/{base_id}/tables":{"get":{"tags":["cbairtable","Query"],"summary":"List synced tables","description":"List all Airtable tables synced into local DuckDB for a base.","operationId":"list_synced_tables_api_tenants__tenant_id__query_bases__base_id__tables_get","parameters":[{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/query/bases/{base_id}/tables/{table_name}":{"post":{"tags":["cbairtable","Query"],"summary":"Query synced data","description":"Query synced Airtable data from local DuckDB (faster than Airtable API).","operationId":"query_table_api_tenants__tenant_id__query_bases__base_id__tables__table_name__post","parameters":[{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"table_name","in":"path","required":true,"schema":{"type":"string","title":"Table Name"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__QueryRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/sync/credentials/{credential_id}/bases/{base_id}":{"post":{"tags":["cbairtable","Sync"],"summary":"Sync entire base","description":"Sync all tables in an Airtable base to DuckDB. Returns NDJSON progress\nstream, or `{job_id, detached: true}` when `detach=true`.","operationId":"sync_base_api_tenants__tenant_id__sync_credentials__credential_id__bases__base_id__post","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"mode","in":"query","required":false,"schema":{"$ref":"#/components/schemas/cbairtable__SyncMode","description":"full = drop & reload, incremental = only changed records, auto = incremental if synced before, otherwise full.","default":"auto"},"description":"full = drop & reload, incremental = only changed records, auto = incremental if synced before, otherwise full."},{"name":"detach","in":"query","required":false,"schema":{"type":"boolean","description":"Run the sync server-side and return the job id immediately instead of streaming. Use this for unattended callers (agents, cron): a streaming sync only advances while a client reads the response, so a caller that POSTs and hangs up leaves the job stuck at `running`. Poll /sync/status/{job_id}.","default":false,"title":"Detach"},"description":"Run the sync server-side and return the job id immediately instead of streaming. Use this for unattended callers (agents, cron): a streaming sync only advances while a client reads the response, so a caller that POSTs and hangs up leaves the job stuck at `running`. Poll /sync/status/{job_id}."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/sync/credentials/{credential_id}/bases/{base_id}/reconcile":{"post":{"tags":["cbairtable","Sync"],"summary":"Reconcile deletes for entire base","description":"Compare record IDs and soft-delete records missing from Airtable.","operationId":"reconcile_base_api_tenants__tenant_id__sync_credentials__credential_id__bases__base_id__reconcile_post","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"detach","in":"query","required":false,"schema":{"type":"boolean","description":"Run the sync server-side and return the job id immediately instead of streaming. Use this for unattended callers (agents, cron): a streaming sync only advances while a client reads the response, so a caller that POSTs and hangs up leaves the job stuck at `running`. Poll /sync/status/{job_id}.","default":false,"title":"Detach"},"description":"Run the sync server-side and return the job id immediately instead of streaming. Use this for unattended callers (agents, cron): a streaming sync only advances while a client reads the response, so a caller that POSTs and hangs up leaves the job stuck at `running`. Poll /sync/status/{job_id}."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/sync/credentials/{credential_id}/bases/{base_id}/tables/{table_id}":{"post":{"tags":["cbairtable","Sync"],"summary":"Sync single table","description":"Sync a single Airtable table to DuckDB. Returns NDJSON progress stream,\nor `{job_id, detached: true}` when `detach=true`.","operationId":"sync_table_api_tenants__tenant_id__sync_credentials__credential_id__bases__base_id__tables__table_id__post","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"string","title":"Table Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"mode","in":"query","required":false,"schema":{"$ref":"#/components/schemas/cbairtable__SyncMode","description":"full = drop & reload, incremental = only changed records, auto = incremental if synced before, otherwise full.","default":"auto"},"description":"full = drop & reload, incremental = only changed records, auto = incremental if synced before, otherwise full."},{"name":"detach","in":"query","required":false,"schema":{"type":"boolean","description":"Run the sync server-side and return the job id immediately instead of streaming. Use this for unattended callers (agents, cron): a streaming sync only advances while a client reads the response, so a caller that POSTs and hangs up leaves the job stuck at `running`. Poll /sync/status/{job_id}.","default":false,"title":"Detach"},"description":"Run the sync server-side and return the job id immediately instead of streaming. Use this for unattended callers (agents, cron): a streaming sync only advances while a client reads the response, so a caller that POSTs and hangs up leaves the job stuck at `running`. Poll /sync/status/{job_id}."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/sync/credentials/{credential_id}/bases/{base_id}/tables/{table_id}/reconcile":{"post":{"tags":["cbairtable","Sync"],"summary":"Reconcile deletes for single table","description":"Compare record IDs and soft-delete records missing from Airtable for one table.","operationId":"reconcile_table_api_tenants__tenant_id__sync_credentials__credential_id__bases__base_id__tables__table_id__reconcile_post","parameters":[{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"string","title":"Table Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"detach","in":"query","required":false,"schema":{"type":"boolean","description":"Run the sync server-side and return the job id immediately instead of streaming. Use this for unattended callers (agents, cron): a streaming sync only advances while a client reads the response, so a caller that POSTs and hangs up leaves the job stuck at `running`. Poll /sync/status/{job_id}.","default":false,"title":"Detach"},"description":"Run the sync server-side and return the job id immediately instead of streaming. Use this for unattended callers (agents, cron): a streaming sync only advances while a client reads the response, so a caller that POSTs and hangs up leaves the job stuck at `running`. Poll /sync/status/{job_id}."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/sync/status":{"get":{"tags":["cbairtable","Sync"],"summary":"List sync jobs","description":"List recent sync jobs for this tenant.","operationId":"list_sync_jobs_api_tenants__tenant_id__sync_status_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbairtable__SyncJob"},"title":"Response List Sync Jobs Api Tenants  Tenant Id  Sync Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/tenants/{tenant_id}/sync/status/{job_id}":{"get":{"tags":["cbairtable","Sync"],"summary":"Get sync job","description":"Get a specific sync job's status.","operationId":"get_sync_job_api_tenants__tenant_id__sync_status__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__SyncJob"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/api/v1/admin/publish_snapshot":{"post":{"tags":["cbairtable","Admin"],"summary":"Publish read-only snapshots","description":"Atomically copy live airtable.duckdb files to airtable.snapshot.duckdb siblings so external readers (cbmodels federation) can ATTACH without lock contention. Runs inside the cbairtable process — no stop/start required. Idempotent: bases whose snapshot is already newer than the live file are counted as skipped_fresh.","operationId":"publish_snapshot_api_v1_admin_publish_snapshot_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__PublishSnapshotRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__PublishSnapshotResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HTTPValidationError"}}}}}}},"/cbairtable/health":{"get":{"tags":["cbairtable","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HealthResponse"}}}}}}},"/cbairtable/health/detailed":{"get":{"tags":["cbairtable","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__HealthResponse"}}}}}}},"/cbairtable/prime":{"get":{"tags":["cbairtable","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__PrimeResponse"}}}}}}},"/cbairtable/status":{"get":{"tags":["cbairtable","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbairtable__StatusResponse"}}}}}}},"/cbapp/api":{"get":{"tags":["cbapp","health"],"summary":"Api Root","description":"Root API endpoint.\n\nReturns basic application information including name, version, and status.","operationId":"api_root_api_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbapp/api/assignments":{"get":{"tags":["cbapp","assignments"],"summary":"List assignments","description":"List segment assignments.\n\nAdmins/directors see all assignments.\nField users only see their own assignments.","operationId":"list_assignments_api_assignments_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by segment","title":"Segment Id"},"description":"Filter by segment"},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by user","title":"User Id"},"description":"Filter by user"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","assignments"],"summary":"Create assignment","description":"Create a new segment assignment.\n\nOnly admins and directors can create assignments.","operationId":"create_assignment_api_assignments_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__AssignmentCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/assignments/user/{user_id}":{"get":{"tags":["cbapp","assignments"],"summary":"Get user assignments","description":"Get all assignments for a specific user.","operationId":"get_user_assignments_api_assignments_user__user_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/assignments/{assignment_id}":{"get":{"tags":["cbapp","assignments"],"summary":"Get assignment","description":"Get a specific assignment.","operationId":"get_assignment_api_assignments__assignment_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"assignment_id","in":"path","required":true,"schema":{"type":"string","title":"Assignment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","assignments"],"summary":"Update assignment","description":"Update an assignment.\n\nOnly admins and directors can update assignments.","operationId":"update_assignment_api_assignments__assignment_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"assignment_id","in":"path","required":true,"schema":{"type":"string","title":"Assignment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__AssignmentUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","assignments"],"summary":"Delete assignment","description":"Delete an assignment and its work queue items.\n\nOnly admins can delete assignments.","operationId":"delete_assignment_api_assignments__assignment_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"assignment_id","in":"path","required":true,"schema":{"type":"string","title":"Assignment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/assignments/{assignment_id}/populate":{"post":{"tags":["cbapp","assignments"],"summary":"Create populate work queue","description":"Populate or refresh the work queue for an assignment.\n\nThis re-executes the segment query and adds any new contacts.\nFor i360 segments, imports matching voters from i360 database.","operationId":"populate_work_queue_api_assignments__assignment_id__populate_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"assignment_id","in":"path","required":true,"schema":{"type":"string","title":"Assignment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/auth/me":{"get":{"tags":["cbapp","authentication"],"summary":"Get read users me","description":"Get the current authenticated user.","operationId":"read_users_me_api_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__User"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/auth/osm/callback":{"get":{"tags":["cbapp","osm-auth"],"summary":"Get osm callback","description":"Handle OSM OAuth2 callback.","operationId":"osm_callback_api_auth_osm_callback_get","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string","title":"Code"}},{"name":"state","in":"query","required":true,"schema":{"type":"string","title":"State"}},{"name":"error","in":"query","required":false,"schema":{"type":"string","title":"Error"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/auth/osm/link":{"post":{"tags":["cbapp","osm-auth"],"summary":"Create link osm account","description":"Link OSM account to current user (for already logged-in users).","operationId":"link_osm_account_api_auth_osm_link_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/auth/osm/link-callback":{"get":{"tags":["cbapp","osm-auth"],"summary":"Get osm link callback","description":"Handle OSM account linking callback.","operationId":"osm_link_callback_api_auth_osm_link_callback_get","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string","title":"Code"}},{"name":"state","in":"query","required":true,"schema":{"type":"string","title":"State"}},{"name":"error","in":"query","required":false,"schema":{"type":"string","title":"Error"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/auth/osm/login":{"get":{"tags":["cbapp","osm-auth"],"summary":"Get osm login","description":"Initiate OSM OAuth2 login flow.","operationId":"osm_login_api_auth_osm_login_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbapp/api/auth/osm/status":{"get":{"tags":["cbapp","osm-auth"],"summary":"Get osm status","description":"Get OSM account status for current user.","operationId":"osm_status_api_auth_osm_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/auth/osm/unlink":{"delete":{"tags":["cbapp","osm-auth"],"summary":"Delete unlink osm account","description":"Unlink OSM account from current user.","operationId":"unlink_osm_account_api_auth_osm_unlink_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/auth/token":{"post":{"tags":["cbapp","authentication"],"summary":"Create login for access token","description":"Authenticate user and provide access token.","operationId":"login_for_access_token_api_auth_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbapp__Body_login_for_access_token_api_auth_token_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Token"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/branding/":{"get":{"tags":["cbapp","branding"],"summary":"Get branding","description":"Get current branding settings.","operationId":"get_branding_api_branding__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BrandingSettings"}}}}},"security":[{"OAuth2PasswordBearer":[]}]},"put":{"tags":["cbapp","branding"],"summary":"Update branding","description":"Update branding settings (colors, app name, tagline).","operationId":"update_branding_api_branding__put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BrandingUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BrandingSettings"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/branding/logo/{logo_type}":{"post":{"tags":["cbapp","branding"],"summary":"Create upload logo","description":"Upload a logo image.","operationId":"upload_logo_api_branding_logo__logo_type__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"logo_type","in":"path","required":true,"schema":{"type":"string","title":"Logo Type"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbapp__Body_upload_logo_api_branding_logo__logo_type__post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__LogoUploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","branding"],"summary":"Delete logo","description":"Delete a logo (reset to default).","operationId":"delete_logo_api_branding_logo__logo_type__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"logo_type","in":"path","required":true,"schema":{"type":"string","title":"Logo Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__LogoDeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/branding/logos":{"get":{"tags":["cbapp","branding"],"summary":"List logos","description":"List all logos with their info.","operationId":"list_logos_api_branding_logos_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__LogosResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/cb-chat/datasets":{"get":{"tags":["cbapp","campaignbrain-chat"],"summary":"Get datasets","description":"Get available datasets for querying.\n\nReturns datasets with their availability status and descriptions.\nUsers can reference these with (data:name) syntax in queries.","operationId":"get_datasets_api_cb_chat_datasets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/cb-chat/health":{"get":{"tags":["cbapp","campaignbrain-chat"],"summary":"Get chat health","description":"Check if chat system is healthy.","operationId":"chat_health_api_cb_chat_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbapp/api/cb-chat/message":{"post":{"tags":["cbapp","campaignbrain-chat"],"summary":"Create chat message","description":"Process a natural language query using Claude with optional tool calling.","operationId":"chat_message_api_cb_chat_message_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__src__api__services__cb_query_ir__ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__src__api__services__cb_query_ir__ChatResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/cb-chat/suggestions":{"get":{"tags":["cbapp","campaignbrain-chat"],"summary":"Get suggestions","description":"Get suggested queries to help users get started.\n\nQuery types with colors:\n- count (blue): Aggregate/counting questions\n- list (green): Find/show queries returning records\n- geographic (amber): Location-based queries\n- demographic (purple): Age, gender, party filters\n- campaign (indigo): Campaign-specific data\n- contact (teal): Phone, email related queries","operationId":"get_suggestions_api_cb_chat_suggestions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/cb-chat/warmup":{"get":{"tags":["cbapp","campaignbrain-chat"],"summary":"Get chat warmup","description":"Run warmup health checks before chat session.\n\nValidates:\n- Claude API configuration\n- i360 database accessibility\n- Campaign API connectivity (optional)\n- Data dictionary generation","operationId":"chat_warmup_api_cb_chat_warmup_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/chat/health":{"get":{"tags":["cbapp","chat"],"summary":"Get chat health check","description":"Check if chat system is healthy","operationId":"chat_health_check_api_chat_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbapp/api/chat/query":{"post":{"tags":["cbapp","chat"],"summary":"Create process chat query","description":"Process a natural language query using CampaignBrain","operationId":"process_chat_query_api_chat_query_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__src__api__routes__chat__ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__src__api__routes__chat__ChatResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/chat/reload":{"post":{"tags":["cbapp","chat"],"summary":"Create reload chat system","description":"Reload chat system (admin only)","operationId":"reload_chat_system_api_chat_reload_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/chat/workflows":{"get":{"tags":["cbapp","chat"],"summary":"List workflows","description":"List available chat workflows","operationId":"list_workflows_api_chat_workflows_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/communications/":{"get":{"tags":["cbapp","communications"],"summary":"List communications","description":"List communications with optional filtering.","operationId":"list_communications_api_communications__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by channel","title":"Channel"},"description":"Filter by channel"},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by type (legacy, use channel)","title":"Type"},"description":"Filter by type (legacy, use channel)"},{"name":"direction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by direction (inbound, outbound)","title":"Direction"},"description":"Filter by direction (inbound, outbound)"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"priority","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by priority (1-5)","title":"Priority"},"description":"Filter by priority (1-5)"},{"name":"person_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by person","title":"Person Id"},"description":"Filter by person"},{"name":"event_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by event","title":"Event Id"},"description":"Filter by event"},{"name":"batch_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by batch","title":"Batch Id"},"description":"Filter by batch"},{"name":"from_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter from date (YYYY-MM-DD)","title":"From Date"},"description":"Filter from date (YYYY-MM-DD)"},{"name":"to_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to date (YYYY-MM-DD)","title":"To Date"},"description":"Filter to date (YYYY-MM-DD)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","communications"],"summary":"Create communication","description":"Create a new communication record.","operationId":"create_communication_api_communications__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__CommunicationCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Communication"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/communications/batches":{"get":{"tags":["cbapp","communications"],"summary":"List batches","description":"List communication batches.","operationId":"list_batches_api_communications_batches_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by channel","title":"Channel"},"description":"Filter by channel"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/communications/batches/{batch_id}":{"get":{"tags":["cbapp","communications"],"summary":"Get batch","description":"Get a batch with current statistics.","operationId":"get_batch_api_communications_batches__batch_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string","description":"Batch ID","title":"Batch Id"},"description":"Batch ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/communications/queue/pending":{"get":{"tags":["cbapp","communications"],"summary":"Get pending queue","description":"Get pending items in the queue.","operationId":"get_pending_queue_api_communications_queue_pending_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by channel","title":"Channel"},"description":"Filter by channel"},{"name":"priority","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by priority","title":"Priority"},"description":"Filter by priority"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/communications/queue/stats":{"get":{"tags":["cbapp","communications"],"summary":"Get queue stats","description":"Get queue statistics.","operationId":"get_queue_stats_api_communications_queue_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__QueueStats"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/communications/send":{"post":{"tags":["cbapp","communications"],"summary":"Create send communication","description":"Create and immediately queue a communication for delivery.","operationId":"send_communication_api_communications_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SendRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/communications/send-to-event":{"post":{"tags":["cbapp","communications"],"summary":"Create send to event","description":"Send a communication to all registrants of an event.","operationId":"send_to_event_api_communications_send_to_event_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SendToEventRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/communications/send-to-segment":{"post":{"tags":["cbapp","communications"],"summary":"Create send to segment","description":"Send a communication to all members of a segment.\n\nSupports two delivery methods:\n- 'queue' (default): Creates individual communication records for worker processing\n- 'workflow_blast': Uses Workflow Engine's email_blast for immediate bulk delivery","operationId":"send_to_segment_api_communications_send_to_segment_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SendToSegmentRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/communications/stats/summary":{"get":{"tags":["cbapp","communications"],"summary":"Get communication stats","description":"Get communication statistics.","operationId":"get_communication_stats_api_communications_stats_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/communications/templates/":{"get":{"tags":["cbapp","communications"],"summary":"List templates","description":"List all communication templates.","operationId":"list_templates_api_communications_templates__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by channel","title":"Channel"},"description":"Filter by channel"},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by type (legacy)","title":"Type"},"description":"Filter by type (legacy)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","communications"],"summary":"Create template","description":"Create a new communication template.","operationId":"create_template_api_communications_templates__post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__TemplateCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Template"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/communications/templates/{template_id}":{"get":{"tags":["cbapp","communications"],"summary":"Get template","description":"Get a single template by ID.","operationId":"get_template_api_communications_templates__template_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","description":"Template ID","title":"Template Id"},"description":"Template ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","communications"],"summary":"Update template","description":"Update a template.","operationId":"update_template_api_communications_templates__template_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","description":"Template ID","title":"Template Id"},"description":"Template ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__TemplateUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","communications"],"summary":"Delete template","description":"Delete a template.","operationId":"delete_template_api_communications_templates__template_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","description":"Template ID","title":"Template Id"},"description":"Template ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/communications/templates/{template_id}/render":{"post":{"tags":["cbapp","communications"],"summary":"Create render template","description":"Render a template with provided variables.","operationId":"render_template_api_communications_templates__template_id__render_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","description":"Template ID","title":"Template Id"},"description":"Template ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__TemplateRender"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__TemplateRendered"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/communications/{communication_id}":{"get":{"tags":["cbapp","communications"],"summary":"Get communication","description":"Get a single communication by ID with full details.","operationId":"get_communication_api_communications__communication_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"communication_id","in":"path","required":true,"schema":{"type":"string","description":"Communication ID","title":"Communication Id"},"description":"Communication ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","communications"],"summary":"Update communication","description":"Update a communication record.","operationId":"update_communication_api_communications__communication_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"communication_id","in":"path","required":true,"schema":{"type":"string","description":"Communication ID","title":"Communication Id"},"description":"Communication ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__CommunicationUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","communications"],"summary":"Delete communication","description":"Delete a communication record. Only allowed for draft/cancelled status.","operationId":"delete_communication_api_communications__communication_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"communication_id","in":"path","required":true,"schema":{"type":"string","description":"Communication ID","title":"Communication Id"},"description":"Communication ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/communications/{communication_id}/cancel":{"post":{"tags":["cbapp","communications"],"summary":"Create cancel communication","description":"Cancel a queued communication.","operationId":"cancel_communication_api_communications__communication_id__cancel_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"communication_id","in":"path","required":true,"schema":{"type":"string","description":"Communication ID","title":"Communication Id"},"description":"Communication ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/communications/{communication_id}/queue":{"post":{"tags":["cbapp","communications"],"summary":"Create queue communication","description":"Queue a communication for delivery.","operationId":"queue_communication_api_communications__communication_id__queue_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"communication_id","in":"path","required":true,"schema":{"type":"string","description":"Communication ID","title":"Communication Id"},"description":"Communication ID"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbapp__CommunicationQueue"},{"type":"null"}],"title":"Queue Opts"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/conversations":{"get":{"tags":["cbapp","conversations"],"summary":"List conversations","description":"List conversations for the current user.\n\nReturns conversations ordered by last message time, with optional pagination.\nBy default, archived conversations are excluded.","operationId":"list_conversations_api_conversations_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","description":"Include archived conversations","default":false,"title":"Include Archived"},"description":"Include archived conversations"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Maximum number to return","default":50,"title":"Limit"},"description":"Maximum number to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number to skip for pagination","default":0,"title":"Offset"},"description":"Number to skip for pagination"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ConversationListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/conversations/shared/{share_id}":{"get":{"tags":["cbapp","conversations"],"summary":"Get shared conversation","description":"Get a shared conversation by its share ID.\n\nPublic endpoint - no authentication required.\nOnly returns public (is_public=TRUE) conversations.","operationId":"get_shared_conversation_api_conversations_shared__share_id__get","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ConversationWithMessages"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/conversations/{conversation_id}":{"get":{"tags":["cbapp","conversations"],"summary":"Get conversation","description":"Get a conversation with all its messages.\n\nReturns the full conversation including all messages in order.\nOnly accessible by the conversation owner.","operationId":"get_conversation_api_conversations__conversation_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ConversationWithMessages"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"patch":{"tags":["cbapp","conversations"],"summary":"Update conversation","description":"Update conversation metadata.\n\nAllows updating title, public status, and archive status.\nOnly accessible by the conversation owner.","operationId":"update_conversation_api_conversations__conversation_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ConversationUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Conversation"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","conversations"],"summary":"Delete archive conversation","description":"Archive a conversation (soft delete).\n\nSets is_archived to TRUE. Use PATCH to restore if needed.\nOnly accessible by the conversation owner.","operationId":"archive_conversation_api_conversations__conversation_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/embeddings/cache/clear":{"post":{"tags":["cbapp","embeddings"],"summary":"Create clear cache","description":"Clear the embedding cache","operationId":"clear_cache_api_embeddings_cache_clear_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/embeddings/cache/stats":{"get":{"tags":["cbapp","embeddings"],"summary":"Get cache stats","description":"Get cache statistics","operationId":"get_cache_stats_api_embeddings_cache_stats_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/embeddings/embed":{"post":{"tags":["cbapp","embeddings"],"summary":"Create embedding","description":"Create an embedding for a single text\n\nSupports caching and retry logic for improved reliability.","operationId":"create_embedding_api_embeddings_embed_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EmbeddingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EmbeddingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/embeddings/embed/batch":{"post":{"tags":["cbapp","embeddings"],"summary":"Create batch embeddings","description":"Create embeddings for multiple texts in batch\n\nMore efficient than individual requests for multiple texts.","operationId":"create_batch_embeddings_api_embeddings_embed_batch_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BatchEmbeddingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BatchEmbeddingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/embeddings/models":{"get":{"tags":["cbapp","embeddings"],"summary":"List ollama models","description":"List available Ollama models","operationId":"list_ollama_models_api_embeddings_models_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/embeddings/stats":{"get":{"tags":["cbapp","embeddings"],"summary":"Get embedding stats","description":"Get embedding service statistics","operationId":"get_embedding_stats_api_embeddings_stats_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EmbeddingStats"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/":{"get":{"tags":["cbapp","events"],"summary":"List events","description":"List events with optional filtering.","operationId":"list_events_api_events__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"skip","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"upcoming","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Upcoming"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__Event"},"title":"Response List Events Api Events  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","events"],"summary":"Create event","description":"Create a new event with auto-generated signup page and segment.","operationId":"create_event_api_events__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Event"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/{event_id}":{"get":{"tags":["cbapp","events"],"summary":"Get event","description":"Get a single event by ID.","operationId":"get_event_api_events__event_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event to get"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Event"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","events"],"summary":"Update event","description":"Update an event.","operationId":"update_event_api_events__event_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event to update"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Event"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","events"],"summary":"Delete event","description":"Delete an event.","operationId":"delete_event_api_events__event_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event to delete"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/{event_id}/page":{"get":{"tags":["cbapp","event-pages"],"summary":"Get event page","description":"Get the event page configuration for an event.","operationId":"get_event_page_api_events__event_id__page_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventPageWithUrls"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","event-pages"],"summary":"Create event page","description":"Create a new event page for an event.","operationId":"create_event_page_api_events__event_id__page_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventPageCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventPageWithUrls"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","event-pages"],"summary":"Update event page","description":"Update an event page configuration.","operationId":"update_event_page_api_events__event_id__page_put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventPageUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventPageWithUrls"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","event-pages"],"summary":"Delete event page","description":"Delete an event page.","operationId":"delete_event_page_api_events__event_id__page_delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/{event_id}/page/attachments":{"post":{"tags":["cbapp","event-page-attachments"],"summary":"Upload Attachment","description":"Upload a document attachment to an event page.\n\nUploads the file to CBFiles and queues AI analysis.\nSupported file types: images, PDFs, Excel/CSV, text files.","operationId":"upload_attachment_api_events__event_id__page_attachments_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbapp__Body_upload_attachment_api_events__event_id__page_attachments_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__AttachmentUploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"get":{"tags":["cbapp","event-page-attachments"],"summary":"List attachments","description":"List all attachments for an event page.\n\nReturns attachments with their analysis status and results.","operationId":"list_attachments_api_events__event_id__page_attachments_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__AttachmentListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/{event_id}/page/attachments/{attachment_id}":{"get":{"tags":["cbapp","event-page-attachments"],"summary":"Get Attachment","description":"Get a single attachment with full analysis results.","operationId":"get_attachment_api_events__event_id__page_attachments__attachment_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}},{"name":"attachment_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the attachment"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__AttachmentWithUrl"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","event-page-attachments"],"summary":"Delete attachment","description":"Delete an attachment.\n\nRemoves the file from CBFiles and the database record.","operationId":"delete_attachment_api_events__event_id__page_attachments__attachment_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}},{"name":"attachment_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the attachment"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/{event_id}/page/attachments/{attachment_id}/retry":{"post":{"tags":["cbapp","event-page-attachments"],"summary":"Create retry attachment analysis","description":"Retry analysis for a failed attachment.","operationId":"retry_attachment_analysis_api_events__event_id__page_attachments__attachment_id__retry_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}},{"name":"attachment_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the attachment"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/{event_id}/page/preview-token":{"post":{"tags":["cbapp","event-pages"],"summary":"Create regenerate preview token","description":"Regenerate the preview token for an event page.","operationId":"regenerate_preview_token_api_events__event_id__page_preview_token_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventPageWithUrls"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/{event_id}/page/registrations":{"get":{"tags":["cbapp","event-pages"],"summary":"List page registrations","description":"List all public registrations for an event page.","operationId":"list_page_registrations_api_events__event_id__page_registrations_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__PublicRegistration"},"title":"Response List Page Registrations Api Events  Event Id  Page Registrations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/{event_id}/page/status":{"put":{"tags":["cbapp","event-pages"],"summary":"Update event page status","description":"Change the status of an event page.","operationId":"update_event_page_status_api_events__event_id__page_status_put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventPageStatusUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventPageWithUrls"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/{event_id}/registrations":{"post":{"tags":["cbapp","events"],"summary":"Create register for event","description":"Register a person for an event.","operationId":"register_for_event_api_events__event_id__registrations_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event to register for"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__RegistrationCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Registration"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/events/{event_id}/registrations/{registration_id}":{"put":{"tags":["cbapp","events"],"summary":"Update registration","description":"Update an event registration.","operationId":"update_registration_api_events__event_id__registrations__registration_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}},{"name":"registration_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the registration to update"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__RegistrationUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Registration"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","events"],"summary":"Delete cancel registration","description":"Cancel an event registration.","operationId":"cancel_registration_api_events__event_id__registrations__registration_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the event"}},{"name":"registration_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the registration to cancel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/exports/persons":{"get":{"tags":["cbapp","exports"],"summary":"Get export persons","description":"Export persons as a CSV file.\n\nSupports the same filters as the list_persons endpoint:\n- search: Filter by name or email\n- tag: Filter by tag name\n- whip_status: Filter by whip status\n\nReturns a streaming CSV download.","operationId":"export_persons_api_exports_persons_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search term for filtering","title":"Search"},"description":"Search term for filtering"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Tag name to filter by","title":"Tag"},"description":"Tag name to filter by"},{"name":"whip_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Whip status to filter by","title":"Whip Status"},"description":"Whip status to filter by"},{"name":"token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/exports/segments/{segment_id}":{"get":{"tags":["cbapp","exports"],"summary":"Get export segment","description":"Export segment members as a CSV file.\n\nExports all persons that belong to the specified segment.\nReturns a streaming CSV download.","operationId":"export_segment_api_exports_segments__segment_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/files/admin/ensure-bucket":{"post":{"tags":["cbapp","files"],"summary":"Create ensure bucket","description":"Ensure the tenant bucket exists (admin only).","operationId":"ensure_bucket_api_files_admin_ensure_bucket_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/files/admin/user-folder":{"post":{"tags":["cbapp","files"],"summary":"Create user folder","description":"Create a folder for a user (admin only).\n\nReturns 400 if folder already exists.","operationId":"create_user_folder_api_files_admin_user_folder_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__UserFolderCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__UserFolderInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/files/admin/user-folders":{"get":{"tags":["cbapp","files"],"summary":"List user folders","description":"List all user folders (admin only).","operationId":"list_user_folders_api_files_admin_user_folders_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbapp__UserFolderInfo"},"type":"array","title":"Response List User Folders Api Files Admin User Folders Get"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/files/download/{path}":{"get":{"tags":["cbapp","files"],"summary":"Get download file","description":"Download a file.\n\nReturns the file content with appropriate content-type.","operationId":"download_file_api_files_download__path__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string","title":"Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/files/folder":{"post":{"tags":["cbapp","files"],"summary":"Create folder","description":"Create a new folder.\n\nThe folder is created under the specified parent path.","operationId":"create_folder_api_files_folder_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__FolderCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Folder Api Files Folder Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/files/health":{"get":{"tags":["cbapp","files"],"summary":"Get check files health","description":"Check CBFiles service health.","operationId":"check_files_health_api_files_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__FilesHealthResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/files/info/{path}":{"get":{"tags":["cbapp","files"],"summary":"Get file info","description":"Get file metadata.","operationId":"get_file_info_api_files_info__path__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string","title":"Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__FileItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/files/list":{"get":{"tags":["cbapp","files"],"summary":"List folder","description":"List files and folders at a given path.\n\nReturns folder contents with permission flags.","operationId":"list_folder_api_files_list_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"path","in":"query","required":false,"schema":{"type":"string","description":"Folder path to list","default":"","title":"Path"},"description":"Folder path to list"},{"name":"recursive","in":"query","required":false,"schema":{"type":"boolean","description":"List recursively","default":false,"title":"Recursive"},"description":"List recursively"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__FolderContents"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/files/roots":{"get":{"tags":["cbapp","files"],"summary":"Get accessible roots","description":"Get the root folders accessible to the current user.","operationId":"get_accessible_roots_api_files_roots_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbapp__FolderRoot"},"type":"array","title":"Response Get Accessible Roots Api Files Roots Get"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/files/signed-url/{path}":{"post":{"tags":["cbapp","files"],"summary":"Get signed url","description":"Get a signed download URL for a file.\n\nThe URL expires after the specified duration (default 1 hour).","operationId":"get_signed_url_api_files_signed_url__path__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string","title":"Path"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SignedUrlRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SignedUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/files/upload":{"post":{"tags":["cbapp","files"],"summary":"Create upload file","description":"Upload a file to the specified path.\n\nThe file is uploaded to the given folder path.","operationId":"upload_file_api_files_upload_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"path","in":"query","required":true,"schema":{"type":"string","description":"Target folder path","title":"Path"},"description":"Target folder path"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbapp__Body_upload_file_api_files_upload_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__FileUploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/files/{path}":{"delete":{"tags":["cbapp","files"],"summary":"Delete file","description":"Delete a file or folder.\n\nUsers can only delete their own files in shared folders.\nAdmins can delete any file.","operationId":"delete_file_api_files__path__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string","title":"Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/goals/":{"get":{"tags":["cbapp","goals"],"summary":"List goals","description":"List goals with optional filtering.","operationId":"list_goals_api_goals__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"goal_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by goal type","title":"Goal Type"},"description":"Filter by goal type"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by assigned user","title":"User Id"},"description":"Filter by assigned user"},{"name":"skip","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__Goal"},"title":"Response List Goals Api Goals  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","goals"],"summary":"Create goal","description":"Create a new goal.","operationId":"create_goal_api_goals__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__GoalCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Goal"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/goals/stats":{"get":{"tags":["cbapp","goals"],"summary":"Get goal stats","description":"Get aggregate goal statistics.","operationId":"get_goal_stats_api_goals_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__GoalStats"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/goals/{goal_id}":{"get":{"tags":["cbapp","goals"],"summary":"Get goal","description":"Get a single goal by ID.","operationId":"get_goal_api_goals__goal_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"goal_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the goal to get"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Goal"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","goals"],"summary":"Update goal","description":"Update a goal.","operationId":"update_goal_api_goals__goal_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"goal_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the goal to update"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__GoalUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Goal"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","goals"],"summary":"Delete goal","description":"Delete a goal.","operationId":"delete_goal_api_goals__goal_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"goal_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the goal to delete"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/hello-page/config":{"get":{"tags":["cbapp","hello-page"],"summary":"Get hello page config","description":"Get the hello page configuration.\n\nRequires admin role.","operationId":"get_hello_page_config_api_hello_page_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HelloPageConfig"}}}}},"security":[{"OAuth2PasswordBearer":[]}]},"put":{"tags":["cbapp","hello-page"],"summary":"Update hello page config","description":"Update the hello page configuration.\n\nRequires admin role. Creates the config if it doesn't exist.","operationId":"update_hello_page_config_api_hello_page_config_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HelloPageConfigUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HelloPageConfig"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/hello-page/config/reset":{"post":{"tags":["cbapp","hello-page"],"summary":"Create reset hello page config","description":"Reset hello page configuration to defaults.\n\nRequires admin role.","operationId":"reset_hello_page_config_api_hello_page_config_reset_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/hello-page/templates":{"get":{"tags":["cbapp","hello-page"],"summary":"List hello page templates","description":"List available hello page templates.\n\nRequires admin role.","operationId":"list_hello_page_templates_api_hello_page_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/i360/filters":{"get":{"tags":["cbapp","i360"],"summary":"Get filter options","description":"Get available filter options for the search interface.","operationId":"get_filter_options_api_i360_filters_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__FilterOptions"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/i360/geojson":{"get":{"tags":["cbapp","i360"],"summary":"Get all voters geojson","description":"Get all i360 voters as GeoJSON for map visualization.\n\nReturns a FeatureCollection with Point features for voters with coordinates.\nResults are cached in Redis for 1 hour for performance.","operationId":"get_all_voters_geojson_api_i360_geojson_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50000,"minimum":1,"default":10000,"title":"Limit"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/i360/import":{"post":{"tags":["cbapp","i360"],"summary":"Create import voters to audience","description":"Import selected i360 voters into the campaign audience.","operationId":"import_voters_to_audience_api_i360_import_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__I360ImportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__I360ImportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/i360/save-as-segment":{"post":{"tags":["cbapp","i360"],"summary":"Create save search as segment","description":"Save i360 search criteria as a reusable segment.","operationId":"save_search_as_segment_api_i360_save_as_segment_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SaveAsSegmentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SaveAsSegmentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/i360/search":{"get":{"tags":["cbapp","i360"],"summary":"Get search voters","description":"Search i360 voters with advanced filtering.","operationId":"search_voters_api_i360_search_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name, city, or phone","title":"Q"},"description":"Search by name, city, or phone"},{"name":"gender","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"}},{"name":"ethnicity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ethnicity"}},{"name":"religion","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Religion"}},{"name":"race","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race"}},{"name":"birth_year_min","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Birth Year Min"}},{"name":"birth_year_max","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Birth Year Max"}},{"name":"turnout_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100,"minimum":0},{"type":"null"}],"title":"Turnout Min"}},{"name":"turnout_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100,"minimum":0},{"type":"null"}],"title":"Turnout Max"}},{"name":"trump_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100,"minimum":0},{"type":"null"}],"title":"Trump Min"}},{"name":"trump_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100,"minimum":0},{"type":"null"}],"title":"Trump Max"}},{"name":"county","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"}},{"name":"city","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"}},{"name":"congressional_district","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Congressional District"}},{"name":"lat","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"}},{"name":"lng","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lng"}},{"name":"radius","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Radius in miles","title":"Radius"},"description":"Radius in miles"},{"name":"voted_2020","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Voted 2020"}},{"name":"voted_2022","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Voted 2022"}},{"name":"has_cell","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Cell"}},{"name":"has_email","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Email"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":25,"title":"Per Page"}},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","default":"last_name","title":"Sort By"}},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","default":"asc","title":"Sort Order"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/i360/stats":{"get":{"tags":["cbapp","i360"],"summary":"Get search stats","description":"Get i360 database statistics.","operationId":"get_search_stats_api_i360_stats_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/i360/voter/{svid}":{"get":{"tags":["cbapp","i360"],"summary":"Get voter detail","description":"Get detailed information for a specific voter.","operationId":"get_voter_detail_api_i360_voter__svid__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"svid","in":"path","required":true,"schema":{"type":"integer","title":"Svid"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/integrations/":{"get":{"tags":["cbapp","integrations"],"summary":"List integrations","description":"List all integrations with their current settings and status.","operationId":"list_integrations_api_integrations__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__IntegrationList"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/integrations/typeform/seed-tags":{"post":{"tags":["cbapp","integrations","integrations"],"summary":"Create seed typeform tags","description":"Pre-create all Typeform-related tags with proper categories.\n\nSafe to call multiple times — skips tags that already exist.","operationId":"seed_typeform_tags_api_integrations_typeform_seed_tags_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/integrations/typeform/status":{"get":{"tags":["cbapp","integrations","integrations"],"summary":"Get typeform status","description":"Get Typeform sync status: last sync time, form configs, cbwebhook health.","operationId":"typeform_status_api_integrations_typeform_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/integrations/typeform/sync":{"post":{"tags":["cbapp","integrations","integrations"],"summary":"Create sync typeform","description":"Trigger manual sync of Typeform responses from cbwebhook.\n\nFetches new responses since last sync and creates/updates person records\nwith appropriate tags based on form answers.","operationId":"sync_typeform_api_integrations_typeform_sync_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"form","in":"query","required":false,"schema":{"type":"string","description":"Form to sync: 'all', 'volunteers', or 'pledges'","default":"all","title":"Form"},"description":"Form to sync: 'all', 'volunteers', or 'pledges'"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/integrations/{integration}":{"get":{"tags":["cbapp","integrations"],"summary":"Get integration","description":"Get details for a specific integration.","operationId":"get_integration_api_integrations__integration__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"integration","in":"path","required":true,"schema":{"type":"string","title":"Integration"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__IntegrationStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/integrations/{integration}/test":{"post":{"tags":["cbapp","integrations"],"summary":"Create test integration","description":"Test connectivity for an integration.","operationId":"test_integration_api_integrations__integration__test_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"integration","in":"path","required":true,"schema":{"type":"string","title":"Integration"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__TestResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/integrations/{integration}/{key}":{"put":{"tags":["cbapp","integrations"],"summary":"Update setting","description":"Update a setting value.","operationId":"update_setting_api_integrations__integration___key__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"integration","in":"path","required":true,"schema":{"type":"string","title":"Integration"}},{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SettingUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SettingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","integrations"],"summary":"Delete reset setting","description":"Reset a setting to its .env/default value.","operationId":"reset_setting_api_integrations__integration___key__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"integration","in":"path","required":true,"schema":{"type":"string","title":"Integration"}},{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SettingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/intel/briefing":{"get":{"tags":["cbapp","intel"],"summary":"Get briefing","description":"Get daily briefing data for the Intel dashboard.\n\nReturns today's stats, priority items, and recent activity.","operationId":"get_briefing_api_intel_briefing_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/intel/crawl":{"post":{"tags":["cbapp","intel"],"summary":"Create start crawl","description":"Start an async crawl job.\n\nReturns immediately with a job_id. The job will be polled automatically\nin the background. Use GET /intel/jobs/{job_id} to check status.\n\nResults are stored in CBFiles upon completion.","operationId":"start_crawl_api_intel_crawl_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__CrawlRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/intel/fetch":{"post":{"tags":["cbapp","intel"],"summary":"Create fetch url","description":"Fetch a single URL synchronously.\n\nThis is a blocking call that returns the page content directly.\nSuitable for quick lookups (5-30 second response time).\n\nResults are stored in CBFiles for future reference.","operationId":"fetch_url_api_intel_fetch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__FetchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/intel/health":{"get":{"tags":["cbapp","intel"],"summary":"Get health check","description":"Check Intel service health.","operationId":"health_check_api_intel_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbapp/api/intel/jobs":{"get":{"tags":["cbapp","intel"],"summary":"List jobs","description":"List intel jobs for the current user.\n\nSupports filtering by status and job type.","operationId":"list_jobs_api_intel_jobs_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"job_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by job type (fetch/crawl)","title":"Job Type"},"description":"Filter by job type (fetch/crawl)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/intel/jobs/{job_id}":{"get":{"tags":["cbapp","intel"],"summary":"Get job","description":"Get details of an intel job.","operationId":"get_job_api_intel_jobs__job_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/intel/jobs/{job_id}/cancel":{"post":{"tags":["cbapp","intel"],"summary":"Create cancel job","description":"Cancel a running job.","operationId":"cancel_job_api_intel_jobs__job_id__cancel_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/intel/jobs/{job_id}/poll":{"post":{"tags":["cbapp","intel"],"summary":"Create poll job","description":"Manually poll a job for status updates.\n\nNormally jobs are polled automatically, but this allows\nimmediate status refresh.","operationId":"poll_job_api_intel_jobs__job_id__poll_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/intel/search":{"post":{"tags":["cbapp","intel"],"summary":"Create start search","description":"Start an async keyword search job.\n\nUnlike /crawl which requires a URL, this accepts a search query\n(keywords) that the Intel service will use to find and analyze\nrelevant content.\n\nReturns immediately with a job_id. The job will be polled automatically\nin the background. Use GET /intel/jobs/{job_id} to check status.\n\nResults are stored in CBFiles upon completion.","operationId":"start_search_api_intel_search_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SearchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/intel/transcript":{"post":{"tags":["cbapp","intel"],"summary":"Create fetch transcript","description":"Fetch a YouTube video transcript.\n\nAccepts a YouTube URL or video ID. Submits job to CBIntel\nand returns immediately. Poll for status using /jobs/{job_id}.\n\nSupported URL formats:\n- youtube.com/watch?v=ID\n- youtu.be/ID\n- youtube.com/shorts/ID\n- Pages with embedded YouTube videos","operationId":"fetch_transcript_api_intel_transcript_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__TranscriptRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/interactions/persons/{person_id}":{"get":{"tags":["cbapp","interactions"],"summary":"Get person interactions","description":"Get all interactions for a person (the ledger view).","operationId":"get_person_interactions_api_interactions_persons__person_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","description":"Person ID","title":"Person Id"},"description":"Person ID"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","interactions"],"summary":"Create interaction","description":"Log a standalone interaction for a person.","operationId":"create_interaction_api_interactions_persons__person_id__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","description":"Person ID","title":"Person Id"},"description":"Person ID"},{"name":"interaction_type","in":"query","required":true,"schema":{"type":"string","description":"Type of interaction","title":"Interaction Type"},"description":"Type of interaction"},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Notes","title":"Notes"},"description":"Notes"},{"name":"metadata","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON metadata","title":"Metadata"},"description":"JSON metadata"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/interactions/persons/{person_id}/summary":{"get":{"tags":["cbapp","interactions"],"summary":"Get person interaction summary","description":"Get a summary of interactions for a person.","operationId":"get_person_interaction_summary_api_interactions_persons__person_id__summary_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","description":"Person ID","title":"Person Id"},"description":"Person ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/list-loader/history":{"get":{"tags":["cbapp","list-loader"],"summary":"Get history","description":"Get import history.","operationId":"get_history_api_list_loader_history_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/list-loader/upload":{"post":{"tags":["cbapp","list-loader"],"summary":"Create upload file","description":"Upload a file for import.\n\nSupports CSV, TSV, and Excel files.\nReturns a job_id to track the import process.","operationId":"upload_file_api_list_loader_upload_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbapp__Body_upload_file_api_list_loader_upload_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/list-loader/{job_id}/analyze":{"post":{"tags":["cbapp","list-loader"],"summary":"Create analyze mappings","description":"Trigger AI analysis to suggest field mappings.\n\nThis runs asynchronously. Poll the mappings endpoint for results.","operationId":"analyze_mappings_api_list_loader__job_id__analyze_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/list-loader/{job_id}/execute":{"post":{"tags":["cbapp","list-loader"],"summary":"Create execute import","description":"Execute the import to the local database.\n\nCreates Person records from the mapped data.","operationId":"execute_import_api_list_loader__job_id__execute_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ImportOptions","default":{"skip_duplicates":true,"tags":[]}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/list-loader/{job_id}/files":{"get":{"tags":["cbapp","list-loader"],"summary":"List job files","description":"List all files associated with an import job.","operationId":"list_job_files_api_list_loader__job_id__files_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/list-loader/{job_id}/mappings":{"get":{"tags":["cbapp","list-loader"],"summary":"Get mappings","description":"Get suggested field mappings.","operationId":"get_mappings_api_list_loader__job_id__mappings_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","list-loader"],"summary":"Update mappings","description":"Update field mappings manually.","operationId":"update_mappings_api_list_loader__job_id__mappings_put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__MappingUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/list-loader/{job_id}/preview":{"get":{"tags":["cbapp","list-loader"],"summary":"Get preview","description":"Get preview of uploaded file with headers and sample rows.","operationId":"get_preview_api_list_loader__job_id__preview_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/list-loader/{job_id}/select-worksheet":{"post":{"tags":["cbapp","list-loader"],"summary":"Create select worksheet","description":"Select a worksheet for an Excel file and parse it.","operationId":"select_worksheet_api_list_loader__job_id__select_worksheet_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WorksheetSelect"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/list-loader/{job_id}/status":{"get":{"tags":["cbapp","list-loader"],"summary":"Get status","description":"Get current status of an import job.","operationId":"get_status_api_list_loader__job_id__status_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/list-loader/{job_id}/validate":{"post":{"tags":["cbapp","list-loader"],"summary":"Create validate data","description":"Validate data against schema before import.","operationId":"validate_data_api_list_loader__job_id__validate_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/list-loader/{job_id}/worksheets":{"get":{"tags":["cbapp","list-loader"],"summary":"Get worksheets","description":"Get worksheet list for an Excel file.","operationId":"get_worksheets_api_list_loader__job_id__worksheets_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/patches/":{"get":{"tags":["cbapp","patches"],"summary":"List patches","description":"List patches with optional filtering. Admin/director only for all patches.","operationId":"list_patches_api_patches__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"person_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by person","title":"Person Id"},"description":"Filter by person"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__Patch"},"title":"Response List Patches Api Patches  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/patches/persons/{person_id}":{"post":{"tags":["cbapp","patches"],"summary":"Create patches","description":"Submit one or more field patches for a person.","operationId":"create_patches_api_patches_persons__person_id__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","description":"Person ID","title":"Person Id"},"description":"Person ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__PatchBulkCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__Patch"},"title":"Response Create Patches Api Patches Persons  Person Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"get":{"tags":["cbapp","patches"],"summary":"Get person patches","description":"Get all patches for a specific person.","operationId":"get_person_patches_api_patches_persons__person_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","description":"Person ID","title":"Person Id"},"description":"Person ID"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__Patch"},"title":"Response Get Person Patches Api Patches Persons  Person Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/patches/stats":{"get":{"tags":["cbapp","patches"],"summary":"Get patch stats","description":"Get patch statistics. Admin/director only.","operationId":"get_patch_stats_api_patches_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__PatchStats"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/patches/{patch_id}":{"patch":{"tags":["cbapp","patches"],"summary":"Update review patch","description":"Approve or reject a patch. Admin/director only.","operationId":"review_patch_api_patches__patch_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"patch_id","in":"path","required":true,"schema":{"type":"string","description":"Patch ID","title":"Patch Id"},"description":"Patch ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__PatchReview"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/patches/{patch_id}/apply":{"post":{"tags":["cbapp","patches"],"summary":"Create apply patch","description":"Apply an approved patch. Admin/director only.","operationId":"apply_patch_api_patches__patch_id__apply_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"patch_id","in":"path","required":true,"schema":{"type":"string","description":"Patch ID","title":"Patch Id"},"description":"Patch ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/persons/":{"get":{"tags":["cbapp","persons"],"summary":"List persons","description":"List persons with optional filtering.","operationId":"list_persons_api_persons__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"skip","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag"}},{"name":"whip_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Whip Status"}},{"name":"precinct_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct Status"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__PersonsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","persons"],"summary":"Create person","description":"Create a new person record.","operationId":"create_person_api_persons__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__PersonCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Person"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/persons/batch":{"post":{"tags":["cbapp","persons"],"summary":"Create batch import persons","description":"Bulk import persons with duplicate detection.\n\nDuplicates are detected by email OR phone match.\nEach imported record is tagged with batch_id, loaded_ts, and list_name.","operationId":"batch_import_persons_api_persons_batch_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BatchImportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BatchImportResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/persons/bulk-import":{"post":{"tags":["cbapp","persons"],"summary":"Create bulk import csv","description":"Direct DuckDB bulk import from CSV file.\n\nThis is much faster than the /batch endpoint for large imports:\n- 1,000 records: <1s (vs 30s via /batch)\n- 83,000 records: ~2s (vs 40min via /batch)\n\nThe CSV file must already exist on the server in /tmp/imports/.\nColumn mappings translate CSV column names to database columns.","operationId":"bulk_import_csv_api_persons_bulk_import_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BulkImportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BulkImportResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/persons/search/semantic":{"get":{"tags":["cbapp","persons"],"summary":"Get semantic search persons","description":"Search persons using semantic similarity.","operationId":"semantic_search_persons_api_persons_search_semantic_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string","description":"Search query for semantic matching","title":"Query"},"description":"Search query for semantic matching"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Maximum number of results","default":10,"title":"Limit"},"description":"Maximum number of results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__Person"},"title":"Response Semantic Search Persons Api Persons Search Semantic Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/persons/{person_id}":{"get":{"tags":["cbapp","persons"],"summary":"Get person","description":"Get a single person by ID.","operationId":"get_person_api_persons__person_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the person to get"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Person"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","persons"],"summary":"Update person","description":"Update a person record.","operationId":"update_person_api_persons__person_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the person to update"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__PersonUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Person"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","persons"],"summary":"Delete person","description":"Delete a person record.","operationId":"delete_person_api_persons__person_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the person to delete"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/protected":{"get":{"tags":["cbapp","auth"],"summary":"Protected Route","description":"Test authentication.\n\nReturns the current user's information to verify JWT authentication is working.\nUseful for testing token validity.","operationId":"protected_route_api_protected_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/public/events/{slug}":{"get":{"tags":["cbapp","public-events"],"summary":"Get public event page","description":"Get a public event page by slug.\n\nThis is a public endpoint - no authentication required.\nOnly returns published pages unless a valid preview token is provided.","operationId":"get_public_event_page_api_public_events__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"preview","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Preview token for unpublished pages","title":"Preview"},"description":"Preview token for unpublished pages"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__EventPagePublicView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/public/events/{slug}/register":{"post":{"tags":["cbapp","public-events"],"summary":"Create register for event","description":"Submit a public event registration.\n\nThis is a public endpoint - no authentication required.\nCreates or matches a person record and registers them for the event.","operationId":"register_for_event_api_public_events__slug__register_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__PublicRegistrationCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__RegistrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/bridge/broadcast":{"post":{"tags":["cbapp","scout"],"summary":"Create bridge broadcast","description":"Broadcast a message to multiple users.\n\nSupports broadcast addressing:\n- broadcast:admins - All admin users\n- broadcast:directors - All director users\n- broadcast:room:<uuid> - All members of a room\n- broadcast:online - All currently online users\n- broadcast:all - All active users\n\nCreates DMs to each recipient with the broadcast content.\n\nExample:\n    POST /api/scout/bridge/broadcast\n    {\n        \"address\": \"broadcast:admins\",\n        \"content\": \"System maintenance scheduled for midnight\",\n        \"notification_type\": \"warning\",\n        \"sender_service\": \"maintenance-scheduler\"\n    }","operationId":"bridge_broadcast_api_scout_bridge_broadcast_post","parameters":[{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BridgeBroadcastRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BridgeBroadcastResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/bridge/connection":{"get":{"tags":["cbapp","scout"],"summary":"Get bridge connection status","description":"Get detailed bridge connection status.\n\nReturns information about the mesh WebSocket connection.","operationId":"bridge_connection_status_api_scout_bridge_connection_get","parameters":[{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/bridge/publish":{"post":{"tags":["cbapp","scout"],"summary":"Create publish system message","description":"Publish a system message to a system room.\n\nUsed by mesh services to post logs, alerts, job progress, and events.\nRequires X-Service-Key header (if SCOUT_BRIDGE_KEY is configured).\n\nSystem types:\n- logs: Application logs (posts to #system-logs)\n- alerts: Critical alerts (posts to #system-alerts)\n- jobs: Background job progress (posts to #system-jobs)\n- announcements: System-wide notices (posts to #announcements)\n\nObject types determine the data schema:\n- log: LogEntryData (level, service, message, details)\n- alert: AlertData (severity, service, message, action_required)\n- job: JobProgressData (status, progress, current_step, can_cancel)\n- system_event: SystemEventData (event_type, service, summary)","operationId":"publish_system_message_api_scout_bridge_publish_post","parameters":[{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SystemMessageCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SystemMessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/bridge/resolve":{"post":{"tags":["cbapp","scout"],"summary":"Create resolve address","description":"Resolve a semantic address to concrete IDs.\n\nUseful for debugging and validation.\n\nExample:\n    GET /api/scout/bridge/resolve?address=room:system:logs","operationId":"resolve_address_api_scout_bridge_resolve_post","parameters":[{"name":"address","in":"query","required":true,"schema":{"type":"string","description":"Address to resolve","title":"Address"},"description":"Address to resolve"},{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/bridge/send":{"post":{"tags":["cbapp","scout"],"summary":"Create bridge send message","description":"Send a message to a room using semantic addressing.\n\nSupports flexible room addressing:\n- room:<uuid> - Direct room ID\n- room:system:<type> - System room (logs, alerts, jobs, announcements)\n- room:dm:<user1>:<user2> - DM between two users\n\nExample:\n    POST /api/scout/bridge/send\n    {\n        \"room\": \"room:system:jobs\",\n        \"content\": \"Import completed successfully\",\n        \"sender_service\": \"list-loader\"\n    }","operationId":"bridge_send_message_api_scout_bridge_send_post","parameters":[{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BridgePublishRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BridgePublishResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/bridge/start":{"post":{"tags":["cbapp","scout"],"summary":"Create start bridge service","description":"Start the mesh bridge as a background task.\n\nConnects to the configured cbmesh gateway and begins routing messages.\nRequires CBMESH_GATEWAY_URL to be configured (or pass gateway_url param).","operationId":"start_bridge_service_api_scout_bridge_start_post","parameters":[{"name":"gateway_url","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional gateway URL override","title":"Gateway Url"},"description":"Optional gateway URL override"},{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/bridge/status":{"get":{"tags":["cbapp","scout"],"summary":"Get bridge status","description":"Get bridge connection status.\n\nReturns status information for mesh service diagnostics.","operationId":"get_bridge_status_api_scout_bridge_status_get","parameters":[{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/bridge/stop":{"post":{"tags":["cbapp","scout"],"summary":"Create stop bridge service","description":"Stop the mesh bridge.","operationId":"stop_bridge_service_api_scout_bridge_stop_post","parameters":[{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/dm/{user_id}":{"post":{"tags":["cbapp","scout"],"summary":"Get or create dm","description":"Get or create a DM with another user.\n\nIdempotent - returns existing DM if one exists.","operationId":"get_or_create_dm_api_scout_dm__user_id__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoom"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/events/enable-mesh":{"post":{"tags":["cbapp","scout"],"summary":"Create enable mesh events","description":"Enable or disable event publishing to mesh.\n\nWhen enabled, Scout events (messages, rooms, presence) are\npublished to the mesh gateway for other services to consume.","operationId":"enable_mesh_events_api_scout_events_enable_mesh_post","parameters":[{"name":"enabled","in":"query","required":false,"schema":{"type":"boolean","description":"Enable mesh publishing","default":true,"title":"Enabled"},"description":"Enable mesh publishing"},{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/events/status":{"get":{"tags":["cbapp","scout"],"summary":"Get events status","description":"Get event publishing status.\n\nReturns information about the event publisher configuration.","operationId":"get_events_status_api_scout_events_status_get","parameters":[{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/mesh/query":{"post":{"tags":["cbapp","scout"],"summary":"Create mesh query","description":"Query a mesh service.\n\nSends a query to a target service through the mesh gateway.\nRequires bridge to be connected.\n\nExample:\n    POST /api/scout/mesh/query?target=query-engine&query=voters%20where%20score%20%3E%2080","operationId":"mesh_query_api_scout_mesh_query_post","parameters":[{"name":"target","in":"query","required":true,"schema":{"type":"string","description":"Target service name","title":"Target"},"description":"Target service name"},{"name":"query","in":"query","required":true,"schema":{"type":"string","description":"Query string","title":"Query"},"description":"Query string"},{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/mesh/request":{"post":{"tags":["cbapp","scout"],"summary":"Create mesh request","description":"Make a raw request to a mesh service.\n\nLower-level API for service-to-service communication.\n\nExample:\n    POST /api/scout/mesh/request?target=cbfiles&method=GET&path=/files","operationId":"mesh_request_api_scout_mesh_request_post","parameters":[{"name":"target","in":"query","required":true,"schema":{"type":"string","description":"Target service name","title":"Target"},"description":"Target service name"},{"name":"method","in":"query","required":false,"schema":{"type":"string","description":"HTTP method","default":"POST","title":"Method"},"description":"HTTP method"},{"name":"path","in":"query","required":true,"schema":{"type":"string","description":"API path on target","title":"Path"},"description":"API path on target"},{"name":"x-service-key","in":"header","required":false,"schema":{"type":"string","title":"X-Service-Key"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"type":"object","additionalProperties":true},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/messages/{message_id}":{"patch":{"tags":["cbapp","scout"],"summary":"Update edit message","description":"Edit a message.\n\nOnly the sender can edit their messages.","operationId":"edit_message_api_scout_messages__message_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","title":"Message Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutMessageUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","scout"],"summary":"Delete message","description":"Delete a message (soft delete).\n\nSender or room admin can delete messages.","operationId":"delete_message_api_scout_messages__message_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","title":"Message Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/rooms":{"get":{"tags":["cbapp","scout"],"summary":"List rooms","description":"List all rooms the current user is a member of.\n\nReturns rooms ordered by last message time, with unread counts.\nDMs include the other user's information.","operationId":"list_rooms_api_scout_rooms_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","description":"Include archived rooms","default":false,"title":"Include Archived"},"description":"Include archived rooms"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoomListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","scout"],"summary":"Create room","description":"Create a new room (group or DM).\n\nFor DMs, use POST /scout/dm/{user_id} instead for idempotent behavior.\nThe creator is automatically added as owner.","operationId":"create_room_api_scout_rooms_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoomCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoom"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/rooms/{room_id}":{"get":{"tags":["cbapp","scout"],"summary":"Get room","description":"Get room details including member list.\n\nOnly accessible by room members.","operationId":"get_room_api_scout_rooms__room_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoomWithMembers"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"patch":{"tags":["cbapp","scout"],"summary":"Update room","description":"Update room details (name, description, archive status).\n\nOnly room owner or admin can update.","operationId":"update_room_api_scout_rooms__room_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoomUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoom"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","scout"],"summary":"Delete archive room","description":"Archive a room (soft delete).\n\nOnly room owner or admin can archive.","operationId":"archive_room_api_scout_rooms__room_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/rooms/{room_id}/members":{"get":{"tags":["cbapp","scout"],"summary":"List room members","description":"List all members of a room.\n\nOnly accessible by room members.","operationId":"list_room_members_api_scout_rooms__room_id__members_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__ScoutRoomMember"},"title":"Response List Room Members Api Scout Rooms  Room Id  Members Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","scout"],"summary":"Add Room Member","description":"Add a member to a room.\n\nOnly room owner or admin can add members.","operationId":"add_room_member_api_scout_rooms__room_id__members_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoomMemberCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoomMember"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/rooms/{room_id}/members/{user_id}":{"delete":{"tags":["cbapp","scout"],"summary":"Delete remove room member","description":"Remove a member from a room.\n\nUsers can remove themselves. Owners/admins can remove others.\nCannot remove the room owner.","operationId":"remove_room_member_api_scout_rooms__room_id__members__user_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}},{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/rooms/{room_id}/messages":{"get":{"tags":["cbapp","scout"],"summary":"List messages","description":"Get messages in a room with cursor-based pagination.\n\nMessages are returned in chronological order.\nUse 'before' cursor to load older messages, 'after' for newer.","operationId":"list_messages_api_scout_rooms__room_id__messages_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Number of messages to return","default":50,"title":"Limit"},"description":"Number of messages to return"},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Get messages before this message ID","title":"Before"},"description":"Get messages before this message ID"},{"name":"after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Get messages after this message ID","title":"After"},"description":"Get messages after this message ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutMessageListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","scout"],"summary":"Create send message","description":"Send a message to a room (REST fallback).\n\nPrefer WebSocket for real-time messaging.\nThis endpoint is useful for programmatic access.","operationId":"send_message_api_scout_rooms__room_id__messages_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutMessageCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/rooms/{room_id}/objects":{"get":{"tags":["cbapp","scout"],"summary":"List room objects","description":"List shared objects in a room.\n\nReturns files, maps, queries, etc. shared in the room.\nUseful for building an \"attachments\" view.","operationId":"list_room_objects_api_scout_rooms__room_id__objects_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}},{"name":"object_type","in":"query","required":false,"schema":{"anyOf":[{"enum":["file","map","query","segment","person","event"],"type":"string"},{"type":"null"}],"description":"Filter by object type","title":"Object Type"},"description":"Filter by object type"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Number of objects to return","default":50,"title":"Limit"},"description":"Number of objects to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutObjectListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/rooms/{room_id}/read":{"post":{"tags":["cbapp","scout"],"summary":"Create mark room read","description":"Mark messages in a room as read up to a specific message.\n\nUpdates the user's last_read_at and last_read_message_id.","operationId":"mark_room_read_api_scout_rooms__room_id__read_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}},{"name":"message_id","in":"query","required":true,"schema":{"type":"string","description":"Last read message ID","title":"Message Id"},"description":"Last read message ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/scout/system-rooms":{"get":{"tags":["cbapp","scout"],"summary":"List system rooms","description":"List all system rooms the user has access to.\n\nSystem rooms are pre-defined rooms for mesh service communication:\n- #system-logs: Application logs (admin only)\n- #system-alerts: Critical alerts (admin only)\n- #system-jobs: Background job progress (all users)\n- #announcements: System-wide notices (all users)","operationId":"list_system_rooms_api_scout_system_rooms_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoomListResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/scout/system-rooms/{system_type}":{"post":{"tags":["cbapp","scout"],"summary":"Get or create system room","description":"Get or create a system room and subscribe the user.\n\nSystem types:\n- logs: Application logs, debug info (admin/director only)\n- alerts: Critical alerts, errors (admin/director only)\n- jobs: Background job progress (all users)\n- announcements: System-wide notices (all users)\n\nReturns 403 if user lacks access to admin-only rooms.","operationId":"get_or_create_system_room_api_scout_system_rooms__system_type__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"system_type","in":"path","required":true,"schema":{"enum":["logs","alerts","jobs","announcements"],"type":"string","title":"System Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ScoutRoom"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/segments/":{"get":{"tags":["cbapp","segments"],"summary":"List segments","description":"List all segments with optional filtering, sorting, and pagination.","operationId":"list_segments_api_segments__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"skip","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort by: name, created_at, updated_at, person_count","default":"created_at","title":"Sort By"},"description":"Sort by: name, created_at, updated_at, person_count"},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","description":"Sort order: asc or desc","default":"desc","title":"Sort Order"},"description":"Sort order: asc or desc"},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SegmentsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","segments"],"summary":"Create segment","description":"Create a new segment","operationId":"create_segment_api_segments__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SegmentCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Segment"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/segments/{segment_id}":{"get":{"tags":["cbapp","segments"],"summary":"Get segment","description":"Get a specific segment by ID","operationId":"get_segment_api_segments__segment_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SegmentWithStats"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","segments"],"summary":"Update segment","description":"Update an existing segment","operationId":"update_segment_api_segments__segment_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SegmentUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Segment"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","segments"],"summary":"Delete segment","description":"Delete a segment","operationId":"delete_segment_api_segments__segment_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/segments/{segment_id}/execute":{"post":{"tags":["cbapp","segments"],"summary":"Create execute segment","description":"Execute a segment query and update the person count","operationId":"execute_segment_api_segments__segment_id__execute_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/segments/{segment_id}/geojson":{"get":{"tags":["cbapp","segments"],"summary":"Get segment geojson","description":"Get segment voters as GeoJSON for map visualization.\n\nReturns a FeatureCollection with Point features for each voter that has\nvalid latitude/longitude coordinates. Limited to 5000 points by default.","operationId":"get_segment_geojson_api_segments__segment_id__geojson_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":1,"default":5000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/segments/{segment_id}/persons":{"get":{"tags":["cbapp","segments"],"summary":"Get segment persons","description":"Get persons in a segment","operationId":"get_segment_persons_api_segments__segment_id__persons_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"skip","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/segments/{segment_id}/persons/bulk":{"post":{"tags":["cbapp","segments"],"summary":"Create bulk add persons to segment","description":"Add multiple persons to a static segment.\n\nReturns summary of added/skipped persons.","operationId":"bulk_add_persons_to_segment_api_segments__segment_id__persons_bulk_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__BulkAddPersonsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/segments/{segment_id}/persons/{person_id}":{"post":{"tags":["cbapp","segments"],"summary":"Create add person to segment","description":"Add a person to a static segment.\n\nReturns 409 if already a member.\nReturns 400 if segment is dynamic.","operationId":"add_person_to_segment_api_segments__segment_id__persons__person_id__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"Person Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","segments"],"summary":"Delete remove person from segment","description":"Remove a person from a static segment.\n\nReturns 404 if not a member.\nReturns 400 if segment is dynamic.","operationId":"remove_person_from_segment_api_segments__segment_id__persons__person_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"Person Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/surveys/":{"get":{"tags":["cbapp","surveys"],"summary":"List surveys","description":"List all surveys from YASP.","operationId":"list_surveys_api_surveys__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state (DRAFT, ACTIVE, INACTIVE, ARCHIVED)","title":"State"},"description":"Filter by state (DRAFT, ACTIVE, INACTIVE, ARCHIVED)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__SurveySummary"},"title":"Response List Surveys Api Surveys  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","surveys"],"summary":"Create survey","description":"Create a new survey in YASP.","operationId":"create_survey_api_surveys__post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SurveyCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Survey"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/surveys/health":{"get":{"tags":["cbapp","surveys"],"summary":"Get check yasp health","description":"Check YASP service health.","operationId":"check_yasp_health_api_surveys_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/surveys/responses":{"post":{"tags":["cbapp","surveys"],"summary":"Create submit response","description":"Submit a survey response.","operationId":"submit_response_api_surveys_responses_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ResponseSubmit"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/surveys/segment/{segment_id}":{"get":{"tags":["cbapp","surveys"],"summary":"Get segment surveys","description":"Get all surveys linked to a segment.","operationId":"get_segment_surveys_api_surveys_segment__segment_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","description":"Segment ID","title":"Segment Id"},"description":"Segment ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","surveys"],"summary":"Create link survey to segment","description":"Link a survey to a segment.","operationId":"link_survey_to_segment_api_surveys_segment__segment_id__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","description":"Segment ID","title":"Segment Id"},"description":"Segment ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SegmentSurveyCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/surveys/segment/{segment_id}/{survey_id}":{"delete":{"tags":["cbapp","surveys"],"summary":"Delete unlink survey from segment","description":"Unlink a survey from a segment.","operationId":"unlink_survey_from_segment_api_surveys_segment__segment_id___survey_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","description":"Segment ID","title":"Segment Id"},"description":"Segment ID"},{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","surveys"],"summary":"Update segment survey link","description":"Update a segment-survey link (display order, required status).","operationId":"update_segment_survey_link_api_surveys_segment__segment_id___survey_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","description":"Segment ID","title":"Segment Id"},"description":"Segment ID"},{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"},{"name":"display_order","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Display Order"}},{"name":"is_required","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Required"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/surveys/{survey_id}":{"get":{"tags":["cbapp","surveys"],"summary":"Get survey","description":"Get a survey with its questions from YASP.","operationId":"get_survey_api_surveys__survey_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Survey"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","surveys"],"summary":"Update survey","description":"Update a survey's name or description.","operationId":"update_survey_api_surveys__survey_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SurveyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Survey"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/surveys/{survey_id}/archive":{"post":{"tags":["cbapp","surveys"],"summary":"Create archive survey","description":"Archive a survey (terminal state).","operationId":"archive_survey_api_surveys__survey_id__archive_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Survey"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/surveys/{survey_id}/pause":{"post":{"tags":["cbapp","surveys"],"summary":"Create pause survey","description":"Pause a survey (ACTIVE -> INACTIVE).","operationId":"pause_survey_api_surveys__survey_id__pause_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Survey"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/surveys/{survey_id}/publish":{"post":{"tags":["cbapp","surveys"],"summary":"Create publish survey","description":"Publish a survey (DRAFT -> ACTIVE).","operationId":"publish_survey_api_surveys__survey_id__publish_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Survey"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/surveys/{survey_id}/questions":{"post":{"tags":["cbapp","surveys"],"summary":"Create add question","description":"Add a question to a survey.","operationId":"add_question_api_surveys__survey_id__questions_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__QuestionCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/surveys/{survey_id}/questions/{question_id}":{"put":{"tags":["cbapp","surveys"],"summary":"Update question","description":"Update a question.","operationId":"update_question_api_surveys__survey_id__questions__question_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"},{"name":"question_id","in":"path","required":true,"schema":{"type":"string","description":"Question ID","title":"Question Id"},"description":"Question ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__QuestionUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","surveys"],"summary":"Delete question","description":"Delete a question from a survey.","operationId":"delete_question_api_surveys__survey_id__questions__question_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"},{"name":"question_id","in":"path","required":true,"schema":{"type":"string","description":"Question ID","title":"Question Id"},"description":"Question ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/surveys/{survey_id}/resume":{"post":{"tags":["cbapp","surveys"],"summary":"Create resume survey","description":"Resume a survey (INACTIVE -> ACTIVE).","operationId":"resume_survey_api_surveys__survey_id__resume_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","description":"Survey ID","title":"Survey Id"},"description":"Survey ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Survey"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/tags/":{"get":{"tags":["cbapp","tags"],"summary":"List tags","description":"List tags with optional filtering by parent or category.","operationId":"list_tags_api_tags__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by parent tag ID","title":"Parent Id"},"description":"Filter by parent tag ID"},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by category","title":"Category"},"description":"Filter by category"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__Tag"},"title":"Response List Tags Api Tags  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","tags"],"summary":"Create tag","description":"Create a new tag.","operationId":"create_tag_api_tags__post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__TagCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Tag"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/tags/for/{record_type}/{record_id}":{"get":{"tags":["cbapp","tags"],"summary":"Get tags for record","description":"Get all tags associated with a specific record.","operationId":"get_tags_for_record_api_tags_for__record_type___record_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"record_type","in":"path","required":true,"schema":{"type":"string","description":"Type of record (person, event, etc.)","title":"Record Type"},"description":"Type of record (person, event, etc.)"},{"name":"record_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the record","title":"Record Id"},"description":"ID of the record"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","tags"],"summary":"Create add tags to record","description":"Add one or more tags to a record. Idempotent - existing associations are ignored.","operationId":"add_tags_to_record_api_tags_for__record_type___record_id__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"record_type","in":"path","required":true,"schema":{"type":"string","description":"Type of record (person, event, etc.)","title":"Record Type"},"description":"Type of record (person, event, etc.)"},{"name":"record_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the record","title":"Record Id"},"description":"ID of the record"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"title":"Tag Ids"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/tags/for/{record_type}/{record_id}/{tag_id}":{"delete":{"tags":["cbapp","tags"],"summary":"Delete remove tag from record","description":"Remove a tag from a record.","operationId":"remove_tag_from_record_api_tags_for__record_type___record_id___tag_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"record_type","in":"path","required":true,"schema":{"type":"string","description":"Type of record","title":"Record Type"},"description":"Type of record"},{"name":"record_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the record","title":"Record Id"},"description":"ID of the record"},{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the tag to remove","title":"Tag Id"},"description":"ID of the tag to remove"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/tags/hierarchy":{"get":{"tags":["cbapp","tags"],"summary":"Get tag hierarchy","description":"Get the complete tag hierarchy with parent-child relationships.","operationId":"get_tag_hierarchy_api_tags_hierarchy_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbapp__Tag"},"type":"array","title":"Response Get Tag Hierarchy Api Tags Hierarchy Get"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/tags/search":{"get":{"tags":["cbapp","tags"],"summary":"Get search tags","description":"Search tags by name for autocomplete.","operationId":"search_tags_api_tags_search_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Search query","title":"Q"},"description":"Search query"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/tags/stats/usage":{"get":{"tags":["cbapp","tags"],"summary":"Get tag usage stats","description":"Get tag usage statistics across all record types.","operationId":"get_tag_usage_stats_api_tags_stats_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/tags/{tag_id}":{"get":{"tags":["cbapp","tags"],"summary":"Get tag","description":"Get a single tag by ID.","operationId":"get_tag_api_tags__tag_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the tag to get"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Tag"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","tags"],"summary":"Update tag","description":"Update a tag.","operationId":"update_tag_api_tags__tag_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the tag to update"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__TagUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Tag"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","tags"],"summary":"Delete tag","description":"Delete a tag.","operationId":"delete_tag_api_tags__tag_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the tag to delete"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/tags/{tag_id}/records":{"get":{"tags":["cbapp","tags"],"summary":"Get records with tag","description":"Get all records that have a specific tag.","operationId":"get_records_with_tag_api_tags__tag_id__records_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the tag","title":"Tag Id"},"description":"ID of the tag"},{"name":"record_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by record type","title":"Record Type"},"description":"Filter by record type"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/users/":{"get":{"tags":["cbapp","users"],"summary":"List users","description":"List users with optional filtering.","operationId":"list_users_api_users__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"skip","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"}},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbapp__User"},"title":"Response List Users Api Users  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","users"],"summary":"Create user","description":"Create a new user.","operationId":"create_user_api_users__post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__UserCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__User"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/users/{user_id}":{"get":{"tags":["cbapp","users"],"summary":"Get user","description":"Get a single user by ID.","operationId":"get_user_api_users__user_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the user to get"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__User"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","users"],"summary":"Update user","description":"Update a user record.","operationId":"update_user_api_users__user_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the user to update"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__UserUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__User"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","users"],"summary":"Delete user","description":"Delete a user record.","operationId":"delete_user_api_users__user_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the user to delete"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/users/{user_id}/password":{"post":{"tags":["cbapp","users"],"summary":"Create change password","description":"Change a user's password.","operationId":"change_password_api_users__user_id__password_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the user to update password for"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Password Data"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/users/{user_id}/toggle-active":{"post":{"tags":["cbapp","users"],"summary":"Create toggle user active status","description":"Toggle a user's active status.","operationId":"toggle_user_active_status_api_users__user_id__toggle_active_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"The ID of the user to toggle active status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/":{"get":{"tags":["cbapp","walkbooks"],"summary":"List walkbooks","description":"List all walkbooks with optional filtering and pagination.\n\nRequires admin or director role.","operationId":"list_walkbooks_api_walkbooks__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"skip","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status: draft, active, archived","title":"Status"},"description":"Filter by status: draft, active, archived"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name or description","title":"Search"},"description":"Search by name or description"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort by: name, created_at, updated_at, person_count","default":"created_at","title":"Sort By"},"description":"Sort by: name, created_at, updated_at, person_count"},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","description":"Sort order: asc or desc","default":"desc","title":"Sort Order"},"description":"Sort order: asc or desc"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbooksResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","walkbooks"],"summary":"Create walkbook","description":"Create a new walkbook from a segment.\n\nRequires admin or director role.","operationId":"create_walkbook_api_walkbooks__post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Walkbook"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/my-assignments":{"get":{"tags":["cbapp","walkbooks"],"summary":"Get my assignments","description":"Get current user's walkbook assignments.\n\nReturns all walkbooks assigned to the authenticated user.","operationId":"get_my_assignments_api_walkbooks_my_assignments_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/sync":{"post":{"tags":["cbapp","walkbooks"],"summary":"Create sync walkbook actions","description":"Batch upload field actions from mobile device.\n\nUser must be assigned to the walkbook. Actions are processed and stored\nin the contact_action table.","operationId":"sync_walkbook_actions_api_walkbooks_sync_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookSyncRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookSyncResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/walkbooks/sync/{sync_id}":{"get":{"tags":["cbapp","walkbooks"],"summary":"Get sync details","description":"Get details of a specific sync operation.\n\nUser must have access to the assignment (own it or be admin/director).","operationId":"get_sync_details_api_walkbooks_sync__sync_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"sync_id","in":"path","required":true,"schema":{"type":"string","title":"Sync Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}":{"get":{"tags":["cbapp","walkbooks"],"summary":"Get walkbook","description":"Get a specific walkbook by ID.\n\nAny authenticated user can view walkbook details.","operationId":"get_walkbook_api_walkbooks__walkbook_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookWithSegment"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"put":{"tags":["cbapp","walkbooks"],"summary":"Update walkbook","description":"Update walkbook settings.\n\nRequires admin or director role.","operationId":"update_walkbook_api_walkbooks__walkbook_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__Walkbook"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","walkbooks"],"summary":"Delete walkbook","description":"Delete a walkbook.\n\nRequires admin role. Also deletes associated assignments and checkouts.","operationId":"delete_walkbook_api_walkbooks__walkbook_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/actions":{"get":{"tags":["cbapp","walkbooks"],"summary":"Get walkbook actions","description":"Get all contact actions for a walkbook.\n\nRequires admin or director role.","operationId":"get_walkbook_actions_api_walkbooks__walkbook_id__actions_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}},{"name":"skip","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"action_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by action type","title":"Action Type"},"description":"Filter by action type"},{"name":"action_result","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by action result","title":"Action Result"},"description":"Filter by action result"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/actions/since":{"get":{"tags":["cbapp","walkbooks"],"summary":"Get actions since","description":"Get actions synced after a timestamp for delta sync.\n\nUse case: Mobile app catches up after being offline.\n\nUser must have an assignment to the walkbook or be admin/director.\nActions are returned in synced_at order (oldest first) for sequential replay.","operationId":"get_actions_since_api_walkbooks__walkbook_id__actions_since_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}},{"name":"since","in":"query","required":true,"schema":{"type":"string","format":"date-time","description":"ISO timestamp to fetch actions after","title":"Since"},"description":"ISO timestamp to fetch actions after"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Max actions to return","default":100,"title":"Limit"},"description":"Max actions to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__DeltaSyncResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/assignments":{"get":{"tags":["cbapp","walkbooks"],"summary":"List assignments","description":"List all assignments for a walkbook.\n\nAny authenticated user can view assignments.","operationId":"list_assignments_api_walkbooks__walkbook_id__assignments_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status: active, paused, completed","title":"Status"},"description":"Filter by status: active, paused, completed"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookAssignmentsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"post":{"tags":["cbapp","walkbooks"],"summary":"Create assignment","description":"Assign a user to a walkbook.\n\nRequires admin or director role.","operationId":"create_assignment_api_walkbooks__walkbook_id__assignments_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookAssignmentCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookAssignment"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/assignments/{assignment_id}":{"put":{"tags":["cbapp","walkbooks"],"summary":"Update Assignment","description":"Update an assignment.\n\nRequires admin or director role.","operationId":"update_assignment_api_walkbooks__walkbook_id__assignments__assignment_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}},{"name":"assignment_id","in":"path","required":true,"schema":{"type":"string","title":"Assignment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookAssignmentUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookAssignment"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}},"delete":{"tags":["cbapp","walkbooks"],"summary":"Delete assignment","description":"Remove a user assignment from a walkbook.\n\nRequires admin or director role.","operationId":"delete_assignment_api_walkbooks__walkbook_id__assignments__assignment_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}},{"name":"assignment_id","in":"path","required":true,"schema":{"type":"string","title":"Assignment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/assignments/{assignment_id}/force-release":{"post":{"tags":["cbapp","walkbooks"],"summary":"Create force release checkout","description":"Force release a checkout (admin only).\n\nUse this when a device is lost or user cannot check in normally.","operationId":"force_release_checkout_api_walkbooks__walkbook_id__assignments__assignment_id__force_release_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}},{"name":"assignment_id","in":"path","required":true,"schema":{"type":"string","title":"Assignment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/checkin":{"post":{"tags":["cbapp","walkbooks"],"summary":"Create checkin walkbook","description":"Check in after field work.\n\nUser must be assigned to the walkbook and have it checked out.\nRecords the checkin in audit log.","operationId":"checkin_walkbook_api_walkbooks__walkbook_id__checkin_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookCheckinRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/checkout":{"post":{"tags":["cbapp","walkbooks"],"summary":"Create checkout walkbook","description":"Check out a walkbook for field use.\n\nUser must be assigned to the walkbook. Concurrent checkouts are allowed.\nRecords the checkout in audit log.\n\nIf there have been syncs since the last export, the export is auto-regenerated\nto include the latest status changes.","operationId":"checkout_walkbook_api_walkbooks__walkbook_id__checkout_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookCheckoutRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/download":{"get":{"tags":["cbapp","walkbooks"],"summary":"Get download walkbook","description":"Download the walkbook export file.\n\nUser must be assigned to the walkbook or be admin/director.\nReturns the DuckDB file for offline use.","operationId":"download_walkbook_api_walkbooks__walkbook_id__download_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/export":{"post":{"tags":["cbapp","walkbooks"],"summary":"Create export walkbook","description":"Generate a DuckDB export file for offline use.\n\nRequires admin or director role. Creates a portable DuckDB file containing\nall contacts, tags, custom fields, surveys, and pre-generated QR codes.","operationId":"export_walkbook_api_walkbooks__walkbook_id__export_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookExportRequest","default":{"regenerate":false}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookExportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/refresh-count":{"post":{"tags":["cbapp","walkbooks"],"summary":"Create refresh walkbook count","description":"Refresh the person count from the linked segment.\n\nRequires admin or director role.","operationId":"refresh_walkbook_count_api_walkbooks__walkbook_id__refresh_count_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/stats":{"get":{"tags":["cbapp","walkbooks"],"summary":"Get walkbook stats","description":"Get summary statistics for a walkbook.\n\nAny authenticated user can view stats.","operationId":"get_walkbook_stats_api_walkbooks__walkbook_id__stats_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/status":{"get":{"tags":["cbapp","walkbooks"],"summary":"Get walkbook status","description":"Get checkout status for all assignments on a walkbook.\n\nAny authenticated user can view status.","operationId":"get_walkbook_status_api_walkbooks__walkbook_id__status_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/walkbooks/{walkbook_id}/sync-history":{"get":{"tags":["cbapp","walkbooks"],"summary":"Get sync history","description":"Get sync history for a walkbook.\n\nReturns sync logs for assignments the user has access to.\nAdmin/directors see all syncs, regular users see only their own.","operationId":"get_sync_history_api_walkbooks__walkbook_id__sync_history_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"walkbook_id","in":"path","required":true,"schema":{"type":"string","title":"Walkbook Id"}},{"name":"skip","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__WalkbookSyncHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/work-queue/action-types":{"get":{"tags":["cbapp","work-queue"],"summary":"Get action types","description":"Get available action types with their valid results.","operationId":"get_action_types_api_work_queue_action_types_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/work-queue/all-queues":{"get":{"tags":["cbapp","work-queue"],"summary":"Get all queues","description":"Get all active segment assignments with their work queue stats.\nShows which queues are assigned to the current user.","operationId":"get_all_queues_api_work_queue_all_queues_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/work-queue/my-assignments":{"get":{"tags":["cbapp","work-queue"],"summary":"Get my assignments","description":"Get the current user's active segment assignments with work queue stats.","operationId":"get_my_assignments_api_work_queue_my_assignments_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/work-queue/stats/today":{"get":{"tags":["cbapp","work-queue"],"summary":"Get today stats","description":"Get today's activity stats for the current user.","operationId":"get_today_stats_api_work_queue_stats_today_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbapp/api/work-queue/{assignment_id}/next":{"get":{"tags":["cbapp","work-queue"],"summary":"Get next contact","description":"Get the next contact in the work queue for an assignment.","operationId":"get_next_contact_api_work_queue__assignment_id__next_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"assignment_id","in":"path","required":true,"schema":{"type":"string","title":"Assignment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/work-queue/{queue_item_id}/action":{"post":{"tags":["cbapp","work-queue"],"summary":"Create log action","description":"Log an action on a contact and optionally complete the queue item.","operationId":"log_action_api_work_queue__queue_item_id__action_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"queue_item_id","in":"path","required":true,"schema":{"type":"string","title":"Queue Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__ActionLog"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/work-queue/{queue_item_id}/complete":{"post":{"tags":["cbapp","work-queue"],"summary":"Create complete item","description":"Mark a queue item as completed.","operationId":"complete_item_api_work_queue__queue_item_id__complete_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"queue_item_id","in":"path","required":true,"schema":{"type":"string","title":"Queue Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/api/work-queue/{queue_item_id}/skip":{"post":{"tags":["cbapp","work-queue"],"summary":"Create skip item","description":"Skip a queue item.","operationId":"skip_item_api_work_queue__queue_item_id__skip_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"queue_item_id","in":"path","required":true,"schema":{"type":"string","title":"Queue Item Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__SkipRequest","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HTTPValidationError"}}}}}}},"/cbapp/health":{"get":{"tags":["cbapp","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HealthResponse"}}}}}}},"/cbapp/health/detailed":{"get":{"tags":["cbapp","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__HealthResponse"}}}}}}},"/cbapp/prime":{"get":{"tags":["cbapp","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__PrimeResponse"}}}}}}},"/cbapp/status":{"get":{"tags":["cbapp","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbapp__StatusResponse"}}}}}}},"/cbauth/.well-known/oauth-authorization-server":{"get":{"tags":["cbauth","OAuth"],"summary":"Authorization Server Metadata (RFC 8414)","description":"Return OAuth 2.1 authorization server metadata per RFC 8414.","operationId":"authorization_server_metadata__well_known_oauth_authorization_server_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbauth/.well-known/oauth-protected-resource":{"get":{"tags":["cbauth","OAuth"],"summary":"Protected Resource Metadata (RFC 9728)","description":"Return protected resource metadata per RFC 9728.","operationId":"protected_resource_metadata__well_known_oauth_protected_resource_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbauth/api/v1/activity/logins":{"get":{"tags":["cbauth","Activity"],"summary":"List Logins","description":"List recent login events, newest first.","operationId":"list_logins_api_v1_activity_logins_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"success","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Success"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbauth__LoginEventResponse"},"title":"Response List Logins Api V1 Activity Logins Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/activity/sessions":{"get":{"tags":["cbauth","Activity"],"summary":"Active Sessions","description":"Approximate active sessions from recent successful logins.","operationId":"active_sessions_api_v1_activity_sessions_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbauth__ActiveSessionResponse"},"title":"Response Active Sessions Api V1 Activity Sessions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/apps":{"get":{"tags":["cbauth","Apps"],"summary":"List Apps","description":"List all registered apps.","operationId":"list_apps_api_v1_apps_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbauth__AppResponse"},"title":"Response List Apps Api V1 Apps Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"post":{"tags":["cbauth","Apps"],"summary":"Create App","description":"Register a new app.","operationId":"create_app_api_v1_apps_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__AppCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__AppResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/apps/{app_id}":{"get":{"tags":["cbauth","Apps"],"summary":"Get App","description":"Get a specific app.","operationId":"get_app_api_v1_apps__app_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","title":"App Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__AppResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"patch":{"tags":["cbauth","Apps"],"summary":"Update App","description":"Update an app.","operationId":"update_app_api_v1_apps__app_id__patch","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","title":"App Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__AppUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__AppResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"delete":{"tags":["cbauth","Apps"],"summary":"Delete App","description":"Delete an app.","operationId":"delete_app_api_v1_apps__app_id__delete","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string","title":"App Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/groups":{"get":{"tags":["cbauth","Groups"],"summary":"List Groups","description":"List all groups.","operationId":"list_groups_api_v1_groups_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbauth__GroupResponse"},"title":"Response List Groups Api V1 Groups Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"post":{"tags":["cbauth","Groups"],"summary":"Create Group","description":"Create a new group.","operationId":"create_group_api_v1_groups_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__GroupCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__GroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/groups/{group_id}":{"get":{"tags":["cbauth","Groups"],"summary":"Get Group","description":"Get a group with its permissions.","operationId":"get_group_api_v1_groups__group_id__get","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__GroupWithPermissions"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"patch":{"tags":["cbauth","Groups"],"summary":"Update Group","description":"Update a group.","operationId":"update_group_api_v1_groups__group_id__patch","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__GroupUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__GroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"delete":{"tags":["cbauth","Groups"],"summary":"Delete Group","description":"Delete a group.","operationId":"delete_group_api_v1_groups__group_id__delete","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/groups/{group_id}/permissions":{"post":{"tags":["cbauth","Groups"],"summary":"Assign Permission","description":"Assign a permission to a group.","operationId":"assign_permission_api_v1_groups__group_id__permissions_post","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__AssignPermission"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/groups/{group_id}/permissions/{permission_id}":{"delete":{"tags":["cbauth","Groups"],"summary":"Remove Permission","description":"Remove a permission from a group.","operationId":"remove_permission_api_v1_groups__group_id__permissions__permission_id__delete","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}},{"name":"permission_id","in":"path","required":true,"schema":{"type":"string","title":"Permission Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/keys":{"get":{"tags":["cbauth","API Keys"],"summary":"List My Keys","description":"List the authenticated user's API keys.","operationId":"list_my_keys_api_v1_keys_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbauth__APIKeyResponse"},"title":"Response List My Keys Api V1 Keys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"post":{"tags":["cbauth","API Keys"],"summary":"Create Key","description":"Create an API key for the current user.","operationId":"create_key_api_v1_keys_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__APIKeyCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__APIKeyCreated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/keys/all":{"get":{"tags":["cbauth","API Keys"],"summary":"List All Keys","description":"List all API keys (admin).","operationId":"list_all_keys_api_v1_keys_all_get","parameters":[{"name":"include_revoked","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Revoked"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbauth__APIKeyResponse"},"title":"Response List All Keys Api V1 Keys All Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/keys/{key_id}":{"delete":{"tags":["cbauth","API Keys"],"summary":"Revoke Key","description":"Revoke one of YOUR API keys. Holders of `auth.keys.revoke` (or a\ncovering wildcard) may revoke any key.\n\nOwnership check added per AUTH-BASELINE-2026-09-02 P0.2 — previously\nany authenticated identity could revoke any key by id.","operationId":"revoke_key_api_v1_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/magic-links":{"post":{"tags":["cbauth","Magic Links"],"summary":"Issue Magic Link","description":"Mint a one-time sign-in link for a target user.","operationId":"issue_magic_link_api_v1_magic_links_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__MagicLinkIssueRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__MagicLinkIssueResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/permissions":{"get":{"tags":["cbauth","Permissions"],"summary":"List Permissions","description":"List all permissions.","operationId":"list_permissions_api_v1_permissions_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbauth__PermissionResponse"},"title":"Response List Permissions Api V1 Permissions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"post":{"tags":["cbauth","Permissions"],"summary":"Create Permission","description":"Create a new permission.","operationId":"create_permission_api_v1_permissions_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__PermissionCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__PermissionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/permissions/{permission_id}":{"get":{"tags":["cbauth","Permissions"],"summary":"Get Permission","description":"Get a specific permission.","operationId":"get_permission_api_v1_permissions__permission_id__get","parameters":[{"name":"permission_id","in":"path","required":true,"schema":{"type":"string","title":"Permission Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__PermissionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"delete":{"tags":["cbauth","Permissions"],"summary":"Delete Permission","description":"Delete a permission.","operationId":"delete_permission_api_v1_permissions__permission_id__delete","parameters":[{"name":"permission_id","in":"path","required":true,"schema":{"type":"string","title":"Permission Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/service-accounts":{"post":{"tags":["cbauth","Service Accounts"],"summary":"Create a service account","description":"Atomically creates a non-interactive cbauth user, a per-slug group holding the requested permissions, and an initial API key. The raw key is returned once — store it in the service's vault-consumer env (SYSFORGE_API_KEY).","operationId":"create_service_account_endpoint_api_v1_service_accounts_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__ServiceAccountCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__ServiceAccountCreated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/users":{"get":{"tags":["cbauth","Users"],"summary":"List Users","description":"List all users (admin).","operationId":"list_users_api_v1_users_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbauth__UserResponse"},"title":"Response List Users Api V1 Users Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"post":{"tags":["cbauth","Users"],"summary":"Create User","description":"Create a new user (admin or service-account caller).\n\ncbonboard's RealAuthClient creates provisional users here with\npassword=None, kind=\"human\", phone_confirmed=False, email_confirmed=False.\nAdmin tooling typically passes a password.\n\nService-account user records (kind=\"service\") go through\n/api/v1/service-accounts, not this endpoint.","operationId":"create_user_api_v1_users_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__UserCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/users/me":{"get":{"tags":["cbauth","Users"],"summary":"Get Me","description":"Get current authenticated user's profile.\n\nReturns M2M groups + businesses in addition to the legacy group_id.\nDownstream services (cbloom auth resolver) must aggregate permissions\nacross groups[], not just group_id — users moved to multi-group only\nhave group_id=None.\n\nAlso returns Trinity-Step-3 tenant context: ``current_tenant`` (read\nfrom the JWT claim) and ``tenants`` (full membership list from\nuser_tenants).","operationId":"get_me_api_v1_users_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__UserMe"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/users/me/tenants/switch":{"post":{"tags":["cbauth","Users"],"summary":"Switch the active tenant","description":"Re-issue access + refresh tokens scoped to a different tenant the\nuser belongs to. Returns fresh JWTs and (for browser callers) sets\nauth cookies on .campaignbrain.dev.\n\n403 if the user isn't a member of the requested tenant.","operationId":"switch_tenant_api_v1_users_me_tenants_switch_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_refresh","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Refresh"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__TenantSwitchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/users/{user_id}":{"get":{"tags":["cbauth","Users"],"summary":"Get User","description":"Get a specific user by ID.","operationId":"get_user_api_v1_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"patch":{"tags":["cbauth","Users"],"summary":"Update User","description":"Update a user.\n\nSelf-edit (caller.id == user_id) is allowed for safe profile fields\nwithout auth.users.update. Group/business/is_active/external_id changes\non any user (including self) require auth.users.update.\n\nGroup membership semantics:\n  - ``group_ids`` (M2M, preferred): replaces user_groups rows wholesale.\n    Empty list removes the user from all groups. Also updates the\n    legacy ``group_id`` to the first id in the new set.\n  - ``group_id`` (legacy single, kept for back-compat): replaces M2M\n    rows with one row pointing at the new group, OR clears them when\n    explicitly null.\n\nPermissions are invalidated on any mutation.","operationId":"update_user_api_v1_users__user_id__patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__UserUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"delete":{"tags":["cbauth","Users"],"summary":"Deactivate User","description":"Deactivate a user (admin).","operationId":"deactivate_user_api_v1_users__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/users/{user_id}/activity":{"get":{"tags":["cbauth","Activity"],"summary":"User Activity","description":"Per-user activity summary.\n\nSelf-read (caller.id == user_id) is allowed without auth.activity.read.\nReading another user's activity requires auth.activity.read.","operationId":"user_activity_api_v1_users__user_id__activity_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__UserActivityResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/users/{user_id}/mfa/reset":{"post":{"tags":["cbauth","Users"],"summary":"Clear a user's 2FA enrollment (admin recovery)","description":"Wipe ALL second-factor enrollment for a user — TOTP secret, SMS 2FA, and\nbackup codes — plus any outstanding SMS challenge codes.\n\nSelf-service disable (``/auth/mfa/*/disable``) needs a password re-auth, so\nit's useless to someone locked out of BOTH their authenticator and their\nbackup codes. This is the admin escape hatch: after a reset the user logs in\nwith just their password and can re-enroll from My Account. Idempotent —\nresetting an account with no 2FA is a no-op. Gated on ``auth.users.update``\n(same as unlock); every call is logged for audit.","operationId":"reset_user_mfa_api_v1_users__user_id__mfa_reset_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/users/{user_id}/tenants":{"post":{"tags":["cbauth","Users"],"summary":"Grant a user membership in a tenant","description":"Grant a user membership in a tenant.\n\nIdempotent — if the membership already exists, returns the existing row\n(200-equivalent semantics; we still emit 201 because clients of this\nendpoint typically don't need to distinguish create vs already-exists).\n\nThe membership is what the dashboard's tenant switcher reads to decide\nwhich tenants to show. Used by tenant onboarding to promote a freshly\ncreated user into the new tenant as `admin`.","operationId":"grant_tenant_membership_api_v1_users__user_id__tenants_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__TenantMembershipCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__TenantMembership"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/users/{user_id}/tenants/{tenant_slug}":{"delete":{"tags":["cbauth","Users"],"summary":"Revoke a tenant membership","description":"Revoke a user's membership in a tenant. 404 if not a member.","operationId":"revoke_tenant_membership_api_v1_users__user_id__tenants__tenant_slug__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"tenant_slug","in":"path","required":true,"schema":{"type":"string","title":"Tenant Slug"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/api/v1/users/{user_id}/unlock":{"post":{"tags":["cbauth","Users"],"summary":"Clear lockout state for a user","description":"Reset ``failed_login_attempts`` and clear ``locked_until``.\n\nThe natural unlock path is a successful password login (auth.py clears\nboth fields). This endpoint is the admin escape hatch when the user\ncan't get to a successful login on their own — e.g. they don't remember\nthe password yet, or the lockout has expired but their browser is still\ncaching the old \"Account locked\" message. Idempotent: clearing an\nalready-clear state is a no-op.","operationId":"unlock_user_api_v1_users__user_id__unlock_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/change-password":{"get":{"tags":["cbauth","Authentication"],"summary":"Change password form","description":"Show the change password form.","operationId":"change_password_form_auth_change_password_get","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"post":{"tags":["cbauth","Authentication"],"summary":"Change password","description":"Change the authenticated user's password.\n\nSend ``Accept: application/json`` for a machine-readable answer —\n``200 {\"ok\": true}`` on success, ``4xx {\"error\": \"...\"}`` on refusal. Same\nnegotiation the login route uses. Without that header the rendered form\ncomes back, now with a truthful status code (cbauth#85).","operationId":"change_password_auth_change_password_post","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbauth__Body_change_password_auth_change_password_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/dashboard":{"get":{"tags":["cbauth","Authentication"],"summary":"User service dashboard","description":"Show authenticated user their available services.","operationId":"dashboard_auth_dashboard_get","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/forgot-password":{"post":{"tags":["cbauth","Authentication"],"summary":"Request a password reset email","description":"Email a single-use reset link if the address is a login-capable account.\n\nThe response is intentionally uniform (always ``200 {\"ok\": true}``) whether\nor not the account exists, is a service account, or the email send fails, so\nan unauthenticated caller cannot enumerate accounts by probing this route.\nRate-limited requests get the same uniform 200 with no email sent.","operationId":"forgot_password_auth_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__ForgotPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/kiosk-token":{"post":{"tags":["cbauth","Authentication"],"summary":"Mint a device-bound kiosk token for a provisioned principal","description":"Mint a kiosk token whose ``sub`` is the device's provisioned\nservice-account principal. Caller must hold ``auth.kiosk.mint`` (enforced by\nthe dependency). 404 if the device has no active principal.","operationId":"mint_kiosk_token_auth_kiosk_token_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__KioskTokenRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__KioskTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/login":{"post":{"tags":["cbauth","Authentication"],"summary":"Authenticate with email and password","description":"Validate credentials and issue JWT tokens.\n\nremember (default True to preserve legacy persistent-cookie behavior for\nAPI callers that omit it) controls cookie persistence — see\n_set_auth_cookies. The login page sends the checkbox state explicitly.","operationId":"login_auth_login_post","parameters":[{"name":"oauth_return_qs","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oauth Return Qs"}}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbauth__Body_login_auth_login_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}},"get":{"tags":["cbauth","Authentication"],"summary":"Login page","description":"Serve the login form.","operationId":"login_page_auth_login_get","parameters":[{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"next","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}},{"name":"oauth_return_qs","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oauth Return Qs"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/logout":{"get":{"tags":["cbauth","Authentication"],"summary":"Clear session and logout","description":"Clear auth cookies.","operationId":"logout_auth_logout_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbauth/auth/magic-consume":{"post":{"tags":["cbauth","Authentication"],"summary":"Consume a magic link and start a session","description":"Validate a single-use magic token and issue session cookies (login).\n\nThe login page POSTs ``{\"token\": \"...\"}`` here after it spots ``?magic=`` in\nthe URL; on success we set the auth cookies and return JSON so the page can\nredirect. Errors come back as 400 JSON with a friendly message.","operationId":"magic_consume_auth_magic_consume_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbauth/auth/magic-link":{"post":{"tags":["cbauth","Authentication"],"summary":"Email a one-time passwordless login link","description":"Email a single-use sign-in link if the address is a login-capable account.\n\nUniform ``200 {\"ok\": true}`` regardless of whether the account exists — same\nanti-enumeration contract as forgot-password. Rate-limited requests get the\nsame uniform 200 with no email sent.","operationId":"magic_link_auth_magic_link_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__MagicLinkRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/mfa/sms/disable":{"post":{"tags":["cbauth","Authentication"],"summary":"Turn off SMS 2FA","description":"Disable SMS 2FA. Requires a password re-auth.","operationId":"mfa_sms_disable_auth_mfa_sms_disable_post","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/mfa/sms/enable":{"post":{"tags":["cbauth","Authentication"],"summary":"Confirm the texted code and turn on SMS 2FA","description":"Step 2: verify the code from /mfa/sms/start, confirm the phone, and turn\non SMS 2FA. Returns one-time backup codes if this is the first factor.","operationId":"mfa_sms_enable_auth_mfa_sms_enable_post","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/mfa/sms/start":{"post":{"tags":["cbauth","Authentication"],"summary":"Text a code to begin SMS 2FA setup","description":"Send a one-time code to the phone on file to begin SMS 2FA enrollment.\n\nStep 1 of the SMS enrollment loop (setup -> confirm, mirroring TOTP). The\nnumber does not need to be confirmed yet — confirming it is the whole point;\n/mfa/sms/enable flips phone_confirmed once the texted code comes back.","operationId":"mfa_sms_start_auth_mfa_sms_start_post","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/mfa/status":{"get":{"tags":["cbauth","Authentication"],"summary":"Current user's 2FA status","description":"Report the caller's 2FA state — which factors are on, whether a confirmed\nphone is on file, and how many single-use backup codes remain. Drives the\nSecurity section of the user-preferences page.","operationId":"mfa_status_auth_mfa_status_get","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/mfa/totp/disable":{"post":{"tags":["cbauth","Authentication"],"summary":"Turn off TOTP","description":"Disable authenticator 2FA. Requires a password re-auth.","operationId":"mfa_totp_disable_auth_mfa_totp_disable_post","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/mfa/totp/enable":{"post":{"tags":["cbauth","Authentication"],"summary":"Confirm and activate TOTP","description":"Verify the first code from the authenticator, activate TOTP, and return\none-time backup codes (shown once).","operationId":"mfa_totp_enable_auth_mfa_totp_enable_post","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/mfa/totp/setup":{"post":{"tags":["cbauth","Authentication"],"summary":"Begin TOTP enrollment","description":"Generate a fresh TOTP secret (stored but not active) and return the\notpauth:// URI so the page can render a QR. Confirm with /mfa/totp/enable.","operationId":"mfa_totp_setup_auth_mfa_totp_setup_post","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/mfa/verify":{"post":{"tags":["cbauth","Authentication"],"summary":"Complete MFA and start a session","description":"Exchange an mfa_challenge token + a valid code for a real session.\n\nAccepts a TOTP code, an SMS code, or a single-use backup code.","operationId":"mfa_verify_auth_mfa_verify_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbauth/auth/refresh":{"post":{"tags":["cbauth","Authentication"],"summary":"Refresh access token","description":"Exchange a refresh token for a new access token.","operationId":"refresh_auth_refresh_post","parameters":[{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}},{"name":"cbauth_refresh","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Refresh"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/register":{"post":{"tags":["cbauth","Authentication"],"summary":"Register a new user","description":"Create a new user account (if registration is enabled).","operationId":"register_auth_register_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbauth__Body_register_auth_register_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/reset-password":{"post":{"tags":["cbauth","Authentication"],"summary":"Set a new password using a reset token","description":"Validate a single-use reset token and set the new password.","operationId":"reset_password_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/sms-code":{"post":{"tags":["cbauth","Authentication"],"summary":"Text a one-time sign-in code to a phone","description":"Text a 6-digit sign-in code if the phone matches an account.\n\nUniform ``200 {\"ok\": true}`` whether or not a matching account exists —\nsame anti-enumeration contract as magic-link, and the same rate limits\n(per-IP window + per-account cooldown).","operationId":"sms_code_auth_sms_code_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__SmsCodeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/sms-login":{"post":{"tags":["cbauth","Authentication"],"summary":"Sign in with a phone number and texted code","description":"Exchange phone + code for a session.\n\nWrong guesses increment the code's attempt counter; at\n``sms_code_max_attempts`` the code burns and a fresh one must be\nrequested. Errors are deliberately vague (no \"wrong code\" vs \"no such\nphone\" distinction) to keep the enumeration surface closed.","operationId":"sms_login_auth_sms_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__SmsLoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/auth/verify":{"get":{"tags":["cbauth","Authentication"],"summary":"Verify session authentication","description":"NGINX auth_request endpoint. Returns 200/401/403.","operationId":"verify_auth_auth_verify_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-required-permission","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Required-Permission"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/health":{"get":{"tags":["cbauth","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HealthResponse"}}}}}}},"/cbauth/health/detailed":{"get":{"tags":["cbauth","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HealthResponse"}}}}}}},"/cbauth/oauth/authorize":{"get":{"tags":["cbauth","OAuth"],"summary":"Authorization code grant","description":"Issue an authorization code with PKCE binding for the authenticated user.","operationId":"authorize_oauth_authorize_get","parameters":[{"name":"response_type","in":"query","required":true,"schema":{"type":"string","title":"Response Type"}},{"name":"client_id","in":"query","required":true,"schema":{"type":"string","title":"Client Id"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string","title":"Redirect Uri"}},{"name":"code_challenge","in":"query","required":true,"schema":{"type":"string","title":"Code Challenge"}},{"name":"code_challenge_method","in":"query","required":false,"schema":{"type":"string","default":"S256","title":"Code Challenge Method"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope"}},{"name":"resource","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resource"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/oauth/elevate":{"post":{"tags":["cbauth","OAuth"],"summary":"Atomic onboarding elevation","description":"Atomically apply onboarding state transitions and re-mint a user\ntoken (cbauth#66).\n\nAuth: Authorization: Bearer <user access_token>. The bearer's user_id\nmust match the elevation_proof's `sub`.\n\nBody:\n  {\n    \"elevation_proof\": \"<HS256 JWT>\",\n    \"group_changes\": {\"add\": [...], \"remove\": [...]},\n    \"set_phone_confirmed\": bool,\n    \"set_email_confirmed\": bool  // optional\n  }\n\nOn success: returns a fresh access+refresh pair in the same\ntoken_family_id as the bearer. Group/confirmation state and token mint\nhappen in a single DB transaction; the elevation_proof's jti is burned\nin Redis to prevent replay.","operationId":"oauth_elevate_oauth_elevate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbauth/oauth/register":{"post":{"tags":["cbauth","OAuth"],"summary":"Dynamic Client Registration","description":"Register a new OAuth client dynamically per RFC 7591.","operationId":"register_client_oauth_register_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbauth/oauth/revoke":{"post":{"tags":["cbauth","OAuth"],"summary":"Token revocation","description":"Revoke a refresh token per RFC 7009.","operationId":"revoke_token_oauth_revoke_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbauth__Body_revoke_token_oauth_revoke_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__HTTPValidationError"}}}}}}},"/cbauth/oauth/token":{"post":{"tags":["cbauth","OAuth"],"summary":"Token exchange","description":"Exchange a credential for an access token.\n\nSupports two body shapes:\n  - form-encoded (OAuth-standard): grant_type=authorization_code or\n    grant_type=refresh_token. Original PKCE flow.\n  - JSON (cbonboard impersonation): grant_type=service_account,\n    user_id=<target>. Caller authenticates via X-API-Key (cbauth_)\n    and must hold the auth.tokens.mint_user permission.","operationId":"token_exchange_oauth_token_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbauth/prime":{"get":{"tags":["cbauth","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__PrimeResponse"}}}}}}},"/cbauth/status":{"get":{"tags":["cbauth","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbauth__StatusResponse"}}}}}}},"/cbbanking/api/v1/datasets":{"get":{"tags":["cbbanking","Datasets (Fabric)"],"summary":"List datasets exposed by this driver","description":"List the self-describing datasets this driver exposes to cbmodels fabric.\n\nReturns one entry per Huntington feed (`huntington.txns`,\n`huntington.balances`), each with its column schema, current row count,\nlast-pull / last-seen-row freshness, a `health` flag, and a `flavor`\ndescriptor. The shape matches what\n`cbmodels.federation.registry._discover_http_drivers` consumes.","operationId":"list_datasets_api_v1_datasets_get","responses":{"200":{"description":"One self-describing entry per feed","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Datasets Api V1 Datasets Get"},"example":[{"id":"huntington.txns","kind_hint":"txns","columns":[{"name":"account_id","type":"TEXT"},{"name":"txn_date","type":"DATE"},{"name":"amount","type":"DOUBLE"},{"name":"bai2_type_code","type":"TEXT"},{"name":"description","type":"TEXT"}],"row_count":1402,"last_pulled":"2026-09-12T12:05:44+00:00","last_seen_row":"2026-09-11","health":"ok","flavor":{"flavor":"banking","basis":"cash","kind":"txns","bai2_codes":["169","206","277","351","466","469","475","506","651"],"source_system":"huntington"}},{"id":"huntington.balances","kind_hint":"balances","columns":[{"name":"account_id","type":"TEXT"},{"name":"as_of_date","type":"DATE"},{"name":"closing_balance","type":"DOUBLE"}],"row_count":264,"last_pulled":"2026-09-12T12:05:44+00:00","last_seen_row":"2026-09-11","health":"ok","flavor":{"flavor":"banking","basis":"cash","kind":"balances","source_system":"huntington"}}]}}}}}},"/cbbanking/api/v1/datasets/{dataset_id}/rows":{"get":{"tags":["cbbanking","Datasets (Fabric)"],"summary":"Stream dataset rows as ndjson","description":"Stream all rows for a dataset as newline-delimited JSON.\n\nOne JSON object per line. Dates are emitted as ISO YYYY-MM-DD strings.\nConsumed by cbmodels cash_position via httpx-fetch + DuckDB read_json_auto.","operationId":"stream_rows_api_v1_datasets__dataset_id__rows_get","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","description":"Dataset id from GET /api/v1/datasets: huntington.txns or huntington.balances","title":"Dataset Id"},"description":"Dataset id from GET /api/v1/datasets: huntington.txns or huntington.balances"}],"responses":{"200":{"description":"One JSON object per line, keys exactly as declared in `columns`","content":{"application/json":{"schema":{}},"application/x-ndjson":{"example":"{\"account_id\": \"09990001234\", \"txn_date\": \"2026-09-09\", \"amount\": 24812.5, \"bai2_type_code\": \"169\", \"description\": \"BANKCARD DEPOSIT\\nMERCH SVCS\"}\n{\"account_id\": \"09990001234\", \"txn_date\": \"2026-09-09\", \"amount\": 38971.3, \"bai2_type_code\": \"651\", \"description\": \"TARGET SWEEP DEBIT\"}\n"}}},"404":{"description":"No dataset with that id; `GET /api/v1/datasets` lists them","content":{"application/json":{"example":{"detail":"unknown dataset: huntington.loans"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbanking__HTTPValidationError"}}}}}}},"/cbbanking/api/v1/export/balances":{"get":{"tags":["cbbanking","Export"],"summary":"Daily per-account balance summary feed","description":"One row per account per business date, from the BAI2 account header.\n\n## What this returns\n\nThe bank's own summary for each account on each `as_of_date`: opening and\nclosing ledger, opening and closing available, total credits and total\ndebits, all integer cents, plus the full list of `summary_items` the file\ncarried with their type codes and labels. `parse_status='ok'` files only.\n`balance_uid` (`{profile}:{account}:{as_of_date}`) is stable across\nre-parses; upsert on it.\n\n## Use this when\n\n- \"What was the balance on date X?\" Use `closing_ledger` (booked) or\n  `closing_available` (spendable, after float).\n- Reconciling: `opening_ledger + total_credits - total_debits` should\n  equal `closing_ledger`; when it does not, the file deserves a look.\n- Syncing incrementally with `since_parsed`, as with `/txns`.\n\n## Reading a row\n\nThe six named fields are lifted from `summary_items` by type code (010,\n015, 040, 045, 100, 400). `summary_items` is the complete set, including\nthe float buckets (072, 074, 076) and anything new the bank starts\nsending; `/summary-codes` says which codes have ever appeared.\n`item_count` and `funds_type` are null unless the bank populated them.\n\n## Example\n\n\"Operating account, 2026-09-09: opened and closed at $25,000.00 (the\nsweep target), with $39,812.50 in and $39,812.50 out during the day.\"","operationId":"export_balances_api_v1_export_balances_get","parameters":[{"name":"profile","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by SFTP profile name","title":"Profile"},"description":"Filter by SFTP profile name"},{"name":"account","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact account_number match","title":"Account"},"description":"Exact account_number match"},{"name":"as_of_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Business date lower bound, ISO YYYY-MM-DD (inclusive)","title":"As Of From"},"description":"Business date lower bound, ISO YYYY-MM-DD (inclusive)"},{"name":"as_of_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Business date upper bound, ISO YYYY-MM-DD (inclusive)","title":"As Of To"},"description":"Business date upper bound, ISO YYYY-MM-DD (inclusive)"},{"name":"since_parsed","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Incremental cursor; rows with parsed_at strictly greater are returned","title":"Since Parsed"},"description":"Incremental cursor; rows with parsed_at strictly greater are returned"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Page size, 1 to 5000","default":500,"title":"Limit"},"description":"Page size, 1 to 5000"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Rows to skip; page with `total` from the response","default":0,"title":"Offset"},"description":"Rows to skip; page with `total` from the response"}],"responses":{"200":{"description":"One page of account-days, newest first","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Export Balances Api V1 Export Balances Get"},"example":{"total":264,"limit":500,"offset":0,"balances":[{"balance_uid":"h_prod:09990001234:2026-09-09","profile":"huntington_prod","account_number":"09990001234","as_of_date":"2026-09-09","currency":"USD","opening_ledger":2500000,"closing_ledger":2500000,"opening_available":2500000,"closing_available":2500000,"total_credits":3981250,"total_debits":3981250,"summary_items":[{"type_code":"010","label":"Opening Ledger","amount_cents":2500000},{"type_code":"015","label":"Closing Ledger","amount_cents":2500000},{"type_code":"022","label":"Average Closing Ledger MTD","amount_cents":2500000},{"type_code":"040","label":"Opening Available","amount_cents":2500000},{"type_code":"045","label":"Closing Available","amount_cents":2500000},{"type_code":"072","label":"1-Day Float","amount_cents":0},{"type_code":"074","label":"2-Day Float","amount_cents":0},{"type_code":"076","label":">2-Day Float","amount_cents":0},{"type_code":"100","label":"Total Credits","amount_cents":3981250},{"type_code":"400","label":"Total Debits","amount_cents":3981250}],"log_number":"13990012345","parsed_at":"2026-09-10T12:10:41.409594"}]}}}},"422":{"description":"A date or cursor filter did not parse","content":{"application/json":{"example":{"detail":"as_of_from must be ISO YYYY-MM-DD, got: '9/9/2026'"}}}}}}},"/cbbanking/api/v1/export/returns":{"get":{"tags":["cbbanking","Export"],"summary":"Flat per-ACH-return export feed","description":"Flat per-return export over parsed_nacha_*. ``parse_status='ok'`` only.\n\n**`is_test` is not decoration.** Huntington stamps sample files by\nputting TEST in the Immediate Origin Name of the file header, and the\nonly ACH return data we hold today is stamped that way. A caller that\nsums these without checking the flag reports fabricated money — the\nsame failure as the sweep overstatement, and harder to spot because\nevery individual figure parses and reconciles. The flag rides on every\nrow, and `totals` is broken out by it, so no consumer has to know the\nconvention to avoid the trap.\n\n`totals` is computed server-side over the whole filtered set, not the\nreturned page, for the reason given in `/summary`: a client that sums\na truncated page reports a confident wrong figure.\n\nUnlike `/txns`, this materialises the filtered set before paging.\n`category` and `retriable` are derived from the code table in Python,\nso they cannot be pushed into the WHERE clause without duplicating\nthat table in SQL, and the totals have to see every matching row\nanyway. That is a deliberate trade sized to ACH returns — a feed\nmeasured in tens per day, against tens of thousands of BAI2\ntransactions. If returns ever reach that order, push the code table\ninto SQL and aggregate there; do not paginate the totals.","operationId":"export_returns_api_v1_export_returns_get","parameters":[{"name":"profile","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by SFTP profile name","title":"Profile"},"description":"Filter by SFTP profile name"},{"name":"reason_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact return reason code, e.g. R01","title":"Reason Code"},"description":"Exact return reason code, e.g. R01"},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"funds | account | authorization | format | other","title":"Category"},"description":"funds | account | authorization | format | other"},{"name":"retriable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only returns the ODFI may (true) or may not (false) re-present","title":"Retriable"},"description":"Only returns the ODFI may (true) or may not (false) re-present"},{"name":"is_test","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter to the bank's TEST files (true) or real ones (false)","title":"Is Test"},"description":"Filter to the bank's TEST files (true) or real ones (false)"},{"name":"since_parsed","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Incremental cursor; rows with parsed_at strictly greater are returned","title":"Since Parsed"},"description":"Incremental cursor; rows with parsed_at strictly greater are returned"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Page size, 1 to 5000","default":500,"title":"Limit"},"description":"Page size, 1 to 5000"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Rows to skip; page with `total` from the response","default":0,"title":"Offset"},"description":"Rows to skip; page with `total` from the response"}],"responses":{"200":{"description":"One page of returns; `totals` cover the whole filtered set","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Export Returns Api V1 Export Returns Get"},"example":{"total":2,"limit":500,"offset":0,"filters":{},"totals":{"count":2,"amount_cents":160000,"retriable_amount_cents":35000,"live":{"count":0,"amount_cents":0,"retriable_amount_cents":0},"test":{"count":2,"amount_cents":160000,"retriable_amount_cents":35000}},"returns":[{"return_uid":"h_prep:13990010001:0:0","profile":"huntington_prep","log_number":"13990010001","sender_filename":"123456.PSN.NACHA.RTRN.20260824115900.txt","is_test":true,"origin_name":"HUNTINGTON NAT'L BK TEST","file_creation_date":"2026-08-24","effective_entry_date":"2026-08-21","company_name":"EXAMPLE PAYROLL LLC","company_identification":"9123456789","standard_entry_class":"PPD","individual_name":"PAT EXAMPLE","individual_id":"EMP-00042","receiving_routing":"072000000","dfi_account_number":"099900771122","amount_cents":35000,"transaction_code":"26","return_reason_code":"R01","return_reason_label":"Insufficient Funds","return_reason_category":"funds","retriable":true,"trace_number":"072000000000123","original_entry_trace":"072000000000098","parsed_at":"2026-08-24T15:31:02.118004"},{"return_uid":"h_prep:13990010001:0:1","profile":"huntington_prep","log_number":"13990010001","sender_filename":"123456.PSN.NACHA.RTRN.20260824115900.txt","is_test":true,"origin_name":"HUNTINGTON NAT'L BK TEST","file_creation_date":"2026-08-24","effective_entry_date":"2026-08-21","company_name":"EXAMPLE PAYROLL LLC","company_identification":"9123456789","standard_entry_class":"PPD","individual_name":"SAM EXAMPLE","individual_id":"EMP-00042","receiving_routing":"072000000","dfi_account_number":"099900771122","amount_cents":125000,"transaction_code":"26","return_reason_code":"R02","return_reason_label":"Account Closed","return_reason_category":"account","retriable":false,"trace_number":"072000000000124","original_entry_trace":"072000000000098","parsed_at":"2026-08-24T15:31:02.118004"}]}}}},"422":{"description":"A date or cursor filter did not parse","content":{"application/json":{"example":{"detail":"as_of_from must be ISO YYYY-MM-DD, got: '9/9/2026'"}}}}}}},"/cbbanking/api/v1/export/summary":{"get":{"tags":["cbbanking","Export"],"summary":"Aggregate transaction rollups, split external vs internal","description":"Aggregate rollups over successfully-parsed transactions.\n\nAmounts are integer cents throughout — no floats, so nothing is lost to\nbinary rounding on the way out.\n\nEach rollup carries three buckets:\n\n- **external** — genuine cash flow with third parties. This is the figure\n  to answer \"how much money came in\".\n- **internal** — movement between the company's own accounts: sweeps to\n  and from the concentration account, and online book transfers. Real\n  ledger entries, but not cash flow.\n- **gross** — external + internal, i.e. what naively summing rows gives.\n\nReporting all three makes the distortion visible instead of implicit: on\nas-of 2026-07-27 gross inflow was \\$406,856.93 against \\$47,243.35\nexternal, because that day's sweep landed as a credit.\n\n`unclassified` catches type codes that resolve to neither credit nor\ndebit, so a code class we have not seen yet cannot quietly drop out of the\ntotals. `parse_status='ok'` rows only.","operationId":"export_summary_api_v1_export_summary_get","parameters":[{"name":"profile","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by SFTP profile name","title":"Profile"},"description":"Filter by SFTP profile name"},{"name":"account","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact account_number match","title":"Account"},"description":"Exact account_number match"},{"name":"as_of_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Business date lower bound, ISO YYYY-MM-DD (inclusive)","title":"As Of From"},"description":"Business date lower bound, ISO YYYY-MM-DD (inclusive)"},{"name":"as_of_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Business date upper bound, ISO YYYY-MM-DD (inclusive)","title":"As Of To"},"description":"Business date upper bound, ISO YYYY-MM-DD (inclusive)"},{"name":"group_by","in":"query","required":false,"schema":{"enum":["none","day","month","account","type_code"],"type":"string","description":"Break the rollup down by `day`, `month`, `account`, or `type_code`. `none` returns totals only.","default":"none","title":"Group By"},"description":"Break the rollup down by `day`, `month`, `account`, or `type_code`. `none` returns totals only."}],"responses":{"200":{"description":"Totals over the whole filtered set, plus one entry per group","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Export Summary Api V1 Export Summary Get"},"example":{"filters":{"profile":"huntington_prod","as_of_from":"2026-09-09","as_of_to":"2026-09-09"},"group_by":"account","totals":{"txn_count":5,"external":{"inflow_cents":2481250,"outflow_cents":234120,"net_cents":2247130,"credit_count":1,"debit_count":2},"internal":{"inflow_cents":1500000,"outflow_cents":3897130,"net_cents":-2397130,"credit_count":1,"debit_count":1},"gross":{"inflow_cents":3981250,"outflow_cents":4131250,"net_cents":-150000,"credit_count":2,"debit_count":3},"unclassified":{"txn_count":0,"amount_cents":0}},"groups":[{"key":{"account_number":"09990001234"},"txn_count":4,"external":{"inflow_cents":2481250,"outflow_cents":84120,"net_cents":2397130,"credit_count":1,"debit_count":1},"internal":{"inflow_cents":1500000,"outflow_cents":3897130,"net_cents":-2397130,"credit_count":1,"debit_count":1},"gross":{"inflow_cents":3981250,"outflow_cents":3981250,"net_cents":0,"credit_count":2,"debit_count":2},"unclassified":{"txn_count":0,"amount_cents":0}},{"key":{"account_number":"09990005678"},"txn_count":1,"external":{"inflow_cents":0,"outflow_cents":150000,"net_cents":-150000,"credit_count":0,"debit_count":1},"internal":{"inflow_cents":0,"outflow_cents":0,"net_cents":0,"credit_count":0,"debit_count":0},"gross":{"inflow_cents":0,"outflow_cents":150000,"net_cents":-150000,"credit_count":0,"debit_count":1},"unclassified":{"txn_count":0,"amount_cents":0}}]}}}},"422":{"description":"A date or cursor filter did not parse","content":{"application/json":{"example":{"detail":"as_of_from must be ISO YYYY-MM-DD, got: '9/9/2026'"}}}}}}},"/cbbanking/api/v1/export/summary-codes":{"get":{"tags":["cbbanking","Export"],"summary":"Distinct account summary-item codes observed","description":"Which ``03``-record summary-item codes the feed has ever carried.\n\nThe transaction side of a shape change is already visible through\n``/summary?group_by=type_code``; the balance side was not visible\nanywhere. That mattered on 2026-08-14, when Huntington's enriched sample\nturned out to carry four summary codes we had never seen (140, 205, 450,\n470) and to drop one we consume (076) — a difference no endpoint could\nreport and therefore no watch could catch.\n\nAggregated server-side and keyed only by code, so the response is a dozen\nrows however much history accumulates. ``parse_status='ok'`` rows only.\n\nA null ``label`` means the code is outside ``bai2_codes._TABLE``: we can\nplace it in a direction band by its numeric range but have no spec\nmeaning for it. That is the interesting case — it is a field the bank\nstarted sending that we cannot yet name.","operationId":"export_summary_codes_api_v1_export_summary_codes_get","parameters":[{"name":"profile","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by SFTP profile name","title":"Profile"},"description":"Filter by SFTP profile name"},{"name":"account","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact account_number match","title":"Account"},"description":"Exact account_number match"},{"name":"as_of_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Business date lower bound, ISO YYYY-MM-DD (inclusive)","title":"As Of From"},"description":"Business date lower bound, ISO YYYY-MM-DD (inclusive)"},{"name":"as_of_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Business date upper bound, ISO YYYY-MM-DD (inclusive)","title":"As Of To"},"description":"Business date upper bound, ISO YYYY-MM-DD (inclusive)"}],"responses":{"200":{"description":"Every summary code ever seen; a null label is a new one","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Export Summary Codes Api V1 Export Summary Codes Get"},"example":{"filters":{"profile":"huntington_prod"},"total_codes":11,"codes":[{"type_code":"010","label":"Opening Ledger","direction":"summary","occurrences":264,"first_as_of":"2026-05-11","last_as_of":"2026-09-11"},{"type_code":"015","label":"Closing Ledger","direction":"summary","occurrences":264,"first_as_of":"2026-05-11","last_as_of":"2026-09-11"},{"type_code":"100","label":"Total Credits","direction":"summary","occurrences":264,"first_as_of":"2026-05-11","last_as_of":"2026-09-11"},{"type_code":"400","label":"Total Debits","direction":"summary","occurrences":264,"first_as_of":"2026-05-11","last_as_of":"2026-09-11"},{"type_code":"140","direction":"summary","occurrences":3,"first_as_of":"2026-09-09","last_as_of":"2026-09-11"}]}}}},"422":{"description":"A date or cursor filter did not parse","content":{"application/json":{"example":{"detail":"as_of_from must be ISO YYYY-MM-DD, got: '9/9/2026'"}}}}}}},"/cbbanking/api/v1/export/txns":{"get":{"tags":["cbbanking","Export"],"summary":"Flat per-transaction export feed","description":"One row per BAI2 transaction, filterable and paginated.\n\n## What this returns\n\nEvery transaction record the bank has delivered in a successfully parsed\nfile (`parse_status='ok'` only), newest business date first, joined to\nthe code table so each row carries a human `type_label`, a `direction`\n(`credit` / `debit` / `summary` / `loan` / `informational` / `unknown`)\nand an `is_internal_transfer` flag. Amounts are integer cents. `txn_uid`\nis stable across re-parses and re-sends of the same business day, so\nupsert on it and never on `log_number`.\n\n## Use this when\n\n- Listing what happened on an account or a date range for a person to read.\n- Syncing incrementally into another store: pass the newest `parsed_at`\n  you hold as `since_parsed` and page with `limit` / `offset`.\n- Drilling into a figure `/summary` produced.\n\nDo not sum these rows to answer \"how much came in\". Use `/summary`, which\naggregates the whole filtered set on the server; a page is not a total.\n\n## Reading a row\n\n`direction` says which way money moved. `is_internal_transfer=true` (type\ncodes 206, 277, 351, 506, 651) means it moved between the company's own\naccounts: a real ledger entry, not cash flow. `text` is the bank's\ndescription lines joined with newlines. `as_of_date` is the business date;\nthe file carrying it arrived the following calendar day.\n\n## Example\n\n\"Four transactions on 2026-09-09 for the operating account: a $24,812.50\ncard-settlement deposit, an $841.20 ACH debit, a $15,000.00 book transfer\nin, and a $38,971.30 target sweep out. External net +$23,971.30; the\ntransfer and the sweep are internal and excluded.\"","operationId":"export_txns_api_v1_export_txns_get","parameters":[{"name":"profile","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by SFTP profile name","title":"Profile"},"description":"Filter by SFTP profile name"},{"name":"account","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact account_number match","title":"Account"},"description":"Exact account_number match"},{"name":"as_of_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Business date lower bound, ISO YYYY-MM-DD (inclusive)","title":"As Of From"},"description":"Business date lower bound, ISO YYYY-MM-DD (inclusive)"},{"name":"as_of_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Business date upper bound, ISO YYYY-MM-DD (inclusive)","title":"As Of To"},"description":"Business date upper bound, ISO YYYY-MM-DD (inclusive)"},{"name":"since_parsed","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Incremental cursor; rows with parsed_at strictly greater are returned","title":"Since Parsed"},"description":"Incremental cursor; rows with parsed_at strictly greater are returned"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Page size, 1 to 5000","default":500,"title":"Limit"},"description":"Page size, 1 to 5000"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Rows to skip; page with `total` from the response","default":0,"title":"Offset"},"description":"Rows to skip; page with `total` from the response"}],"responses":{"200":{"description":"One page of transactions, newest business date first","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Export Txns Api V1 Export Txns Get"},"example":{"total":1402,"limit":500,"offset":0,"txns":[{"txn_uid":"h_prod:09990001234:2026-09-09:169:2481250:2599900001:0","profile":"huntington_prod","account_number":"09990001234","as_of_date":"2026-09-09","type_code":"169","type_label":"Bankcard Deposit","direction":"credit","is_internal_transfer":false,"amount_cents":2481250,"currency":"USD","bank_reference":"2599900001","text":"BANKCARD DEPOSIT\nMERCH SVCS","log_number":"13990012345","account_idx":0,"txn_idx":0,"sender_filename":"123456.PSN.BAI2.BAI2PD.20260910041226.txt","parsed_at":"2026-09-10T12:10:41.409594"},{"txn_uid":"h_prod:09990001234:2026-09-09:466:84120:2599900002:0","profile":"huntington_prod","account_number":"09990001234","as_of_date":"2026-09-09","type_code":"466","type_label":"ACH Settlement","direction":"debit","is_internal_transfer":false,"amount_cents":84120,"currency":"USD","bank_reference":"2599900002","text":"ACH SETTLEMENT\nPAYROLL SVC","log_number":"13990012345","account_idx":0,"txn_idx":1,"sender_filename":"123456.PSN.BAI2.BAI2PD.20260910041226.txt","parsed_at":"2026-09-10T12:10:41.409594"},{"txn_uid":"h_prod:09990001234:2026-09-09:206:1500000:6599900003:0","profile":"huntington_prod","account_number":"09990001234","as_of_date":"2026-09-09","type_code":"206","type_label":"Book Transfer Credit","direction":"credit","is_internal_transfer":true,"amount_cents":1500000,"currency":"USD","bank_reference":"6599900003","text":"BOOK TRANSFER CREDIT","log_number":"13990012345","account_idx":0,"txn_idx":2,"sender_filename":"123456.PSN.BAI2.BAI2PD.20260910041226.txt","parsed_at":"2026-09-10T12:10:41.409594"},{"txn_uid":"h_prod:09990001234:2026-09-09:651:3897130:900001234:0","profile":"huntington_prod","account_number":"09990001234","as_of_date":"2026-09-09","type_code":"651","type_label":"Target Sweep Debit","direction":"debit","is_internal_transfer":true,"amount_cents":3897130,"currency":"USD","bank_reference":"900001234","text":"TARGET SWEEP DEBIT","log_number":"13990012345","account_idx":0,"txn_idx":3,"sender_filename":"123456.PSN.BAI2.BAI2PD.20260910041226.txt","parsed_at":"2026-09-10T12:10:41.409594"}]}}}},"422":{"description":"A date or cursor filter did not parse","content":{"application/json":{"example":{"detail":"as_of_from must be ISO YYYY-MM-DD, got: '9/9/2026'"}}}}}}},"/cbbanking/api/v1/inbound/envelopes":{"get":{"tags":["cbbanking","Inbound"],"summary":"List inbound envelopes with pickup + parse state","description":"Every file the bank has delivered, with its collection and parse state.\n\n## What this returns\n\nOne row per envelope (the bank's `log_number`), newest first: the\nOpenText mailbox metadata (`sender_filename`, `application_reference`,\n`mailboxed_at`), what pickup did with it (`archive_path`, `sha256`,\n`source_dir`, `downloaded_at`) and what parsing did (`parsed_at`,\n`parse_status`, `parse_error`). Pickup fields are null for an envelope\nthat was seen but never fetched.\n\n## Use this when\n\n- \"Did today's file arrive?\" Filter `profile=huntington_prod&limit=1` and\n  compare `mailboxed_at` with today.\n- \"Is anything stuck?\" `parse_status=error` lists failures with the\n  parser's message; `parse_status=none` lists collected-but-unparsed.\n- Building a feed-health view: count by `application_reference` and\n  `source_dir`.\n\n## Reading a row\n\n`parse_status` is `ok`, `error`, `unknown-format`, or null (not yet run).\n`application_reference` is the bank's format tag: `BAI2` for the daily\nbalance file, `NACHA_RTRN` for ACH returns. A `source_dir` other than\n`/custom/inbound` means the bank used a second delivery stream. Timestamps\nare UTC; `mailboxed_at` is the bank's clock, the rest are ours.\n\n## Example\n\n\"91 envelopes, 90 parsed ok. The newest, mailboxed 2026-09-12 08:12 UTC,\nis Friday's BAI2 (business date 2026-09-11), collected at 12:05 UTC and\nparsed five minutes later.\"","operationId":"list_envelopes_api_v1_inbound_envelopes_get","parameters":[{"name":"profile","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by SFTP profile name","title":"Profile"},"description":"Filter by SFTP profile name"},{"name":"parse_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by parse_status (ok | error | unknown-format | none)","title":"Parse Status"},"description":"Filter by parse_status (ok | error | unknown-format | none)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Page size, 1 to 1000","default":100,"title":"Limit"},"description":"Page size, 1 to 1000"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Rows to skip; page with `total` from the response","default":0,"title":"Offset"},"description":"Rows to skip; page with `total` from the response"}],"responses":{"200":{"description":"One page of envelopes, newest first","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Envelopes Api V1 Inbound Envelopes Get"},"example":{"total":91,"limit":100,"offset":0,"envelopes":[{"log_number":"13990012345","profile":"huntington_prod","m_filename":"M9A00001","sender_filename":"123456.PSN.BAI2.BAI2PD.20260910041226.txt","sender_address":"HUNTBOT","receiver_address":"HUNTB_EXAMPLE_SFTP","application_reference":"BAI2","data_type":"B","envelope_type":"BIN","service_reference":"011000009999999999","size_bytes":3807,"mailboxed_at":"2026-09-10T08:12:31","observed_at":"2026-09-10T12:05:42.451433","archive_path":"data/archive/huntington_prod/2026/09/13990012345__123456.PSN.BAI2.BAI2PD.20260910041226.txt.bin","sha256":"3f1c9a2e7b8d4c5f6a0b1e2d3c4b5a69788796a5b4c3d2e1f0a9b8c7d6e5f4a3","source_dir":"/custom/inbound","downloaded_at":"2026-09-10T12:05:42.451433","parsed_at":"2026-09-10T12:10:41.409594","parse_status":"ok"}]}}}},"422":{"description":"A query parameter failed validation (FastAPI/Pydantic format)","content":{"application/json":{"example":{"detail":[{"type":"less_than_equal","loc":["query","limit"],"msg":"Input should be less than or equal to 1000","input":"5000"}]}}}}}}},"/cbbanking/api/v1/inbound/envelopes/{log_number}":{"get":{"tags":["cbbanking","Inbound"],"summary":"Full envelope + pickup + parsed detail","description":"Full envelope + pickup + whichever parsed tree the format produced.\n\nExactly one of `parsed_bai2` / `parsed_nacha` is populated for a\nsuccessfully parsed envelope; both are null for one that is unparsed\nor of an unrecognised format. They are separate keys rather than a\nsingle polymorphic `parsed` because the two trees share no shape, and\na caller that handles one must not be handed the other silently.","operationId":"envelope_detail_api_v1_inbound_envelopes__log_number__get","parameters":[{"name":"log_number","in":"path","required":true,"schema":{"type":"string","description":"The bank's message id, as listed by /envelopes","title":"Log Number"},"description":"The bank's message id, as listed by /envelopes"}],"responses":{"200":{"description":"Envelope, pickup, and the parsed tree for its format","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Envelope Detail Api V1 Inbound Envelopes  Log Number  Get"},"example":{"envelope":{"log_number":"13990012345","profile":"huntington_prod","m_filename":"M9A00001","sender_filename":"123456.PSN.BAI2.BAI2PD.20260910041226.txt","sender_address":"HUNTBOT","receiver_address":"HUNTB_EXAMPLE_SFTP","application_reference":"BAI2","data_type":"B","envelope_type":"BIN","service_reference":"011000009999999999","size_bytes":3807,"mailboxed_at":"2026-09-10T08:12:31","observed_at":"2026-09-10T12:05:42.451433"},"pickup":{"archive_path":"data/archive/huntington_prod/2026/09/13990012345__123456.PSN.BAI2.BAI2PD.20260910041226.txt.bin","sha256":"3f1c9a2e7b8d4c5f6a0b1e2d3c4b5a69788796a5b4c3d2e1f0a9b8c7d6e5f4a3","source_dir":"/custom/inbound","downloaded_at":"2026-09-10T12:05:42.451433","parsed_at":"2026-09-10T12:10:41.409594","parse_status":"ok"},"parsed_bai2":{"file":{"sender_id":"072000000","receiver_id":"123456","file_creation_date":"260910","file_creation_time":"0412","file_id_modifier":"1","record_length":80,"version":"2","control_total":15962500,"group_count":1,"record_count":28,"parsed_at":"2026-09-10T12:10:41.409594"},"accounts":[{"account_idx":0,"account_number":"09990001234","currency":"USD","as_of_date":"260909","summary_items":[{"type_code":"010","amount":2500000},{"type_code":"015","amount":2500000},{"type_code":"100","amount":3981250},{"type_code":"400","amount":3981250}],"control_total":15962500,"record_count":8}],"transactions":[{"account_idx":0,"txn_idx":0,"type_code":"169","amount":2481250,"bank_reference":"2599900001","text":"BANKCARD DEPOSIT\nMERCH SVCS"},{"account_idx":0,"txn_idx":1,"type_code":"651","amount":3897130,"bank_reference":"900001234","text":"TARGET SWEEP DEBIT"}]}}}}},"404":{"description":"No envelope with that log_number","content":{"application/json":{"example":{"detail":"unknown log_number: 13990099999"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbanking__HTTPValidationError"}}}}}}},"/cbbanking/api/v1/inbound/envelopes/{log_number}/download":{"get":{"tags":["cbbanking","Inbound"],"summary":"Download the raw archived bytes","description":"Stream the raw archive bytes captured at pickup time.\n\n404 if the log_number is unknown; 410 if the DB row exists but the\narchive file is missing on disk (operator action required —\nsomething deleted bytes we promised to keep).","operationId":"envelope_download_api_v1_inbound_envelopes__log_number__download_get","parameters":[{"name":"log_number","in":"path","required":true,"schema":{"type":"string","description":"The bank's message id, as listed by /envelopes","title":"Log Number"},"description":"The bank's message id, as listed by /envelopes"}],"responses":{"200":{"description":"The archived file byte for byte, served with the bank's original filename","content":{"application/json":{"schema":{}},"application/octet-stream":{"example":"01,072000000,123456,260910,0412,1,80,2/\n02,123456,072000000,1,260909,,USD,2/\n03,09990001234,USD,010,2500000,,/\n88,015,2500000,,/\n16,169,2481250,,2599900001,,BANKCARD DEPOSIT/\n88,MERCH SVCS/\n16,651,3897130,,900001234,,TARGET SWEEP DEBIT/\n49,15962500,8/\n98,15962500,1,10/\n99,15962500,1,12/\n"}}},"404":{"description":"No envelope with that log_number","content":{"application/json":{"example":{"detail":"unknown log_number: 13990099999"}}}},"410":{"description":"The row exists but the archived bytes are gone from disk; operator action required","content":{"application/json":{"example":{"detail":"archive file missing on disk: data/archive/huntington_prod/2026/09/13990012345__123456.PSN.BAI2.BAI2PD.20260910041226.txt.bin"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbanking__HTTPValidationError"}}}}}}},"/cbbanking/api/v1/parsing/run":{"post":{"tags":["cbbanking","Parsing"],"summary":"Parse downloaded-but-unparsed envelopes","description":"Run the post-pickup parse dispatcher.\n\nWalks ``inbound_pickup`` rows where ``parsed_at IS NULL``, dispatches\neach to the right parser based on the envelope's\n``application_reference``, persists parsed records, and stamps\n``parse_status`` (``ok`` / ``unknown-format`` / ``error``).\n\nWith no arguments this only ever moves rows *forward* from the pending\nstate, which is what the nightly cron wants. `reparse` and\n`log_number` additionally clear parse state first, so already-stamped\nenvelopes are replayed from their archived bytes — the path for\npicking up files captured before their parser existed. Both are\nidempotent; persisting a parse replaces that envelope's rows.","operationId":"run_parsing_api_v1_parsing_run_post","parameters":[{"name":"reparse","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Before running, requeue every envelope currently at this parse_status (ok | error | unknown-format). Use after shipping a parser for a format that was previously unknown-format.","title":"Reparse"},"description":"Before running, requeue every envelope currently at this parse_status (ok | error | unknown-format). Use after shipping a parser for a format that was previously unknown-format."},{"name":"log_number","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Before running, requeue just this envelope.","title":"Log Number"},"description":"Before running, requeue just this envelope."}],"responses":{"200":{"description":"Run finished. `errors` lists per-envelope failures, which do not fail the run","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Run Parsing Api V1 Parsing Run Post"},"example":{"requeued":0,"seen":1,"parsed":1,"success":1,"unknown":0,"error":0,"errors":[]}}}},"422":{"description":"`reparse` names a status that cannot be requeued, or neither argument was usable","content":{"application/json":{"example":{"detail":"status must be one of ('ok', 'error', 'unknown-format'), got 'pending'"}}}},"502":{"description":"The parse run itself failed. Per-envelope failures do not do this: they come back as a 200 with entries in `errors` and `parse_status='error'` on the envelope","content":{"application/json":{"example":{"detail":"parsing error: Conflicting lock is held in another process"}}}}}}},"/cbbanking/api/v1/sftp/profiles":{"get":{"tags":["cbbanking","SFTP"],"summary":"List configured SFTP profiles + health state","description":"List every configured SFTP endpoint with its connection metadata.\n\nReturns one entry per known profile (`huntington_prep`, `huntington_prod`)\nwith host / port / username / inbound directories and a `configured`\nboolean that is true only when host, username, and a credential (password\nor key path) are all present. Secrets themselves are never returned.\n\n`inbound_dirs` lists every directory pickup searches, primary first;\n`inbound_dir` remains the primary alone.","operationId":"list_profiles_api_v1_sftp_profiles_get","responses":{"200":{"description":"One entry per profile; secrets are never included","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Profiles Api V1 Sftp Profiles Get"},"example":{"profiles":{"huntington_prep":{"host":"huntington-sftpbeta.opentext.cloud","port":22,"username":"AUZ00000","configured":true,"inbound_dir":"/custom/inbound","inbound_dirs":["/custom/inbound","/custom/INFO_RPTG"]},"huntington_prod":{"host":"huntington-sftp.opentext.cloud","port":22,"username":"ABI00000","configured":true,"inbound_dir":"/custom/inbound","inbound_dirs":["/custom/inbound","/custom/INFO_RPTG"]}}}}}}}}},"/cbbanking/api/v1/sftp/{profile}/inbound":{"get":{"tags":["cbbanking","SFTP"],"summary":"List files waiting in the inbound dir","description":"List the files currently sitting in `profile`'s inbound directories.\n\nRead-only directory listing on the remote SFTP server — name, size, mtime,\nand source directory per file, across every directory pickup searches.\nDownloads nothing; use the pickup endpoint to capture them. Returns 400 on\na configuration / auth failure and 502 on other errors.","operationId":"list_inbound_api_v1_sftp__profile__inbound_get","parameters":[{"name":"profile","in":"path","required":true,"schema":{"enum":["huntington_prep","huntington_prod"],"type":"string","description":"SFTP profile to list","title":"Profile"},"description":"SFTP profile to list"}],"responses":{"200":{"description":"Files waiting upstream, across every directory pickup searches","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Inbound Api V1 Sftp  Profile  Inbound Get"},"example":{"profile":"huntington_prod","inbound_dir":"/custom/inbound","inbound_dirs":["/custom/inbound","/custom/INFO_RPTG"],"count":1,"count_by_dir":{"/custom/inbound":1,"/custom/INFO_RPTG":0},"files":[{"name":"123456.PSN.BAI2.BAI2PD.20260910041226.txt","size":3807,"mtime":1789027951.0,"dir":"/custom/inbound"}]}}}},"400":{"description":"The profile has no credentials, or the bank rejected the login","content":{"application/json":{"example":{"detail":"SFTP profile missing credentials (password or key_path)"}}}},"422":{"description":"Unknown profile name; only `huntington_prep` and `huntington_prod` exist","content":{"application/json":{"example":{"detail":[{"type":"literal_error","loc":["path","profile"],"msg":"Input should be 'huntington_prep' or 'huntington_prod'","input":"chase_prod"}]}}}},"502":{"description":"Transport failure talking to the bank (network, host key, remote error)","content":{"application/json":{"example":{"detail":"sftp error: [Errno 110] Connection timed out"}}}}}}},"/cbbanking/api/v1/sftp/{profile}/pickup":{"post":{"tags":["cbbanking","SFTP"],"summary":"Download new envelopes into archive + DuckDB","description":"Run a mailbox-first pickup for `profile`.\n\nFor each pending message not already in `inbound_envelope`, downloads\nthe body, writes it to the archive root, records sha256, and inserts\nenvelope + pickup rows in DuckDB. Idempotent on `log_number`.\n\nDiscovery is mailbox-wide, so a message routed to any provisioned receive\ndirectory is picked up. `by_dir` reports where each file was fetched from\nand `new_stream_files` counts those taken from anywhere but the primary —\nnon-zero means the bank has started delivering on a second stream.","operationId":"pickup_api_v1_sftp__profile__pickup_post","parameters":[{"name":"profile","in":"path","required":true,"schema":{"enum":["huntington_prep","huntington_prod"],"type":"string","description":"SFTP profile to pick up","title":"Profile"},"description":"SFTP profile to pick up"}],"responses":{"200":{"description":"Pickup ran. `errors` is per-file and can be non-empty on 200","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Pickup Api V1 Sftp  Profile  Pickup Post"},"example":{"profile":"huntington_prod","seen":1,"new":1,"downloaded":1,"errors":[],"by_dir":{"/custom/inbound":1},"new_stream_files":0}}}},"400":{"description":"The profile has no credentials, or the bank rejected the login","content":{"application/json":{"example":{"detail":"SFTP profile missing credentials (password or key_path)"}}}},"422":{"description":"Unknown profile name; only `huntington_prep` and `huntington_prod` exist","content":{"application/json":{"example":{"detail":[{"type":"literal_error","loc":["path","profile"],"msg":"Input should be 'huntington_prep' or 'huntington_prod'","input":"chase_prod"}]}}}},"502":{"description":"Transport failure talking to the bank (network, host key, remote error)","content":{"application/json":{"example":{"detail":"sftp error: [Errno 110] Connection timed out"}}}}}}},"/cbbanking/api/v1/sftp/{profile}/ping":{"post":{"tags":["cbbanking","SFTP"],"summary":"Connect + stat the inbound dir (no transfer)","description":"Open an SFTP connection for `profile` and stat its inbound directory.\n\nA lightweight liveness check — it authenticates and confirms the inbound\npath is reachable but transfers no files. Returns 400 on a configuration /\nauth failure and 502 on any other transport error.","operationId":"ping_api_v1_sftp__profile__ping_post","parameters":[{"name":"profile","in":"path","required":true,"schema":{"enum":["huntington_prep","huntington_prod"],"type":"string","description":"SFTP profile to test","title":"Profile"},"description":"SFTP profile to test"}],"responses":{"200":{"description":"Connected and authenticated; per-directory detail in `dirs`","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Ping Api V1 Sftp  Profile  Ping Post"},"example":{"profile":"huntington_prod","host":"huntington-sftp.opentext.cloud","inbound_dir":"/custom/inbound","inbound_mode":16877,"reachable":true,"dirs":[{"dir":"/custom/inbound","reachable":true,"mode":16877},{"dir":"/custom/INFO_RPTG","reachable":true,"mode":16877}]}}}},"400":{"description":"The profile has no credentials, or the bank rejected the login","content":{"application/json":{"example":{"detail":"SFTP profile missing credentials (password or key_path)"}}}},"422":{"description":"Unknown profile name; only `huntington_prep` and `huntington_prod` exist","content":{"application/json":{"example":{"detail":[{"type":"literal_error","loc":["path","profile"],"msg":"Input should be 'huntington_prep' or 'huntington_prod'","input":"chase_prod"}]}}}},"502":{"description":"Transport failure talking to the bank (network, host key, remote error)","content":{"application/json":{"example":{"detail":"sftp error: [Errno 110] Connection timed out"}}}}}}},"/cbbanking/health":{"get":{"tags":["cbbanking","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbanking__HealthResponse"}}}}}}},"/cbbanking/health/detailed":{"get":{"tags":["cbbanking","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbanking__HealthResponse"}}}}}}},"/cbbanking/prime":{"get":{"tags":["cbbanking","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbanking__PrimeResponse"}}}}}}},"/cbbanking/status":{"get":{"tags":["cbbanking","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbanking__StatusResponse"}}}}}}},"/cbbilling/api/v1/bundles":{"get":{"tags":["cbbilling","Catalog"],"summary":"List the canonical App-Bundle catalog","description":"Canonical bundles (cbloom cards/<id>/bundle.json).","operationId":"bundles_api_v1_bundles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Bundles Api V1 Bundles Get"}}}}}}},"/cbbilling/api/v1/grants":{"get":{"tags":["cbbilling","Catalog"],"summary":"Show the tenant → bundle grant ledger","operationId":"grants_api_v1_grants_get","parameters":[{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Grants Api V1 Grants Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HTTPValidationError"}}}}}},"post":{"tags":["cbbilling","Catalog"],"summary":"Grant an App Bundle to a tenant","operationId":"grant_api_v1_grants_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__GrantRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Grant Api V1 Grants Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HTTPValidationError"}}}}}}},"/cbbilling/api/v1/grants/{tenant}/{bundle}":{"delete":{"tags":["cbbilling","Catalog"],"summary":"Revoke a granted App Bundle","operationId":"revoke_api_v1_grants__tenant___bundle__delete","parameters":[{"name":"tenant","in":"path","required":true,"schema":{"type":"string","title":"Tenant"}},{"name":"bundle","in":"path","required":true,"schema":{"type":"string","title":"Bundle"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Revoke Api V1 Grants  Tenant   Bundle  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HTTPValidationError"}}}}}}},"/cbbilling/api/v1/invoices":{"post":{"tags":["cbbilling","Invoices"],"summary":"Generate + persist an invoice","operationId":"generate_api_v1_invoices_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__GenerateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Generate Api V1 Invoices Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HTTPValidationError"}}}}}},"get":{"tags":["cbbilling","Invoices"],"summary":"List persisted invoices","operationId":"list_invoices_api_v1_invoices_get","parameters":[{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"}},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Invoices Api V1 Invoices Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HTTPValidationError"}}}}}}},"/cbbilling/api/v1/invoices/preview":{"post":{"tags":["cbbilling","Invoices"],"summary":"Build an invoice without persisting","operationId":"preview_api_v1_invoices_preview_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__GenerateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Preview Api V1 Invoices Preview Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HTTPValidationError"}}}}}}},"/cbbilling/api/v1/invoices/{invoice_id}":{"get":{"tags":["cbbilling","Invoices"],"summary":"Get a persisted invoice","operationId":"get_invoice_api_v1_invoices__invoice_id__get","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"integer","title":"Invoice Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Invoice Api V1 Invoices  Invoice Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HTTPValidationError"}}}}}},"delete":{"tags":["cbbilling","Invoices"],"summary":"Soft-delete an invoice","operationId":"delete_invoice_api_v1_invoices__invoice_id__delete","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"integer","title":"Invoice Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HTTPValidationError"}}}}}}},"/cbbilling/api/v1/ledger":{"get":{"tags":["cbbilling","Invoices","Ledger"],"summary":"List ledger entries","operationId":"ledger_list_api_v1_ledger_get","parameters":[{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"}},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Ledger List Api V1 Ledger Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HTTPValidationError"}}}}}}},"/cbbilling/api/v1/ledger/run":{"post":{"tags":["cbbilling","Invoices","Ledger"],"summary":"Generate + persist invoices for every tenant","description":"Snapshot every known tenant into the ledger for a billing month — the\nnightly daily-aggregation job + the manual test trigger. Best-effort per\ntenant: one failure never aborts the run.\n\nBody is optional (the cbcron dispatch fires with an empty body) → defaults\nto the latest period from cbdatabase.","operationId":"ledger_run_api_v1_ledger_run_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbbilling__RunRequest"},{"type":"null"}],"title":"Req"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Ledger Run Api V1 Ledger Run Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HTTPValidationError"}}}}}}},"/cbbilling/api/v1/ledger/summary":{"get":{"tags":["cbbilling","Invoices","Ledger"],"summary":"Rolled-up invoice ledger","description":"Grand totals + aggregations by generation-day, billing-period, tenant.","operationId":"ledger_summary_api_v1_ledger_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Ledger Summary Api V1 Ledger Summary Get"}}}}}}},"/cbbilling/api/v1/ratecard":{"get":{"tags":["cbbilling","Catalog","Catalog"],"summary":"Get the rate card","description":"The illustrative pricing single-source-of-truth (ratecard.yaml).","operationId":"ratecard_api_v1_ratecard_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Ratecard Api V1 Ratecard Get"}}}}}}},"/cbbilling/api/v1/tenants":{"get":{"tags":["cbbilling","Catalog"],"summary":"List known tenants","operationId":"tenants_api_v1_tenants_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Tenants Api V1 Tenants Get"}}}}}}},"/cbbilling/health":{"get":{"tags":["cbbilling","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HealthResponse"}}}}}}},"/cbbilling/health/detailed":{"get":{"tags":["cbbilling","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__HealthResponse"}}}}}}},"/cbbilling/prime":{"get":{"tags":["cbbilling","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__PrimeResponse"}}}}}}},"/cbbilling/status":{"get":{"tags":["cbbilling","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbbilling__StatusResponse"}}}}}}},"/cbcalendar/api/v1/audit":{"get":{"tags":["cbcalendar","Audit"],"summary":"List audit events for the tenant","description":"**Use these endpoints when:** a downstream consumer needs to mirror calendar events into its own audit log, or when investigating a specific record. Filter by ``event_type`` (``event_create``, ``event_update``, ``event_cancel``, ``connection_created``, ``connection_revoked``, ``mode_changed``), ``source_system``, or ``source_uri_prefix``.\n\n**Response:** a list of audit-event payloads (the discriminated union variants). Order is newest-first by ``sequence_number``.","operationId":"list_audit_events_api_v1_audit_get","parameters":[{"name":"event_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Type"}},{"name":"source_system","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source System"}},{"name":"source_uri_prefix","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Uri Prefix"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Audit Events Api V1 Audit Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/calendars":{"post":{"tags":["cbcalendar","Calendars"],"summary":"Create or replace a calendar","description":"Upsert a calendar by id within a tenant, replacing the record if the id already exists.","operationId":"upsert_calendar_api_v1_calendars_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__Calendar"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__Calendar"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}},"get":{"tags":["cbcalendar","Calendars"],"summary":"List calendars for the tenant","description":"**Use these endpoints when:** a consumer (cbgreenroom, CBPulse) needs to discover the calendar to target. Filter by ``user_id`` to scope to a particular principal's calendars, or ``connection_id`` to scope to a single connection.","operationId":"list_calendars_api_v1_calendars_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"User Id"}},{"name":"connection_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Connection Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbcalendar__Calendar"},"title":"Response List Calendars Api V1 Calendars Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/calendars/{calendar_id}":{"get":{"tags":["cbcalendar","Calendars"],"summary":"Get a calendar by id","description":"Fetch a single calendar by its UUID; 404s if no such calendar exists.","operationId":"get_calendar_api_v1_calendars__calendar_id__get","parameters":[{"name":"calendar_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Calendar Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__Calendar"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/connections":{"post":{"tags":["cbcalendar","Connections"],"summary":"Create or replace a connection","description":"Phase 0: direct upsert. Phase 1 introduces provider-specific OAuth flows; that route will not require token fields in the request body.","operationId":"upsert_connection_api_v1_connections_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__CalendarConnection"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__CalendarConnection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}},"get":{"tags":["cbcalendar","Connections"],"summary":"List connections for the tenant","description":"List the tenant's calendar-provider connections, optionally filtered by user_id and/or provider.","operationId":"list_connections_api_v1_connections_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"User Id"}},{"name":"provider","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbcalendar__CalendarProvider"},{"type":"null"}],"title":"Provider"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbcalendar__CalendarConnection"},"title":"Response List Connections Api V1 Connections Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/connections/google/register":{"post":{"tags":["cbcalendar","Connections"],"summary":"Register a Google Workspace connection (SA + DWD)","description":"**Use these endpoints when:** onboarding a Workspace user whose calendar should be writable from cbcalendar. The body carries a Google service-account JSON (generated per ``docs/GOOGLE.md`` §6.1) plus the email of the user the SA should impersonate via Domain-Wide Delegation. The SA JSON is Fernet-encrypted before persistence and is never logged.\n\nOn success, returns the persisted ``CalendarConnection`` with ``provider=google`` and ``status=active``. The ``service_account_json`` field on the response is the *encrypted envelope*, not the plaintext.","operationId":"register_google_connection_api_v1_connections_google_register_post","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"X-CB-Source-Service","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source-Service"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__GoogleConnectionRegistration"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__CalendarConnection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/connections/{connection_id}":{"get":{"tags":["cbcalendar","Connections"],"summary":"Get a connection by id","description":"Fetch a single calendar-provider connection by its UUID; 404s if no such connection exists.","operationId":"get_connection_api_v1_connections__connection_id__get","parameters":[{"name":"connection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Connection Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__CalendarConnection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}},"delete":{"tags":["cbcalendar","Connections"],"summary":"Revoke a connection","description":"Marks the connection ``status=revoked`` and clears stored tokens. For real providers (Phase 1+) this also calls the provider's revoke endpoint. Phase 0 (Mock) is a database-only flip.","operationId":"revoke_connection_api_v1_connections__connection_id__delete","parameters":[{"name":"connection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Connection Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__CalendarConnection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/events":{"post":{"tags":["cbcalendar","Events"],"summary":"Create a calendar event","description":"**Use these endpoints when:** booking a hit, briefing, or any appearance that needs to land on a calendar. The draft must include ``calendar_id``, ``title``, ``start_at``, ``end_at``, ``source_system``, and ``source_uri`` so the event traces back to the originating record.\n\n**Demo / live behavior:** routing is automatic. If the service is in demo mode OR the tenant has ``outbound_locked=true``, the call is suppressed to MockProvider — the response shape is identical either way; only the ``mode`` field on the response distinguishes.","operationId":"create_event_api_v1_events_post","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"X-CB-Source-Service","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source-Service"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__EventDraft"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__Event"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}},"get":{"tags":["cbcalendar","Events"],"summary":"List events with filters","description":"List events for the tenant, optionally filtered by calendar, source system/uri prefix, and time range, capped by limit.","operationId":"list_events_api_v1_events_get","parameters":[{"name":"calendar_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Calendar Id"}},{"name":"source_system","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source System"}},{"name":"source_uri_prefix","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Uri Prefix"}},{"name":"range_start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Range Start"}},{"name":"range_end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Range End"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbcalendar__Event"},"title":"Response List Events Api V1 Events Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/events/{event_id}":{"get":{"tags":["cbcalendar","Events"],"summary":"Get an event by id","description":"Fetch a single event by its UUID, scoped to the requesting tenant; 404s if missing or cross-tenant.","operationId":"get_event_api_v1_events__event_id__get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__Event"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}},"patch":{"tags":["cbcalendar","Events"],"summary":"Update an event","description":"Patch fields on an existing event. Provider patch is performed first; service-layer merge applies the result to the persisted row. Demo/live routing follows the same rules as create.","operationId":"update_event_api_v1_events__event_id__patch","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"X-CB-Source-Service","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source-Service"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__EventPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__Event"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}},"delete":{"tags":["cbcalendar","Events"],"summary":"Cancel an event","description":"Marks the event ``status=cancelled`` and records the optional reason. Persisted on the cbcalendar side regardless of demo/live.","operationId":"cancel_event_api_v1_events__event_id__delete","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}},{"name":"cancellation_reason","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Cancellation Reason"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"X-CB-Source-Service","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source-Service"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__Event"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/mode":{"get":{"tags":["cbcalendar","Mode"],"summary":"Get service mode","description":"Return the service-wide demo/live mode flag that gates real calendar-provider calls.","operationId":"get_mode_api_v1_mode_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__ServiceMode"}}}}}}},"/cbcalendar/api/v1/tenants":{"get":{"tags":["cbcalendar","Tenants"],"summary":"List tenants","description":"List every tenant known to cbcalendar, including each one's demo/live mode and outbound lock.","operationId":"list_tenants_api_v1_tenants_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbcalendar__Tenant"},"type":"array","title":"Response List Tenants Api V1 Tenants Get"}}}}}},"post":{"tags":["cbcalendar","Tenants"],"summary":"Create or replace a tenant","description":"Upsert a tenant by slug id. New tenants default to ``mode=demo`` and ``outbound_locked=true``; promotion to live requires the dedicated ``/tenants/{id}/mode/promote_to_live`` endpoint with evidence.\n\n**Use these endpoints when:** seeding a demo tenant, or replicating a tenant record from cbtenant during onboarding.","operationId":"upsert_tenant_api_v1_tenants_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__Tenant"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/tenants/{tenant_id}":{"get":{"tags":["cbcalendar","Tenants"],"summary":"Get a tenant by slug id","description":"Fetch a single tenant by its slug id; 404s if the slug is not registered.","operationId":"get_tenant_api_v1_tenants__tenant_id__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/tenants/{tenant_id}/mode":{"get":{"tags":["cbcalendar","Mode"],"summary":"Get effective mode for a tenant","description":"**Use these endpoints when:** a consumer wants to display whether calendar invites are actually being sent for a given tenant. The ``outbound_allowed`` field is the load-bearing signal — the others are inputs to that decision.","operationId":"get_tenant_mode_api_v1_tenants__tenant_id__mode_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__TenantModeView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/tenants/{tenant_id}/mode/promote_to_live":{"post":{"tags":["cbcalendar","Mode"],"summary":"Promote a tenant from demo to live","description":"Idempotent if the tenant is already live. Emits a ``mode_changed`` audit event with the supplied evidence. Permission gating (``cbcalendar.tenant.{id}.promote_mode``) lands in Phase 1.","operationId":"promote_to_live_api_v1_tenants__tenant_id__mode_promote_to_live_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-CB-Source-Service","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source-Service"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__PromoteToLiveBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__TenantModeView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/users":{"post":{"tags":["cbcalendar","Users"],"summary":"Create or replace a user","description":"Upsert a calendar user by id, binding them to a tenant; replaces the record if the id already exists.","operationId":"upsert_user_api_v1_users_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__User"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__User"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}},"get":{"tags":["cbcalendar","Users"],"summary":"List users in the tenant scope","description":"List all calendar users belonging to the tenant resolved from the request scope.","operationId":"list_users_api_v1_users_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbcalendar__User"},"title":"Response List Users Api V1 Users Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/api/v1/users/{user_id}":{"get":{"tags":["cbcalendar","Users"],"summary":"Get a user by id","description":"Fetch a single calendar user by their UUID; 404s if no such user exists.","operationId":"get_user_api_v1_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__User"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HTTPValidationError"}}}}}}},"/cbcalendar/health":{"get":{"tags":["cbcalendar","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HealthResponse"}}}}}}},"/cbcalendar/health/detailed":{"get":{"tags":["cbcalendar","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__HealthResponse"}}}}}}},"/cbcalendar/prime":{"get":{"tags":["cbcalendar","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__PrimeResponse"}}}}}}},"/cbcalendar/status":{"get":{"tags":["cbcalendar","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcalendar__StatusResponse"}}}}}}},"/cbcampaign/api/v1/hello":{"get":{"tags":["cbcampaign","CBCAMPAIGN"],"summary":"Hello world","description":"Return a greeting.","operationId":"hello_api_v1_hello_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbcampaign/health":{"get":{"tags":["cbcampaign","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcampaign__HealthResponse"}}}}}}},"/cbcampaign/health/detailed":{"get":{"tags":["cbcampaign","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcampaign__HealthResponse"}}}}}}},"/cbcampaign/prime":{"get":{"tags":["cbcampaign","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcampaign__PrimeResponse"}}}}}}},"/cbcampaign/status":{"get":{"tags":["cbcampaign","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcampaign__StatusResponse"}}}}}}},"/cbcaptive/api/portal/{property_key}/connect":{"post":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"Contact-info connect","operationId":"portal_connect_api_portal__property_key__connect_post","parameters":[{"name":"property_key","in":"path","required":true,"schema":{"type":"string","title":"Property Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__ConnectBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HTTPValidationError"}}}}}}},"/cbcaptive/api/portal/{property_key}/voucher":{"post":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"Voucher-code connect","operationId":"portal_voucher_api_portal__property_key__voucher_post","parameters":[{"name":"property_key","in":"path","required":true,"schema":{"type":"string","title":"Property Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__VoucherBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HTTPValidationError"}}}}}}},"/cbcaptive/api/v1/activity":{"get":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"Recent authorizations (activity log)","operationId":"activity_api_v1_activity_get","parameters":[{"name":"property","in":"query","required":false,"schema":{"type":"string","default":"maho","title":"Property"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HTTPValidationError"}}}}}}},"/cbcaptive/api/v1/config":{"get":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"Non-secret wiring status","operationId":"config_api_v1_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbcaptive/api/v1/guests":{"get":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"List captured guests (PII)","operationId":"guests_list_api_v1_guests_get","parameters":[{"name":"property","in":"query","required":false,"schema":{"type":"string","default":"maho","title":"Property"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HTTPValidationError"}}}}}}},"/cbcaptive/api/v1/guests/export.csv":{"get":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"CSV export of captured guests (PII)","operationId":"guests_export_api_v1_guests_export_csv_get","parameters":[{"name":"property","in":"query","required":false,"schema":{"type":"string","default":"maho","title":"Property"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HTTPValidationError"}}}}}}},"/cbcaptive/api/v1/stats":{"get":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"Guest-funnel stats","operationId":"stats_api_v1_stats_get","parameters":[{"name":"property","in":"query","required":false,"schema":{"type":"string","default":"maho","title":"Property"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HTTPValidationError"}}}}}}},"/cbcaptive/api/v1/vouchers/redemptions":{"get":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"Per-code voucher redemption counts (external-portal truth)","description":"{code: count}. Not PII, but rides the same admin gate — the only caller\nis cbloom's guest-wifi desk (svc:cbloom holds cbcaptive.guests.read), which\nmerges this over the console's frozen authorizedGuestCount.","operationId":"voucher_redemptions_api_v1_vouchers_redemptions_get","parameters":[{"name":"property","in":"query","required":false,"schema":{"type":"string","default":"maho","title":"Property"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HTTPValidationError"}}}}}}},"/cbcaptive/guest/s/{site_name}/":{"get":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"UniFi external-portal redirect target","description":"The path UniFi actually redirects guests to: /guest/s/<site-name>/?id=…\nMap the console site name to our property and serve the same portal page.\n(The /p/{property} route stays the canonical, human-shareable form.)","operationId":"unifi_redirect_landing_guest_s__site_name___get","parameters":[{"name":"site_name","in":"path","required":true,"schema":{"type":"string","title":"Site Name"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HTTPValidationError"}}}}}}},"/cbcaptive/health":{"get":{"tags":["cbcaptive","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HealthResponse"}}}}}}},"/cbcaptive/health/detailed":{"get":{"tags":["cbcaptive","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HealthResponse"}}}}}}},"/cbcaptive/p/{property_key}":{"get":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"Captive portal page","description":"UniFi external-portal redirect lands here with ?id=<mac>&ap=&ssid=&t=&url=.\nParam names are the task-0 verify item; ``id`` is the client MAC in every\nknown firmware. Off-property opens (no MAC) still render, with a join-first\nnotice instead of a working form.","operationId":"portal_page_p__property_key__get","parameters":[{"name":"property_key","in":"path","required":true,"schema":{"type":"string","title":"Property Key"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HTTPValidationError"}}}}}}},"/cbcaptive/p/{property_key}/logo.png":{"get":{"tags":["cbcaptive","CBCAPTIVE"],"summary":"Property logo asset","operationId":"portal_logo_p__property_key__logo_png_get","parameters":[{"name":"property_key","in":"path","required":true,"schema":{"type":"string","title":"Property Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__HTTPValidationError"}}}}}}},"/cbcaptive/prime":{"get":{"tags":["cbcaptive","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__PrimeResponse"}}}}}}},"/cbcaptive/status":{"get":{"tags":["cbcaptive","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcaptive__StatusResponse"}}}}}}},"/cbcast/api/v1/channels":{"get":{"tags":["cbcast","CBCAST"],"summary":"List channels","description":"List every channel with its mode, current scene, playlist, metadata, owning tenant, and the ids of bound devices.","operationId":"list_channels_api_v1_channels_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Channels Api V1 Channels Get"}}}}}},"post":{"tags":["cbcast","CBCAST"],"summary":"Create a channel","description":"Create a channel (idempotent by id — an existing channel is returned unchanged). The channel is tagged with the caller's tenant on first create. Returns the channel detail.","operationId":"create_channel_api_v1_channels_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ChannelBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Channel Api V1 Channels Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/channels/_self/kiosk-token":{"post":{"tags":["cbcast","CBCAST"],"summary":"Exchange a device token for a cbauth kiosk token (Enhanced URL channels)","description":"Vouch for the authed box and mint it a short-lived, app-scoped cbauth\nkiosk token (so cog can load a gated Loom app). cbcast verifies the device;\ncbauth mints + scope-clamps. Gate-friendly path (/channels/*). 503 if kiosk\nauth isn't configured (no service key). See docs/web-channel-design.md.","operationId":"device_kiosk_token_api_v1_channels__self_kiosk_token_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Device Kiosk Token Api V1 Channels  Self Kiosk Token Post"}}}}}}},"/cbcast/api/v1/channels/_self/wifi":{"get":{"tags":["cbcast","CBCAST"],"summary":"The calling box's desired WiFi list (device-token authed)","description":"The box polls this with its device token and reconciles its NetworkManager connections whenever `rev` changes; an `apply-wifi` command forces it. Uses the same gate-friendly /channels/_self/* path as /current and /kiosk-token.","operationId":"self_wifi_api_v1_channels__self_wifi_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Self Wifi Api V1 Channels  Self Wifi Get"}}}}}}},"/cbcast/api/v1/channels/stale":{"get":{"tags":["cbcast","CBCAST"],"summary":"List stale channels (retire review)","description":"Channels no writer or operator has touched in `days` (default from settings.channel_stale_days): no pushed playlist/scene, no device bind. A channel is never stale while a device is bound to it, a program slot references it, or it is the default channel. This is the review half of the retire flow — stale channels pin their playlist scenes against scene GC forever (cbcast#27), so the graveyard re-forms unless they retire. Returns each candidate with its scene count and last activity.","operationId":"list_stale_channels_api_v1_channels_stale_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Stale Channels Api V1 Channels Stale Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"delete":{"tags":["cbcast","CBCAST"],"summary":"Retire every stale channel (bulk)","description":"Delete every channel the stale review (GET /channels/stale) lists at the same `days` window — the apply half of the retire flow. Uses the same guards (bound devices, program slots, and the default channel are never touched), so it cannot dark a screen. Deleting releases the channels' playlist scenes to scene GC / the daily archive. Returns the retired ids; idempotent — a second call retires nothing.","operationId":"retire_stale_channels_api_v1_channels_stale_delete","parameters":[{"name":"days","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Retire Stale Channels Api V1 Channels Stale Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/channels/{channel_id}":{"get":{"tags":["cbcast","CBCAST"],"summary":"Channel detail","description":"Return one channel's full state + the ids of bound devices. 404 if unknown.","operationId":"get_channel_api_v1_channels__channel_id__get","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Channel Api V1 Channels  Channel Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"delete":{"tags":["cbcast","CBCAST"],"summary":"Delete a channel","description":"Delete a channel and unbind any devices pointing at it (they fall back to standby until re-bound). The default channel is re-created on next service load. 404 if unknown.","operationId":"delete_channel_api_v1_channels__channel_id__delete","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Channel Api V1 Channels  Channel Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"patch":{"tags":["cbcast","CBCAST"],"summary":"Edit channel metadata","description":"Patch display_name / hidden / channel_number. Only fields present in the\nbody are touched. channel_number must be unique (409 on collision).","operationId":"update_channel_api_v1_channels__channel_id__patch","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ChannelMetaBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Channel Api V1 Channels  Channel Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/channels/{channel_id}/current":{"put":{"tags":["cbcast","CBCAST"],"summary":"Set a channel's scene (manual)","description":"Pin a channel to a specific scene. This switches the channel to `manual` mode (stopping any slideshow) and wakes long-polling boxes so they pick up the new scene immediately. 404 if the scene is unknown.","operationId":"set_current_api_v1_channels__channel_id__current_put","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__CurrentBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Current Api V1 Channels  Channel Id  Current Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"delete":{"tags":["cbcast","CBCAST"],"summary":"Clear scene -> standby/logo","description":"Clear a channel's current scene and reset it to `manual` mode. Bound boxes revert to the standby / logo card on their next poll. The channel is created if it doesn't exist.","operationId":"clear_current_api_v1_channels__channel_id__current_delete","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Clear Current Api V1 Channels  Channel Id  Current Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"get":{"tags":["cbcast","CBCAST"],"summary":"Box endpoint (SVG + long-poll)","description":"Serve the channel's currently-active program or scene to a display box: returns the rendered SVG surface sized to the box's screen. Use `_self` to resolve the box's bound channel. Supports long-poll (`wait`) and ETag/304 so the box only repaints when the current content changes; 204 when the channel is on standby.","operationId":"channel_current_api_v1_channels__channel_id__current_get","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}},{"name":"wait","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Wait"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/channels/{channel_id}/data":{"put":{"tags":["cbcast","CBCAST"],"summary":"Push an Enhanced channel's data scene","description":"Set the current scene of an Enhanced channel to a JSON data snapshot.\nContent-addressed like any scene, so re-pushing identical data is a no-op\nand the box's ETag check skips it. 409 if the channel isn't Enhanced.","operationId":"set_data_api_v1_channels__channel_id__data_put","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__DataBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Data Api V1 Channels  Channel Id  Data Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/channels/{channel_id}/pause":{"post":{"tags":["cbcast","CBCAST"],"summary":"Pause/resume a channel (hold current slide)","description":"Pause or resume a channel's slideshow. While paused the ticker won't advance, so the panel holds the current slide. On resume the interval clock restarts (the held slide gets a full interval first). The channel is created if it doesn't exist.","operationId":"pause_channel_api_v1_channels__channel_id__pause_post","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__PauseBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Pause Channel Api V1 Channels  Channel Id  Pause Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/channels/{channel_id}/playlist":{"put":{"tags":["cbcast","CBCAST"],"summary":"Set a slideshow playlist","description":"Switch a channel into `slideshow` mode with an ordered playlist that the server rotates on `interval_s`. All scene ids must exist (404 otherwise) and the list must be non-empty (400 otherwise). The channel is created if it doesn't exist.","operationId":"set_playlist_api_v1_channels__channel_id__playlist_put","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__PlaylistBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Playlist Api V1 Channels  Channel Id  Playlist Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/channels/{channel_id}/story":{"put":{"tags":["cbcast","CBCAST"],"summary":"Compile + push a Story Mode scene","description":"Compile Story Mode markdown and set it as an Enhanced channel's current scene.\n\nThe compiled bundle is content-addressed by its own hash (the served ETag),\nso re-pushing identical markdown is a no-op the box's ETag check skips. The\nscene also carries the static final frame, so a Base (`kind=svg`) box bound\nto the same channel degrades to that frame with zero authoring change. 409 if\nthe channel isn't Enhanced; 502 if cbweave can't compile.","operationId":"set_story_api_v1_channels__channel_id__story_put","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__StoryBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Story Api V1 Channels  Channel Id  Story Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices":{"get":{"tags":["cbcast","CBCAST"],"summary":"List devices + hardware status","description":"List every device (physical boxes + virtual stream workers) with binding, reported hardware (resolution / monitor / EDID), last-seen, pending/last command, health, and stream state.","operationId":"list_devices_api_v1_devices_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Devices Api V1 Devices Get"}}}}}}},"/cbcast/api/v1/devices/claim":{"post":{"tags":["cbcast","CBCAST"],"summary":"Claim a device, issue a token","description":"Register a box and return its device token (idempotent — re-claiming keeps the token). Optionally bind it to a channel. A box self-claim (provision key, no tenant header) is untenanted; an operator rebind carrying a tenant is tenant-guarded against the existing device and target channel (403 cross-tenant).","operationId":"claim_device_api_v1_devices_claim_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ClaimBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Claim Device Api V1 Devices Claim Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/current":{"get":{"tags":["cbcast","CBCAST"],"summary":"Box's bound channel by token (loopback/mesh; gate blocks public)","description":"Clean by-identity endpoint. NOTE: the cbauth edge gate blocks /devices/*\npublicly, so boxes poll /channels/_self/current instead; this stays for\nloopback / mesh / admin callers.","operationId":"device_current_api_v1_devices_current_get","parameters":[{"name":"wait","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Wait"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/frame":{"post":{"tags":["cbcast","CBCAST"],"summary":"Box uploads its actual rendered panel","description":"Authenticated box uploads a snapshot of the panel it is actually showing (so Cast can display the live frame). Requires a valid device token (401 otherwise); 400 on invalid base64.","operationId":"device_frame_api_v1_devices_frame_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__FrameBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Device Frame Api V1 Devices Frame Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/telemetry":{"post":{"tags":["cbcast","CBCAST"],"summary":"Box health/status report","description":"Authenticated box reports the scene it's showing and arbitrary health metrics; updates last-seen. Identifies the box from its device token (unauthenticated reports are recorded against `unknown`).","operationId":"device_telemetry_api_v1_devices_telemetry_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__TelemetryBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Device Telemetry Api V1 Devices Telemetry Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/virtual":{"post":{"tags":["cbcast","CBCAST"],"summary":"Create a server-side stream device","description":"Create (idempotently, by name) a virtual device — a headless 'box' that broadcasts whatever channel it's bound to (HLS + optional YouTube) instead of painting a panel. Bind it to a channel like any device.","operationId":"create_virtual_device_api_v1_devices_virtual_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__VirtualDeviceBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Virtual Device Api V1 Devices Virtual Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}":{"get":{"tags":["cbcast","CBCAST"],"summary":"One device's status + hardware","description":"Return one device's full status, binding, hardware, command, and stream state. 404 if unknown.","operationId":"get_device_api_v1_devices__device_id__get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Device Api V1 Devices  Device Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"delete":{"tags":["cbcast","CBCAST"],"summary":"Deregister a device","description":"Remove a device and its stored frame. Tenant-guarded (403 cross-tenant). 404 if unknown.","operationId":"delete_device_api_v1_devices__device_id__delete","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Device Api V1 Devices  Device Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/channel":{"put":{"tags":["cbcast","CBCAST"],"summary":"Pin a box to a channel (set content / pause one screen)","description":"Pin a single box to a static channel and detach it from any program — the BrainCast Remote's 'set content on this screen' and 'pull one screen out of the room' (pause) move. Tenant-guarded against both the box and the target channel (403 cross-tenant). The box swaps on its next poll (woken immediately). 404 for an unknown box.","operationId":"set_device_channel_api_v1_devices__device_id__channel_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__DeviceChannelBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Device Channel Api V1 Devices  Device Id  Channel Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/classification":{"put":{"tags":["cbcast","CBCAST"],"summary":"Set a device's display clearance ceiling","description":"Set the highest content classification a screen may show (DESIGN §14). A channel classified above the device's clearance is withheld — the box holds standby. Tenant-guarded. 422 for an unknown level, 404 for an unknown device.","operationId":"set_device_classification_api_v1_devices__device_id__classification_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ClassificationBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Device Classification Api V1 Devices  Device Id  Classification Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/command":{"post":{"tags":["cbcast","CBCAST"],"summary":"Queue a command for a box","description":"Queue an out-of-band command (`reboot` / `restart` / `recalibrate` / `identify` / `release`) delivered to the box on its next poll (it's woken immediately). Tenant-guarded. 400 for an unknown action, 404 for an unknown device.","operationId":"device_command_api_v1_devices__device_id__command_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__CommandBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Device Command Api V1 Devices  Device Id  Command Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/frame":{"get":{"tags":["cbcast","CBCAST"],"summary":"A box's last actual panel","description":"Return the PNG bytes of the box's most recently uploaded panel. 404 if the box has never uploaded a frame.","operationId":"get_device_frame_api_v1_devices__device_id__frame_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/lock":{"put":{"tags":["cbcast","CBCAST"],"summary":"Set a device's control lock (remote read-only gate)","description":"Lock or unlock a device from control in the BrainCast Remote. A locked device is shown but read-only (can't be DJ'd or batch-controlled), independent of its channel — for dedicated controllers/kiosks you want protected. Purely a UI flag; the box's display loop is unaffected. Tenant-guarded. 404 for an unknown device.","operationId":"set_device_lock_api_v1_devices__device_id__lock_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__LockBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Device Lock Api V1 Devices  Device Id  Lock Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/name":{"put":{"tags":["cbcast","CBCAST"],"summary":"Set a device's display name","description":"Set a device's operator-facing label (the machine id is unchanged). Tenant-guarded. 404 if unknown.","operationId":"rename_device_api_v1_devices__device_id__name_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__NameBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Rename Device Api V1 Devices  Device Id  Name Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/prefs":{"put":{"tags":["cbcast","CBCAST"],"summary":"Save a box's UI prefs (the remote's preferences)","description":"Replace a box's client-owned UI prefs blob (the BrainCast Remote's last room / favorites / theme). Read them back via `GET /devices/{id}`. Disposable by design — losing them loses nothing. Tenant-guarded. 404 for an unknown box.","operationId":"set_device_prefs_api_v1_devices__device_id__prefs_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__PrefsBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Device Prefs Api V1 Devices  Device Id  Prefs Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/program":{"put":{"tags":["cbcast","CBCAST"],"summary":"Bind a box to a program (or clear)","description":"Attach a box to a Program — it then follows the program's current board\n(server-driven sequence), ignoring its static channel until cleared. Pass\nprogram_id:\"\" to detach. 409 for an unknown program; 404 for an unknown box.\nThe box swaps to the program's current board on its next poll (woken\nimmediately).","operationId":"set_device_program_api_v1_devices__device_id__program_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ProgramBindBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Device Program Api V1 Devices  Device Id  Program Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/rail":{"put":{"tags":["cbcast","CBCAST"],"summary":"Set a box's side-rail width %","description":"Per-panel sidebar:map balance for enhanced URL channels. A fixed-px rail\nlands at different ratios per panel resolution (a 4K TV reports a wide\nscreen); a percentage is resolution-independent. Injected as ?rail= into\nTHIS box's X-Channel-Url. Push a `restart` after changing it.","operationId":"set_device_rail_api_v1_devices__device_id__rail_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__RailBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Device Rail Api V1 Devices  Device Id  Rail Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/scale":{"put":{"tags":["cbcast","CBCAST"],"summary":"Set a box's per-panel render scale","description":"Per-panel zoom for enhanced URL channels — wall TVs differ in size. The\nvalue is injected as ?scale= into the X-Channel-Url served to THIS box, so\nboxes on a shared channel can each be sized independently. Push a `restart`\nafter changing it for cog to reload the URL.","operationId":"set_device_scale_api_v1_devices__device_id__scale_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ScaleBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Device Scale Api V1 Devices  Device Id  Scale Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/stream/start":{"post":{"tags":["cbcast","CBCAST"],"summary":"Start a device's stream — broadcasts its bound channel (HLS + optional YouTube)","description":"Start a virtual device's broadcast: render its bound channel server-side and mux to self-served HLS (and, with `youtube: true`, push to YouTube Live). Returns the stream status. 400 if the stream can't start (e.g. no bound channel, missing YouTube key).","operationId":"stream_start_api_v1_devices__device_id__stream_start_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__StreamStartBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Stream Start Api V1 Devices  Device Id  Stream Start Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/stream/status":{"get":{"tags":["cbcast","CBCAST"],"summary":"A device's stream status","description":"Return the live status of one virtual device's stream (running flag, YouTube flag, HLS watch path, timing).","operationId":"stream_status_api_v1_devices__device_id__stream_status_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Stream Status Api V1 Devices  Device Id  Stream Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/stream/stop":{"post":{"tags":["cbcast","CBCAST"],"summary":"Stop a device's stream","description":"Stop a virtual device's broadcast (tears down the ffmpeg workers). Returns whether a stream was actually running.","operationId":"stream_stop_api_v1_devices__device_id__stream_stop_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Stream Stop Api V1 Devices  Device Id  Stream Stop Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/stream/url":{"put":{"tags":["cbcast","CBCAST"],"summary":"Set a device's public YouTube watch URL (shown in the Cast UI)","description":"Set (or clear) the public YouTube watch URL shown for a virtual device in the Cast UI. 404 if unknown.","operationId":"set_youtube_url_api_v1_devices__device_id__stream_url_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__YoutubeUrlBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Youtube Url Api V1 Devices  Device Id  Stream Url Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/wifi":{"get":{"tags":["cbcast","CBCAST"],"summary":"Read a box's desired WiFi list","description":"The operator-managed WiFi set the box reconciles to NetworkManager.","operationId":"get_device_wifi_api_v1_devices__device_id__wifi_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Device Wifi Api V1 Devices  Device Id  Wifi Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"put":{"tags":["cbcast","CBCAST"],"summary":"Replace a box's WiFi list (full set)","description":"Set the whole desired WiFi list (replace, not a delta; de-duped by ssid). Bumps `wifi_rev`; the box re-applies on its next poll. POST an `apply-wifi` command to force it immediately. Tenant-guarded. 404 if unknown.","operationId":"put_device_wifi_api_v1_devices__device_id__wifi_put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__WifiListBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Put Device Wifi Api V1 Devices  Device Id  Wifi Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"post":{"tags":["cbcast","CBCAST"],"summary":"Add or update one WiFi network","description":"Upsert a single network by ssid (the rest of the list is kept).","operationId":"upsert_device_wifi_api_v1_devices__device_id__wifi_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__WifiNetworkBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Upsert Device Wifi Api V1 Devices  Device Id  Wifi Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/devices/{device_id}/wifi/{ssid}":{"delete":{"tags":["cbcast","CBCAST"],"summary":"Remove one WiFi network","description":"Drop a network by ssid. 404 if the box or the ssid is unknown.","operationId":"delete_device_wifi_api_v1_devices__device_id__wifi__ssid__delete","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"ssid","in":"path","required":true,"schema":{"type":"string","title":"Ssid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Device Wifi Api V1 Devices  Device Id  Wifi  Ssid  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/programs":{"get":{"tags":["cbcast","CBCAST"],"summary":"List programs (+ bound devices)","description":"List every program with its ordered boards (slots), the current cursor/board, paused state, owning tenant, and the ids of devices bound to it.","operationId":"list_programs_api_v1_programs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Programs Api V1 Programs Get"}}}}}},"post":{"tags":["cbcast","CBCAST"],"summary":"Create/rename a program (idempotent by id)","description":"Create a program, or rename an existing one (a new `name` on an existing id renames it). Tagged with the caller's tenant on create; a rename is tenant-guarded (403 cross-tenant). Returns the program detail.","operationId":"create_program_api_v1_programs_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ProgramBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Program Api V1 Programs Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/programs/{program_id}":{"get":{"tags":["cbcast","CBCAST"],"summary":"Program detail","description":"Return one program's full state (slots, cursor, current board, paused, tenant) + bound device ids. 404 if unknown.","operationId":"get_program_api_v1_programs__program_id__get","parameters":[{"name":"program_id","in":"path","required":true,"schema":{"type":"string","title":"Program Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Program Api V1 Programs  Program Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"delete":{"tags":["cbcast","CBCAST"],"summary":"Delete a program (unbinds devices)","description":"Delete a program and detach any devices following it (they fall back to their static channel binding). Tenant-guarded (403 cross-tenant). 404 if unknown.","operationId":"delete_program_api_v1_programs__program_id__delete","parameters":[{"name":"program_id","in":"path","required":true,"schema":{"type":"string","title":"Program Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Program Api V1 Programs  Program Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/programs/{program_id}/jump":{"post":{"tags":["cbcast","CBCAST"],"summary":"Cut a program to a board","description":"Cut the program to a specific board by index (wraps), restart the dwell clock, and wake every bound box so the room cuts immediately. Tenant-guarded. 404 for an unknown or empty program.","operationId":"jump_program_api_v1_programs__program_id__jump_post","parameters":[{"name":"program_id","in":"path","required":true,"schema":{"type":"string","title":"Program Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ProgramJumpBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Jump Program Api V1 Programs  Program Id  Jump Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/programs/{program_id}/pause":{"post":{"tags":["cbcast","CBCAST"],"summary":"Pause/resume a program","description":"Pause or resume a program's sequencer (freeze the whole room on the current board, or resume the rotation — resuming restarts the dwell clock). Tenant-guarded. Wakes bound boxes so the state takes effect immediately. 404 if unknown.","operationId":"pause_program_api_v1_programs__program_id__pause_post","parameters":[{"name":"program_id","in":"path","required":true,"schema":{"type":"string","title":"Program Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ProgramPauseBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Pause Program Api V1 Programs  Program Id  Pause Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/programs/{program_id}/slots":{"put":{"tags":["cbcast","CBCAST"],"summary":"Set a program's ordered boards","description":"Replace the program's boards. Validates each channel exists and that the\nprogram is single-modality (409 on mixed base/enhanced or unknown channel).","operationId":"set_program_slots_api_v1_programs__program_id__slots_put","parameters":[{"name":"program_id","in":"path","required":true,"schema":{"type":"string","title":"Program Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ProgramSlotsBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Program Slots Api V1 Programs  Program Id  Slots Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/render/pdf":{"post":{"tags":["cbcast","CBCAST"],"summary":"Render a campaignbrain.dev URL to PDF","description":"Render a public `*.campaignbrain.dev` page to a PDF via headless chromium and return the bytes (`application/pdf`). The URL is restricted to the `campaignbrain.dev` zone — no SSRF to arbitrary hosts (400 otherwise). Used by cbloom to turn a public proposal page into a signable PDF. Returns 502 if the render fails.","operationId":"render_pdf_api_v1_render_pdf_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__PdfRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/rooms":{"get":{"tags":["cbcast","CBCAST"],"summary":"List rooms (named device groups)","description":"List every room with its name, device membership, owning tenant, and timestamps.","operationId":"list_rooms_api_v1_rooms_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Rooms Api V1 Rooms Get"}}}}}},"post":{"tags":["cbcast","CBCAST"],"summary":"Create a room","description":"Create a room (a named device group). The id is a slug of the name (collision-suffixed). Tagged with the caller's tenant. Returns the room detail.","operationId":"create_room_api_v1_rooms_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__RoomBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Room Api V1 Rooms Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/rooms/{room_id}":{"get":{"tags":["cbcast","CBCAST"],"summary":"Room detail","description":"Return one room's name, device membership, tenant, and timestamps. 404 if unknown.","operationId":"get_room_api_v1_rooms__room_id__get","parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Room Api V1 Rooms  Room Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"patch":{"tags":["cbcast","CBCAST"],"summary":"Edit a room (name / membership)","description":"Patch a room's name and/or device membership (membership is the full replacement set, not a delta). Tenant-guarded (403 cross-tenant). 404 if unknown.","operationId":"update_room_api_v1_rooms__room_id__patch","parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__RoomMetaBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Room Api V1 Rooms  Room Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"delete":{"tags":["cbcast","CBCAST"],"summary":"Delete a room","description":"Delete a room (the member devices are untouched). Tenant-guarded. 404 if unknown.","operationId":"delete_room_api_v1_rooms__room_id__delete","parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Room Api V1 Rooms  Room Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/rooms/{room_id}/beat":{"put":{"tags":["cbcast","CBCAST"],"summary":"Set/clear a room's synchronized beat","description":"Give a room a synchronized **beat** so its screens transition together on one metronome — even when each shows a different channel (the 'video wall' — DESIGN §13). `beat_s: 0` clears it. `align_global` anchors the phase to the global grid; otherwise the interval starts clean from now. While a beat is set it is the **sole** clock for the members' sources (their private dwell/interval is suppressed). Tenant-guarded. 404 if unknown.","operationId":"set_room_beat_api_v1_rooms__room_id__beat_put","parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__RoomBeatBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Room Beat Api V1 Rooms  Room Id  Beat Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/rooms/{room_id}/beat/pause":{"post":{"tags":["cbcast","CBCAST"],"summary":"Pause/resume a room's beat (freeze the wall)","description":"Freeze the whole room on its current frame, or resume (resuming re-anchors the phase so the held frame gets a full beat). Tenant-guarded. 404 if unknown.","operationId":"pause_room_beat_api_v1_rooms__room_id__beat_pause_post","parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__RoomBeatPauseBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Pause Room Beat Api V1 Rooms  Room Id  Beat Pause Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/rooms/{room_id}/beat/pulse":{"post":{"tags":["cbcast","CBCAST"],"summary":"Advance a room one step now (the wall's 'next')","description":"Advance every screen in the room one step immediately (each member's source steps once), and — if a beat is running — re-anchor its phase so the next auto-advance is a full beat out. The wall's manual 'next' button. Tenant-guarded. 404 if unknown.","operationId":"pulse_room_api_v1_rooms__room_id__beat_pulse_post","parameters":[{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Pulse Room Api V1 Rooms  Room Id  Beat Pulse Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/scenes":{"get":{"tags":["cbcast","CBCAST"],"summary":"List scenes","description":"List every scene in the library with its metadata (id, surface, name, raw flag, timestamps, ETag).","operationId":"list_scenes_api_v1_scenes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Scenes Api V1 Scenes Get"}}}}}},"post":{"tags":["cbcast","CBCAST"],"summary":"Create a scene (markdown spec or raw SVG)","description":"Add a scene to the library from a cbweave markdown spec or a pre-rendered SVG. A markdown scene is rendered + validated through cbweave on create (502 if it fails). Content-addressed: posting identical content returns the existing scene. Returns the scene metadata (id, surface, timestamps, ETag).","operationId":"create_scene_api_v1_scenes_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__SceneBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Scene Api V1 Scenes Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/scenes/{scene_id}":{"get":{"tags":["cbcast","CBCAST"],"summary":"Rendered scene SVG","description":"Return a scene's rendered SVG bytes (`image/svg+xml`) with an ETag. Markdown scenes are rendered through cbweave on demand (cached by content hash); raw scenes are served as stored. 404 if unknown, 502 if rendering fails.","operationId":"get_scene_api_v1_scenes__scene_id__get","parameters":[{"name":"scene_id","in":"path","required":true,"schema":{"type":"string","title":"Scene Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"delete":{"tags":["cbcast","CBCAST"],"summary":"Delete a scene from the library","description":"Remove a scene and scrub every reference to it: workspace membership, slideshow playlists, and any channel currently showing it (which falls back to the next playlist entry, else goes idle). 404 if unknown.","operationId":"delete_scene_api_v1_scenes__scene_id__delete","parameters":[{"name":"scene_id","in":"path","required":true,"schema":{"type":"string","title":"Scene Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Scene Api V1 Scenes  Scene Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/screenshots":{"get":{"tags":["cbcast","CBCAST"],"summary":"List screenshots","description":"List all screenshots (newest first) with metadata + a URL to fetch each one's bytes.","operationId":"list_screenshots_api_v1_screenshots_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Screenshots Api V1 Screenshots Get"}}}}}},"post":{"tags":["cbcast","CBCAST"],"summary":"Capture a screenshot (snapshot or upload)","description":"Capture a screenshot from one of three sources (checked in order): raw `image_b64` bytes; a `device_id`'s actual uploaded panel; or a `channel_id` (or device's channel) rendered to SVG. 400 if no usable source, 404 if the channel has no current scene, 502 on render failure.","operationId":"create_screenshot_api_v1_screenshots_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ScreenshotBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Screenshot Api V1 Screenshots Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/screenshots/{shot_id}":{"get":{"tags":["cbcast","CBCAST"],"summary":"Screenshot image bytes","description":"Return a screenshot's raw image bytes with its stored content type. 404 if unknown or the file is missing.","operationId":"get_screenshot_api_v1_screenshots__shot_id__get","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"patch":{"tags":["cbcast","CBCAST"],"summary":"Update a screenshot label","description":"Update a screenshot's label. 404 if unknown.","operationId":"patch_screenshot_api_v1_screenshots__shot_id__patch","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__ScreenshotPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Patch Screenshot Api V1 Screenshots  Shot Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}},"delete":{"tags":["cbcast","CBCAST"],"summary":"Delete a screenshot","description":"Delete a screenshot and its stored image bytes. 404 if unknown.","operationId":"delete_screenshot_api_v1_screenshots__shot_id__delete","parameters":[{"name":"shot_id","in":"path","required":true,"schema":{"type":"string","title":"Shot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Screenshot Api V1 Screenshots  Shot Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/state-rev":{"get":{"tags":["cbcast","CBCAST"],"summary":"Fleet change-revision (cheap poll target)","description":"Return tiny per-domain fingerprints — `channels`, `rooms`, `devices`, `frames`, `online` — plus a roll-up `rev`. A poller (e.g. the BrainCast Remote) fetches THIS every few seconds and re-pulls `/channels`, `/rooms`, or `/devices` only when the matching fingerprint changed, instead of re-fetching all three each cycle. Values are opaque ints — compare for equality, don't interpret. `online` is a physical-device liveness count (threshold-based, so it moves on an on/offline crossing, not on every heartbeat). Covers the whole store, matching the unscoped list endpoints.","operationId":"state_rev_api_v1_state_rev_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response State Rev Api V1 State Rev Get"}}}}}}},"/cbcast/api/v1/streams":{"get":{"tags":["cbcast","CBCAST"],"summary":"All active streams","description":"List the live status of every active virtual-device stream.","operationId":"streams_list_api_v1_streams_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Streams List Api V1 Streams Get"}}}}}}},"/cbcast/api/v1/streams/{device_id}/{filename}":{"get":{"tags":["cbcast","CBCAST"],"summary":"Serve HLS playlist/segments","description":"Serve a virtual device's HLS output: the `live.m3u8` playlist or a `seg_NNNNN.ts` segment. Only these exact filename shapes are servable (no path traversal). 404 for anything else or a missing file.","operationId":"stream_file_api_v1_streams__device_id___filename__get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/workspaces":{"get":{"tags":["cbcast","CBCAST"],"summary":"List workspaces","description":"List every workspace with its id, name, and scene count.","operationId":"list_workspaces_api_v1_workspaces_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Workspaces Api V1 Workspaces Get"}}}}}},"post":{"tags":["cbcast","CBCAST"],"summary":"Create a workspace","description":"Create a new (empty) named scene collection and return its detail.","operationId":"create_workspace_api_v1_workspaces_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__WorkspaceBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Workspace Api V1 Workspaces Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/workspaces/{workspace_id}":{"get":{"tags":["cbcast","CBCAST"],"summary":"Workspace detail (scenes)","description":"Return a workspace with the full metadata of each member scene. 404 if unknown.","operationId":"get_workspace_api_v1_workspaces__workspace_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Workspace Api V1 Workspaces  Workspace Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/workspaces/{workspace_id}/scenes":{"post":{"tags":["cbcast","CBCAST"],"summary":"Add/create a scene","description":"Add a scene to a workspace — either reference an existing scene by `scene_id`, or create a new one inline from `markdown`/`svg`. 404 for an unknown workspace or referenced scene.","operationId":"add_workspace_scene_api_v1_workspaces__workspace_id__scenes_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__WorkspaceSceneBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Workspace Scene Api V1 Workspaces  Workspace Id  Scenes Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/api/v1/workspaces/{workspace_id}/scenes/{scene_id}":{"delete":{"tags":["cbcast","CBCAST"],"summary":"Remove scene","description":"Remove a scene from a workspace (the scene itself stays in the library). 404 if the workspace is unknown.","operationId":"remove_workspace_scene_api_v1_workspaces__workspace_id__scenes__scene_id__delete","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"scene_id","in":"path","required":true,"schema":{"type":"string","title":"Scene Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Remove Workspace Scene Api V1 Workspaces  Workspace Id  Scenes  Scene Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HTTPValidationError"}}}}}}},"/cbcast/health":{"get":{"tags":["cbcast","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HealthResponse"}}}}}}},"/cbcast/health/detailed":{"get":{"tags":["cbcast","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__HealthResponse"}}}}}}},"/cbcast/prime":{"get":{"tags":["cbcast","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__PrimeResponse"}}}}}}},"/cbcast/status":{"get":{"tags":["cbcast","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcast__StatusResponse"}}}}}}},"/cbconvo/api/v1/audit":{"get":{"tags":["cbconvo","CBCONVO"],"summary":"Outbound + inbound audit","description":"Every **attempted** send with its gate verdict, plus every inbound. A refused send appears here with its verdict — the audit row is written whether or not anything left the building.","operationId":"audit_api_v1_audit_get","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Audit Api V1 Audit Get"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/brief":{"get":{"tags":["cbconvo","CBCONVO"],"summary":"The programme brief — the client rollup, DERIVED from the store","description":"Headline counts, the issues table and stated-reason coding for one programme, computed from cbconvo's own claims and contact turns (cbconvo#14) — the artifact a campaign is shown, generated rather than hand-rendered.\n\n**Read `meta.semantics` before quoting a number.** Every stat names the base it is a percentage of; `voters_who_gave_a_reason` and `reasons_given` are different denominators and each reason row carries both; the issues table's columns are shares of each side's own total and do not sum to 100%; a percentage on an empty base is `null`, never `0.0`. `unclassified` is a reported row, never folded into another.","operationId":"brief_api_v1_brief_get","parameters":[{"name":"programme","in":"query","required":true,"schema":{"type":"string","title":"Programme"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Brief Api V1 Brief Get"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"404":{"description":"Unknown programme id."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/claims":{"get":{"tags":["cbconvo","CBCONVO"],"summary":"List claims","description":"Claims, newest first, optionally filtered by `status` (`live`, `closed`, `expired`, `opted_out`).","operationId":"claims_api_v1_claims_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Claims Api V1 Claims Get"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/claims/{claim_id}/redraft":{"post":{"tags":["cbconvo","CBCONVO"],"summary":"Draft again for the last contact turn (cbconvo#29)","description":"Composes a fresh reply to the conversation's LAST voter message with everything previously declined for it in the model's context. For a conversation with nothing pending — e.g. both drafts were rejected. Sends nothing; the draft lands `pending`. On a done claim whose closing was rejected too, the closing is drafted again beside it.","operationId":"redraft_claim_api_v1_claims__claim_id__redraft_post","parameters":[{"name":"claim_id","in":"path","required":true,"schema":{"type":"string","title":"Claim Id"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Redraft Claim Api V1 Claims  Claim Id  Redraft Post"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"404":{"description":"No claim with this id."},"409":{"description":"Nothing to draft into: the claim is ended, the closing has gone, a reply is already pending, or there is no voter message."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/claims/{claim_id}/release":{"post":{"tags":["cbconvo","CBCONVO"],"summary":"End a conversation by hand (sends nothing)","description":"cbconvo#28: closes an ACTIVE claim as `released` with `closed_reason` naming the operator; its pending/held drafts go `superseded`; the number can be started again. Nothing is sent and nothing is deleted — the transcript stays. Before this, a stuck conversation blocked its number until the 24h TTL.","operationId":"release_claim_api_v1_claims__claim_id__release_post","parameters":[{"name":"claim_id","in":"path","required":true,"schema":{"type":"string","title":"Claim Id"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Release Claim Api V1 Claims  Claim Id  Release Post"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"404":{"description":"No claim with this id."},"409":{"description":"The claim is not active (already done / released / opted out / timed out)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/contacts/locations":{"get":{"tags":["cbconvo","CBCONVO"],"summary":"Stated contact locations — the zones that outrank the area code","description":"Every phone with a STATED time zone (cbconvo#26). Quiet hours use this zone alone for that person, ignoring the area code, which is only a guess about where a number lives. Phones are shown here because a location is a fact about a specific person and the operator setting it is legitimately looking at the number.","operationId":"contact_locations_api_v1_contacts_locations_get","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Contact Locations Api V1 Contacts Locations Get"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/contacts/{phone}/location":{"put":{"tags":["cbconvo","CBCONVO"],"summary":"State where a person is (outranks the area code for quiet hours)","description":"Record the IANA zone a contact actually lives in. From then on quiet hours are judged in THAT zone only. Any of their drafts already `approved_held` are re-tried immediately through THE GATE in full — so stating the zone for a Michigan resident on a Pacific number at 09:00 ET releases the morning's held opening in this call; the ids actually transmitted come back in `released`.","operationId":"set_contact_location_api_v1_contacts__phone__location_put","parameters":[{"name":"phone","in":"path","required":true,"schema":{"type":"string","title":"Phone"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__LocationBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Contact Location Api V1 Contacts  Phone  Location Put"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"422":{"description":"`tz` is not an IANA zone this host can resolve, or the phone is not E.164."}}},"delete":{"tags":["cbconvo","CBCONVO"],"summary":"Forget a stated location — quiet hours fall back to the area code","operationId":"clear_contact_location_api_v1_contacts__phone__location_delete","parameters":[{"name":"phone","in":"path","required":true,"schema":{"type":"string","title":"Phone"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Clear Contact Location Api V1 Contacts  Phone  Location Delete"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"404":{"description":"Nothing was stated for this phone."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/drafts":{"get":{"tags":["cbconvo","CBCONVO"],"summary":"Drafts awaiting a human click (default: pending)","description":"`pending` means exactly 'a human can click this'. A newer contact turn supersedes every pending draft on its claim, so a draft listed here and not acted on may be terminal by the time it is clicked.\n\n`?status=approved_held` lists drafts a human HAS approved whose transmission is waiting on a clock: the recipient's quiet-hours window (cbconvo#18) or the per-SIM floor between sends (cbconvo#30). Each carries `held_for` and `holds_until`. A newer inbound supersedes those too, and THE GATE is re-run in full when the hold releases.","operationId":"drafts_api_v1_drafts_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"default":"pending","title":"Status"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Drafts Api V1 Drafts Get"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/drafts/{draft_id}/approve":{"post":{"tags":["cbconvo","CBCONVO"],"summary":"The human click — send one draft (GATED)","description":"Approval is necessary but never sufficient: THE GATE runs on the click, so an approved draft to an unlisted number, or with `CONVO_LIVE` unset, still refuses with 403.","operationId":"approve_draft_api_v1_drafts__draft_id__approve_post","parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Approve Draft Api V1 Drafts  Draft Id  Approve Post"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"403":{"description":"THE GATE refused the send. Body is `{gate: <verdict>, reason: <str>}` — one of `refused_bad_number`, `refused_not_live` (`CONVO_LIVE` != 1), `refused_not_listed` (not in `CONVO_ALLOWED_CONTACTS`), `refused_quiet_hours` or `quiet_hours_unknown_tz`. A refusal is final and audited; do not retry it."},"404":{"description":"No draft with this id."},"409":{"description":"The draft is already terminal — approved, rejected, or superseded by a newer contact turn."},"502":{"description":"The transport failed after the gate allowed the send. The draft is terminal: **do not re-approve** — check `/api/v1/audit` to see whether it went out."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/drafts/{draft_id}/reject":{"post":{"tags":["cbconvo","CBCONVO"],"summary":"Discard one draft — and, for a model reply, draft again (cbconvo#29)","description":"Marks the draft rejected. Sends nothing and never touches the gate. If the draft was a MODEL reply on a live claim and no newer contact turn has arrived, the engine composes a fresh reply for the same contact turn at once, with the declined text (and your `note`) in its context; the new draft lands `pending` like any other. `redraft` carries the outcome, or `redraft_skipped` says why not (an opening, closing or canned line is programme text — nothing to try again).","operationId":"reject_draft_api_v1_drafts__draft_id__reject_post","parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbconvo__RejectBody"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reject Draft Api V1 Drafts  Draft Id  Reject Post"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"404":{"description":"No draft with this id."},"409":{"description":"The draft is already terminal."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/gate":{"get":{"tags":["cbconvo","CBCONVO"],"summary":"Gate status — counts only, never the numbers","description":"The live flag, the allowlist **count**, the quiet-hours window and the bound transport/model names. Never the allowlisted numbers themselves — a phone number is a secret and this endpoint is an MCP tool.\n\nThere is no auto-send switch to report: a human approves every send (docs/REGULATORY.md §1). A draft approved inside quiet hours is HELD and transmits when the window opens (cbconvo#18).","operationId":"gate_api_v1_gate_get","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Gate Api V1 Gate Get"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/optouts":{"get":{"tags":["cbconvo","CBCONVO"],"summary":"Revocations (stage0 + optout_intent) for VT suppression","description":"Every contact that sent a STOP token or an opt-out intent, for Victory Text to suppress against. Opt-outs are recorded, never deleted.","operationId":"optouts_api_v1_optouts_get","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Optouts Api V1 Optouts Get"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/programmes":{"get":{"tags":["cbconvo","CBCONVO"],"summary":"List loaded programmes","description":"Every programme YAML under `CONVO_PROGRAMMES_DIR`, with its goal keys. A file whose `paid_for_by` is still a `<<placeholder>>` is skipped at load and will not appear here.","operationId":"programmes_api_v1_programmes_get","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Programmes Api V1 Programmes Get"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/programmes/{programme_id}/start":{"post":{"tags":["cbconvo","CBCONVO"],"summary":"Open a claim and send the opening (GATED)","description":"Opens one claim on `contact` and composes the opening — fixed text (programme opening + disclosure + paid-for line), never the model. With the programme's default `send_mode=human_click` this returns a `draft_id` and sends nothing; approval is a separate call. One live claim per contact.","operationId":"start_api_v1_programmes__programme_id__start_post","parameters":[{"name":"programme_id","in":"path","required":true,"schema":{"type":"string","title":"Programme Id"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__StartBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Start Api V1 Programmes  Programme Id  Start Post"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"403":{"description":"THE GATE refused the send. Body is `{gate: <verdict>, reason: <str>}` — one of `refused_bad_number`, `refused_not_live` (`CONVO_LIVE` != 1), `refused_not_listed` (not in `CONVO_ALLOWED_CONTACTS`), `refused_quiet_hours` or `quiet_hours_unknown_tz`. A refusal is final and audited; do not retry it."},"404":{"description":"No programme with this id is loaded."},"409":{"description":"The contact already has a live claim, or has opted out."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/programmes/{programme_id}/test-conversations":{"post":{"tags":["cbconvo","CBCONVO"],"summary":"Open a TEST conversation with the programme's real agent (no SMS)","description":"cbconvo#23. Drives the programme's REAL agent -- its prompt, goal capture, guardrails, judge and STOP path -- through a second engine over an in-memory store with a fake transport and an always-open gate. NOTHING here can reach cbsms, a claim, a contact or the audit; every `reply` is the text the agent WOULD have sent. `test: true` on every response.","operationId":"test_start_api_v1_programmes__programme_id__test_conversations_post","parameters":[{"name":"programme_id","in":"path","required":true,"schema":{"type":"string","title":"Programme Id"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__TestStartBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Test Start Api V1 Programmes  Programme Id  Test Conversations Post"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/test-conversations/{conv_id}":{"get":{"tags":["cbconvo","CBCONVO"],"summary":"A TEST conversation: claim, goals and transcript","operationId":"test_get_api_v1_test_conversations__conv_id__get","parameters":[{"name":"conv_id","in":"path","required":true,"schema":{"type":"string","title":"Conv Id"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Test Get Api V1 Test Conversations  Conv Id  Get"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"404":{"description":"Unknown test conversation."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/test-conversations/{conv_id}/messages":{"post":{"tags":["cbconvo","CBCONVO"],"summary":"The voter's turn in a TEST conversation; returns the agent's replies","description":"Synchronous: the inbound runs the whole engine turn and `replies` holds every text the agent produced for it (a reply, then possibly the closing). `outcome` is the engine's (`replied`, `closed`, `canned:...`, `recovery:...`, `stop`).","operationId":"test_message_api_v1_test_conversations__conv_id__messages_post","parameters":[{"name":"conv_id","in":"path","required":true,"schema":{"type":"string","title":"Conv Id"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__TestMessageBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Test Message Api V1 Test Conversations  Conv Id  Messages Post"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"404":{"description":"Unknown test conversation."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/transcript":{"get":{"tags":["cbconvo","CBCONVO"],"summary":"Transcript rows (vt_response shape) for a contact","description":"Every turn for one contact in the `vt_response` shape Victory Text already consumes. `contact` is normalised to E.164 before lookup.","operationId":"transcript_api_v1_transcript_get","parameters":[{"name":"contact","in":"query","required":true,"schema":{"type":"string","title":"Contact"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Transcript Api V1 Transcript Get"}}}},"401":{"description":"No verified operator (nginx `X-User-*` or a cbauth JWT)."},"503":{"description":"`AUTH_JWT_SECRET`/`CB_AUTH_JWT_SECRET` is not provisioned, or the engine has not started. The gated routes refuse rather than serving open."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/api/v1/webhook/sms":{"post":{"tags":["cbconvo","CBCONVO"],"summary":"Inbound SMS from cbsms","description":"Machine-to-machine from cbsms — **the one route with no operator auth**; safety comes from the engine ignoring any sender that is not a claimed contact. cbsms posts each inbound twice (bare and E.164-prefixed), so a repeat within 30s is answered `duplicate` and dropped. `accepted` tells cbsms to stop forwarding; `ignored` lets it fall through to the next callback.","operationId":"webhook_sms_api_v1_webhook_sms_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__InboundSMS"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Webhook Sms Api V1 Webhook Sms Post"}}}},"503":{"description":"The engine has not started."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HTTPValidationError"}}}}}}},"/cbconvo/health":{"get":{"tags":["cbconvo","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HealthResponse"}}}}}}},"/cbconvo/health/detailed":{"get":{"tags":["cbconvo","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__HealthResponse"}}}}}}},"/cbconvo/prime":{"get":{"tags":["cbconvo","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__PrimeResponse"}}}}}}},"/cbconvo/status":{"get":{"tags":["cbconvo","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbconvo__StatusResponse"}}}}}}},"/cbcrm/api/v1/custom-fields":{"post":{"tags":["cbcrm","CBCRM"],"summary":"Define a custom field","operationId":"define_field_api_v1_custom_fields_post","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__CustomFieldIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Define Field Api V1 Custom Fields Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}},"get":{"tags":["cbcrm","CBCRM"],"summary":"List custom fields","operationId":"list_fields_api_v1_custom_fields_get","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Fields Api V1 Custom Fields Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}}},"/cbcrm/api/v1/persons":{"post":{"tags":["cbcrm","CBCRM"],"summary":"Create a person","operationId":"create_person_api_v1_persons_post","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__PersonIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Person Api V1 Persons Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}},"get":{"tags":["cbcrm","CBCRM"],"summary":"Search/list persons","operationId":"list_persons_api_v1_persons_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Match against name/email/phone.","title":"Q"},"description":"Match against name/email/phone."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Persons Api V1 Persons Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}}},"/cbcrm/api/v1/persons/upsert":{"post":{"tags":["cbcrm","CBCRM"],"summary":"Create-or-merge a person","description":"Dedup on pid → svid → email; create or merge. The contract cblist calls to\npromote a list row into a canonical golden person.","operationId":"upsert_person_api_v1_persons_upsert_post","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__PersonIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__UpsertResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}}},"/cbcrm/api/v1/persons/{person_id}":{"get":{"tags":["cbcrm","CBCRM"],"summary":"Get a person","operationId":"get_person_api_v1_persons__person_id__get","parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"Person Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Person Api V1 Persons  Person Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}},"patch":{"tags":["cbcrm","CBCRM"],"summary":"Update a person","operationId":"update_person_api_v1_persons__person_id__patch","parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"Person Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__PersonIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Person Api V1 Persons  Person Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}},"delete":{"tags":["cbcrm","CBCRM"],"summary":"Soft-delete a person","operationId":"delete_person_api_v1_persons__person_id__delete","parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"Person Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Person Api V1 Persons  Person Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}}},"/cbcrm/api/v1/persons/{person_id}/fields":{"get":{"tags":["cbcrm","CBCRM"],"summary":"Get a person's field values","operationId":"get_fields_api_v1_persons__person_id__fields_get","parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"Person Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Fields Api V1 Persons  Person Id  Fields Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}}},"/cbcrm/api/v1/persons/{person_id}/fields/{field_id}":{"put":{"tags":["cbcrm","CBCRM"],"summary":"Set a person's field value","operationId":"set_field_api_v1_persons__person_id__fields__field_id__put","parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"Person Id"}},{"name":"field_id","in":"path","required":true,"schema":{"type":"string","title":"Field Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__FieldValueIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Field Api V1 Persons  Person Id  Fields  Field Id  Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}}},"/cbcrm/api/v1/persons/{person_id}/interactions":{"post":{"tags":["cbcrm","CBCRM"],"summary":"Log an interaction","operationId":"add_interaction_api_v1_persons__person_id__interactions_post","parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"Person Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__InteractionIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Interaction Api V1 Persons  Person Id  Interactions Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}},"get":{"tags":["cbcrm","CBCRM"],"summary":"List interactions","operationId":"list_interactions_api_v1_persons__person_id__interactions_get","parameters":[{"name":"person_id","in":"path","required":true,"schema":{"type":"string","title":"Person Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Interactions Api V1 Persons  Person Id  Interactions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HTTPValidationError"}}}}}}},"/cbcrm/health":{"get":{"tags":["cbcrm","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HealthResponse"}}}}}}},"/cbcrm/health/detailed":{"get":{"tags":["cbcrm","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__HealthResponse"}}}}}}},"/cbcrm/prime":{"get":{"tags":["cbcrm","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__PrimeResponse"}}}}}}},"/cbcrm/status":{"get":{"tags":["cbcrm","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcrm__StatusResponse"}}}}}}},"/cbcron/api/control/quiesce":{"post":{"tags":["cbcron","Control"],"summary":"Pause dispatch for high-priority work","description":"Pause the scheduler so in-flight jobs finish but nothing new is dispatched — for heavy external work (e.g. ccmaster). Auto-resumes after ttl_seconds (crash-safety) if /resume is never called.","operationId":"quiesce_api_control_quiesce_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__QuiesceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Quiesce Api Control Quiesce Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/control/resume":{"post":{"tags":["cbcron","Control"],"summary":"Resume dispatch immediately","description":"Clear an active quiesce and resume dispatching jobs at once.","operationId":"resume_api_control_resume_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Resume Api Control Resume Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/control/status":{"get":{"tags":["cbcron","Control"],"summary":"Current quiesce + active-group state","description":"Returns whether dispatch is quiesced (with reason + auto-resume time) and which serialization groups currently have a job running.","operationId":"control_status_api_control_status_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Control Status Api Control Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/jobs":{"post":{"tags":["cbcron","Jobs"],"summary":"Create a scheduled job","description":"Create a new scheduled job.\n\nRegisters an HTTP dispatch job with one of three trigger types:\n- **cron**: recurring 5-field cron expression (default)\n- **once**: single fire at a specific UTC datetime\n- **calendar**: fire at each datetime in a list","operationId":"create_job_api_jobs_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__JobCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__Job"}}}},"422":{"description":"Invalid cron expression, timezone, or field constraints."}}},"get":{"tags":["cbcron","Jobs"],"summary":"List all jobs","description":"List all jobs, optionally filtered by enabled state and/or tag.\n\nWith no query params, returns every registered job (enabled and paused).\nEach job includes its computed `next_run_at`.","operationId":"list_jobs_api_jobs_get","parameters":[{"name":"enabled","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by enabled state. `true` returns only enabled jobs; omit (or null) to return all jobs regardless of enabled state.","title":"Enabled"},"description":"Filter by enabled state. `true` returns only enabled jobs; omit (or null) to return all jobs regardless of enabled state."},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Return only jobs carrying this tag. Omit for no tag filter.","title":"Tag"},"description":"Return only jobs carrying this tag. Omit for no tag filter."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbcron__Job"},"title":"Response List Jobs Api Jobs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/jobs/{job_id}":{"get":{"tags":["cbcron","Jobs"],"summary":"Get a job","description":"Get job detail including next_run_at.","operationId":"get_job_api_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__Job"}}}},"404":{"description":"Job not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}},"patch":{"tags":["cbcron","Jobs"],"summary":"Update a job","description":"Update a job's configuration.\n\nPartial update — only provided fields are changed. Omitted fields\nremain unchanged.","operationId":"update_job_api_jobs__job_id__patch","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__JobUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__Job"}}}},"400":{"description":"No fields provided to update."},"404":{"description":"Job not found."},"422":{"description":"Invalid cron expression, timezone, or field constraints."}}},"delete":{"tags":["cbcron","Jobs"],"summary":"Delete a job","description":"Delete a job and all its run history.","operationId":"delete_job_api_jobs__job_id__delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"Job not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/jobs/{job_id}/pause":{"post":{"tags":["cbcron","Jobs"],"summary":"Pause a job","description":"Pause a job (set enabled=false).\n\nThe scheduler will skip this job on future ticks. Any in-flight\nexecution will complete normally.","operationId":"pause_job_api_jobs__job_id__pause_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__Job"}}}},"404":{"description":"Job not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/jobs/{job_id}/resume":{"post":{"tags":["cbcron","Jobs"],"summary":"Resume a paused job","description":"Resume a paused job (set enabled=true).","operationId":"resume_job_api_jobs__job_id__resume_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__Job"}}}},"404":{"description":"Job not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/jobs/{job_id}/runs":{"get":{"tags":["cbcron","Runs"],"summary":"List runs for a job","description":"Get run history for a specific job.\n\nReturns runs newest-first. Each run represents one execution attempt.\nMultiple runs with the same `started_at` window indicate retries.","operationId":"list_job_runs_api_jobs__job_id__runs_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum number of runs to return, newest-first.","default":100,"title":"Limit"},"description":"Maximum number of runs to return, newest-first."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbcron__Run"},"title":"Response List Job Runs Api Jobs  Job Id  Runs Get"}}}},"404":{"description":"Job not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/jobs/{job_id}/trigger":{"post":{"tags":["cbcron","Jobs"],"summary":"Trigger a job immediately","description":"Manually trigger a job (fire now, bypasses schedule).\n\nThe job executes asynchronously. Check run history for the result.\nOverlap protection still applies — if the job is already running,\nit will be queued behind the semaphore.","operationId":"trigger_job_api_jobs__job_id__trigger_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Trigger Job Api Jobs  Job Id  Trigger Post"}}}},"404":{"description":"Job not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/runs/recent":{"get":{"tags":["cbcron","Runs"],"summary":"Recent runs across all jobs","description":"Get recent runs across all jobs, newest-first.\n\nA fleet-wide activity feed — useful for spotting recent failures or\nconfirming the scheduler is firing jobs. For a single job's history use\n`GET /api/jobs/{job_id}/runs` instead.","operationId":"recent_runs_api_runs_recent_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum number of runs to return, newest-first.","default":50,"title":"Limit"},"description":"Maximum number of runs to return, newest-first."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbcron__Run"},"title":"Response Recent Runs Api Runs Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/status":{"get":{"tags":["cbcron","Runs","Health"],"summary":"All jobs with run stats","description":"Single source of truth for job health. Returns every job with aggregated run statistics, last run output, and next scheduled fire time.","operationId":"scheduler_status_api_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbcron__JobStatus"},"type":"array","title":"Response Scheduler Status Api Status Get"}}}}}}},"/cbcron/api/status/failing":{"get":{"tags":["cbcron","Runs","Health"],"summary":"Jobs that are currently failing","description":"The health rollup: only jobs whose most-recent execution failed or timed out, sorted by consecutive-failure streak (deepest first). An empty list means every job's last run succeeded. Pass `enabled_only=false` to include paused jobs frozen in a failed state.","operationId":"failing_jobs_api_status_failing_get","parameters":[{"name":"enabled_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only include enabled jobs (the scheduler still fires these).","default":true,"title":"Enabled Only"},"description":"Only include enabled jobs (the scheduler still fires these)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbcron__JobStatus"},"title":"Response Failing Jobs Api Status Failing Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HTTPValidationError"}}}}}}},"/cbcron/api/status/summary":{"get":{"tags":["cbcron","Runs","Health"],"summary":"Fleet-wide cron health rollup","description":"One call for a dashboard: total/enabled/disabled counts, how many jobs are actively failing right now (vs dormant seasonal ones), and a flat row per failing job (deepest streak first). Powers the cbloom Infrastructure Health loom.","operationId":"health_summary_api_status_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__JobHealthSummary"}}}}}}},"/cbcron/health":{"get":{"tags":["cbcron","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HealthResponse"}}}}}}},"/cbcron/health/detailed":{"get":{"tags":["cbcron","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__HealthResponse"}}}}}}},"/cbcron/prime":{"get":{"tags":["cbcron","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__PrimeResponse"}}}}}}},"/cbcron/status":{"get":{"tags":["cbcron","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbcron__StatusResponse"}}}}}}},"/cbdatabase/api/v1/databases":{"get":{"tags":["cbdatabase","Databases"],"summary":"List all managed databases","description":"Return registry records for every database, online or offline.","operationId":"list_databases_api_v1_databases_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbdatabase__DatabaseInfoResponse"},"type":"array","title":"Response List Databases Api V1 Databases Get"}}}}}},"post":{"tags":["cbdatabase","Databases"],"summary":"Mint a database from a schema","description":"Create a new database, materialize its tables and indexes, and bring it online.\n\nThe schema is declarative. Each table lists columns with types and constraints;\nthe control plane compiles it to DDL and runs it as one queued script so table\norder is deterministic. For types the spec does not model, use a table's\n`raw_sql` field.\n\n**Example: a minimal field database with catalog metadata**\n```json\n{\n  \"schema\": {\n    \"name\": \"ia03_field\",\n    \"tables\": [{\n      \"name\": \"voters\",\n      \"columns\": [\n        {\"name\": \"voter_id\", \"type\": \"VARCHAR\", \"primary_key\": true},\n        {\"name\": \"last_name\", \"type\": \"VARCHAR\", \"nullable\": false},\n        {\"name\": \"district_id\", \"type\": \"VARCHAR\"}\n      ],\n      \"indexes\": [{\"name\": \"idx_district\", \"columns\": [\"district_id\"]}]\n    }]\n  },\n  \"owner_tenant\": \"tenant_ia_dem\",\n  \"visibility\": \"tenant\",\n  \"description\": \"IA-03 field operations voter file\",\n  \"tags\": [\"field\", \"ia-03\"]\n}\n```","operationId":"mint_database_api_v1_databases_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__MintRequest"}}},"required":true},"responses":{"201":{"description":"Database minted and online","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__DatabaseInfoResponse"}}}},"409":{"description":"A database with this name already exists","content":{"application/json":{"example":{"detail":"database 'ia03_field' already exists"}}}},"400":{"description":"Schema compiled to invalid DDL","content":{"application/json":{"example":{"detail":"Catalog Error: table voters already exists"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}}},"/cbdatabase/api/v1/databases/{name}":{"get":{"tags":["cbdatabase","Databases"],"summary":"Get one database's status","description":"Return the registry record for a single database by name.","operationId":"get_database_api_v1_databases__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__DatabaseInfoResponse"}}}},"404":{"description":"No such database"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}},"delete":{"tags":["cbdatabase","Databases"],"summary":"Drop a database","description":"Take a database offline and remove it from the registry.\n\nBy default the file is archived to the backup directory so it can be recovered.\nPass `delete_file=true` to permanently delete the file instead.","operationId":"drop_database_api_v1_databases__name__delete","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"delete_file","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Delete File"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__DropResponse"}}}},"404":{"description":"No such database"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}}},"/cbdatabase/api/v1/databases/{name}/backups":{"post":{"tags":["cbdatabase","Backups"],"summary":"Create a backup","description":"Snapshot the whole database to a timestamped backup file, then **verify the copy**\nbefore reporting success.\n\nVerification reads every column of every table and probes every index in the\nwritten file. It runs against the copy rather than the live catalog, so it holds\nno write lock and cannot starve federation readers — and it checks the exact\nartifact a restore would use.\n\nA **409** means the backup file was written but does not verify, which means the\nLIVE store is already damaged. Do not retry the backup: restore from an older\nverified backup instead. `last_backup_at` is deliberately left unchanged so the\nregistry never advertises a corrupt copy as the latest good one.\n\nPass `verify=false` only to deliberately capture a copy of a damaged store, e.g.\npreserving evidence before a repair.","operationId":"create_backup_api_v1_databases__name__backups_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"verify","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Verify"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__BackupResponse"}}}},"404":{"description":"No such database, or it is offline"},"409":{"description":"Backup written but failed verification — the live store is damaged","content":{"application/json":{"example":{"detail":"backup of 'scout' written to /data/... but failed verification: index checkpoints(fts_key): IOException: Corrupt database file"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}},"get":{"tags":["cbdatabase","Backups"],"summary":"List backups","description":"List backup files for a database, newest first.","operationId":"list_backups_api_v1_databases__name__backups_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbdatabase__BackupListItem"},"title":"Response List Backups Api V1 Databases  Name  Backups Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}}},"/cbdatabase/api/v1/databases/{name}/execute":{"post":{"tags":["cbdatabase","Query"],"summary":"Run a write or DDL statement","description":"Run an INSERT, UPDATE, DELETE, or DDL statement.\n\nPass user values through `params`. `rows_changed` is best effort; null means the\nstatement ran but DuckDB did not report a count, which is normal for DDL.","operationId":"run_execute_api_v1_databases__name__execute_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__ExecuteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__ExecuteResponse"}}}},"400":{"description":"SQL error; detail carries the DuckDB message"},"404":{"description":"No such database, or it is offline"},"503":{"description":"Database busy; queue saturated, retry with backoff"},"504":{"description":"Statement exceeded its timeout"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}}},"/cbdatabase/api/v1/databases/{name}/probe":{"post":{"tags":["cbdatabase","Health"],"summary":"Probe a store's real liveness (and self-heal transient poison)","description":"Run a real ``SELECT 1`` against a store — a deep liveness check that catches a\npoisoned-but-alive DuckDB, the failure mode where the worker thread is running\nand ``/healthz`` reports \"online\" while every query throws ``InternalException``.\n\nThe probe flows through the normal query path, so **transient poison is\nself-healed** (connection reset + one retry) before this answers:\n- ``ok: true`` — the store answered. ``poison_events > 0`` means it hit poison\n  and recovered on its own.\n- ``ok: false, poisoned: true`` — the fault survived a connection reset, so the\n  corruption is on disk. Restore from a backup.\n\nCheap and safe to call on a schedule (e.g. a cbcron health tick); it briefly\ntakes the file lock like any query, then releases it on idle.","operationId":"probe_store_api_v1_databases__name__probe_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__ProbeResponse"}}}},"404":{"description":"No such database, or it is offline"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}}},"/cbdatabase/api/v1/databases/{name}/query":{"post":{"tags":["cbdatabase","Query"],"summary":"Run a read query","description":"Run a result-producing statement and get JSON-safe rows back.\n\nPass user values through `params` against `?` placeholders. The response values\nare already converted for JSON: timestamps are ISO strings, decimals are strings,\nblobs are base64.","operationId":"run_query_api_v1_databases__name__query_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__QueryRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__QueryResponse"}}}},"400":{"description":"SQL error; detail carries the DuckDB message"},"404":{"description":"No such database, or it is offline"},"503":{"description":"Database busy; queue saturated, retry with backoff"},"504":{"description":"Query exceeded its timeout"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}}},"/cbdatabase/api/v1/databases/{name}/restore":{"post":{"tags":["cbdatabase","Backups"],"summary":"Restore from a backup","description":"Restore a database in place from one of its backup files.\n\nThis replaces all current data with the backup contents. Consider taking a fresh\nbackup first so the pre-restore state stays recoverable.","operationId":"restore_backup_api_v1_databases__name__restore_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__RestoreRequest"}}}},"responses":{"204":{"description":"Restored"},"400":{"description":"Backup source not found"},"404":{"description":"No such database, or it is offline"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}}},"/cbdatabase/api/v1/databases/{name}/transaction":{"post":{"tags":["cbdatabase","Query"],"summary":"Run statements as one atomic transaction","description":"Run several statements as a single all-or-nothing transaction.\n\nThe statements run in order on one connection inside `BEGIN`/`COMMIT`, with no\nother work interleaving. They all commit together, or — if any one fails — the\nwhole batch is rolled back and **nothing** is applied. This is the right tool for\nmulti-table updates that must be atomic.\n\nKeep all the affected tables in the **same database**: atomicity is a\nper-database property here. A failed transaction returns 400 with\n`rolled_back: true`; the database is left exactly as it was before the call.","operationId":"run_transaction_api_v1_databases__name__transaction_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__TransactionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__TransactionResponse"}}}},"400":{"description":"A statement failed; the whole transaction was rolled back","content":{"application/json":{"example":{"detail":"Constraint Error: duplicate key ...","rolled_back":true}}}},"404":{"description":"No such database, or it is offline"},"503":{"description":"Database busy; queue saturated, retry with backoff"},"504":{"description":"Transaction exceeded its timeout"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}}},"/cbdatabase/api/v1/databases/{name}/verify":{"post":{"tags":["cbdatabase","Backups"],"summary":"Deep integrity check","description":"Read every column of every table and probe every index, looking for corruption.\n\n**This is not the health check.** `/healthz?deep=1` runs `SELECT 1`, which cannot\nsee corruption at all — measured in the lab, a store with corrupt index pages\nanswered `SELECT 1`, `count(*)`, a full column scan and `CHECKPOINT` without\ncomplaint. Use `/healthz` for liveness and this for \"should I trust this file\".\n\nTwo passes, because there are two independent corruption surfaces:\n\n- **data pages** — only a read of every column finds these; the optimizer prunes\n  a `SELECT count(*) FROM (SELECT * FROM t)` down to reading nothing\n- **index pages** — only an equality lookup finds these, because a full scan\n  never consults the index. The data can be byte-perfect while the index is\n  wrecked, which is the fleet's most common corruption failure\n\n`ok: false` is a strong signal: do not trust this file, restore it. `ok: true`\nmeans everything reachable through SQL read cleanly — not a proof of health.\n\n**Cost:** this holds the file lock for the length of a full read, so on a large\ncatalog it will block federation readers for that whole window. For a scheduled\ncheck, back up instead — `POST /backups` verifies the copy with no lock held.","operationId":"verify_database_api_v1_databases__name__verify_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__VerifyResponse"}}}},"404":{"description":"No such database, or it is offline"},"503":{"description":"Store is poisoned or the file is locked elsewhere"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}}},"/cbdatabase/health":{"get":{"tags":["cbdatabase","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__sysforge__core__health__HealthResponse"}}}}}}},"/cbdatabase/health/detailed":{"get":{"tags":["cbdatabase","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__sysforge__core__health__HealthResponse"}}}}}}},"/cbdatabase/healthz":{"get":{"tags":["cbdatabase","Health"],"summary":"Liveness check (optionally deep)","description":"Return service status and how many databases are online.\n\nPass ``?deep=1`` to additionally run a real ``SELECT 1`` against every online\nstore plus the control DB. A deep check reports ``status: \"degraded\"`` and lists\nthe offending stores in ``poisoned`` when any store fails its probe — so a\npoisoned-but-alive store can no longer masquerade as healthy (the gap behind a\n3-day silent outage). The shallow default stays cheap; use deep on a monitor\ntick or before routing critical work.","operationId":"healthz_healthz_get","parameters":[{"name":"deep","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Deep"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__cbdatabase__models__HealthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__HTTPValidationError"}}}}}}},"/cbdatabase/prime":{"get":{"tags":["cbdatabase","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__PrimeResponse"}}}}}}},"/cbdatabase/status":{"get":{"tags":["cbdatabase","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdatabase__StatusResponse"}}}}}}},"/cbdistricts/api/v1/admin/cache/warm-wikipedia":{"post":{"tags":["cbdistricts","Health"],"summary":"Start Wikipedia Cache Warm","description":"Start a background job to warm the Wikipedia cache for all congressional districts.\n\n**Behavior:**\n- Returns 202 Accepted immediately with a job_id\n- Job runs in background with 4 concurrent requests\n- Progress tracked in Redis (query with `/admin/cache/warm-wikipedia/status`)\n\n**Rate Limiting:**\n- Only one warming job can run at a time\n- Returns 409 Conflict if job already running\n- Wikipedia requests limited to 4 concurrent with 0.5s batch delay\n\n**Optional Filtering:**\n- Use `state_fips` to warm only one state's districts\n\n**Example:**\n```\nPOST /api/v1/admin/cache/warm-wikipedia?state_fips=06\n```","operationId":"warmWikipediaCache","parameters":[{"name":"state_fips","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^\\d{2}$"},{"type":"null"}],"description":"Optional 2-digit state FIPS to warm only that state","examples":["06","48","36"],"title":"State Fips"},"description":"Optional 2-digit state FIPS to warm only that state"}],"responses":{"202":{"description":"Cache warming job started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__CacheWarmJobResponse"}}}},"409":{"description":"A warming job is already running"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/admin/cache/warm-wikipedia/status":{"get":{"tags":["cbdistricts","Health"],"summary":"Get Wikipedia Cache Warm Status","description":"Get the status and progress of the current or most recent Wikipedia cache warming job.\n\n**Response:**\n- Job status (pending, running, completed, failed)\n- Progress counts (total, completed, failed, skipped)\n- Current GEOID being processed\n- Start/end times and duration\n\n**Query by Job ID:**\nUse `job_id` parameter to query a specific job instead of the active one.","operationId":"getWikipediaCacheWarmStatus","parameters":[{"name":"job_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Specific job ID to query (defaults to active/most recent)","title":"Job Id"},"description":"Specific job ID to query (defaults to active/most recent)"}],"responses":{"200":{"description":"Current job status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__CacheWarmStatusResponse"}}}},"404":{"description":"No job found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/cache":{"get":{"tags":["cbdistricts","Health"],"summary":"Cache Statistics","description":"Get detailed Redis cache statistics and key counts.\n\n**Response Includes:**\n- Connection status\n- Total cached keys\n- Keys by category (districts, demographics, etc.)\n- Memory usage (if available)","operationId":"getCacheStats","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"tags":["cbdistricts","Health"],"summary":"Clear All Cache","description":"Clear all Congressional Districts API cache entries.\n\n**Warning:** This affects performance temporarily as cache rebuilds.\n\n**Response:**\n- `status`: Operation status\n- `keys_deleted`: Number of cache entries removed","operationId":"clearAllCache","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbdistricts/api/v1/cache/reload-party":{"post":{"tags":["cbdistricts","Health"],"summary":"Reload Party Data","description":"Reload representative party data from Wikipedia cache files.\n\n**What This Does:**\n1. Reads cached Wikipedia data for all districts\n2. Extracts representative and party information\n3. Updates in-memory party cache\n4. Clears GeoJSON Redis cache to serve updated data\n\n**Use When:**\n- Wikipedia cache has been manually updated\n- Party data appears stale in GeoJSON responses\n- After running Wikipedia cache refresh scripts","operationId":"reloadPartyData","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbdistricts/api/v1/cache/stats":{"get":{"tags":["cbdistricts","Health"],"summary":"Cache Hit/Miss Statistics","description":"Get cache hit/miss statistics by endpoint for performance monitoring.\n\n**Response:**\nDictionary of endpoints with hit and miss counts.\nUse to identify caching effectiveness and optimize TTLs.","operationId":"getCacheHitMissStats","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbdistricts/api/v1/cache/{pattern}":{"delete":{"tags":["cbdistricts","Health"],"summary":"Invalidate Cache by Pattern","description":"Invalidate cache entries matching a glob pattern.\n\n**Pattern Examples:**\n| Pattern | Effect |\n|---------|--------|\n| `districts:*` | Clear all district cache |\n| `wikipedia:*` | Clear all Wikipedia cache |\n| `districts:detail:0601` | Clear specific district |\n| `demographics:*` | Clear all demographics cache |\n\n**Use Cases:**\n- Force refresh of specific data after updates\n- Clear stale Wikipedia data\n- Debug caching issues","operationId":"invalidateCachePattern","parameters":[{"name":"pattern","in":"path","required":true,"schema":{"type":"string","description":"Glob pattern to match cache keys","examples":["districts:*","wikipedia:*","districts:detail:0601"],"title":"Pattern"},"description":"Glob pattern to match cache keys"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/demographics":{"get":{"tags":["cbdistricts","Demographics"],"summary":"List District Demographics","description":"Retrieve paginated demographics data for congressional districts.\n\n**Data Source:** American Community Survey (ACS) 1-Year Estimates (2023)\n\n**Filtering Options:**\n- `state_fips`: Filter by 2-digit state FIPS code\n- `population_min/max`: Filter by population range\n- `income_min/max`: Filter by median household income range\n\n**Response Fields:**\n- `population`: Total population\n- `median_age`: Median age in years\n- `median_income`: Median household income (USD)\n- `high_school_plus`: % with high school diploma or higher\n- `bachelors_plus`: % with bachelor's degree or higher\n- `unemployment_rate`: Unemployment rate (%)\n\n**Example:**\n```\nGET /demographics?state_fips=06&income_min=80000\n```","operationId":"listDemographics","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum items per page","default":50,"title":"Limit"},"description":"Maximum items per page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Items to skip for pagination","default":0,"title":"Offset"},"description":"Items to skip for pagination"},{"name":"state_fips","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":2},{"type":"null"}],"description":"Filter by 2-digit state FIPS code","examples":["06","36"],"title":"State Fips"},"description":"Filter by 2-digit state FIPS code"},{"name":"population_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Minimum population filter","title":"Population Min"},"description":"Minimum population filter"},{"name":"population_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Maximum population filter","title":"Population Max"},"description":"Maximum population filter"},{"name":"income_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Minimum median household income (USD)","title":"Income Min"},"description":"Minimum median household income (USD)"},{"name":"income_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Maximum median household income (USD)","title":"Income Max"},"description":"Maximum median household income (USD)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__PaginatedResponse_DemographicsSummary_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/demographics/aggregations":{"get":{"tags":["cbdistricts","Demographics"],"summary":"Get Aggregated Statistics","description":"Get aggregated demographic statistics across all districts or filtered by state.\n\n**Response Includes:**\n- `count`: Number of districts\n- `population_total/avg/min/max`: Population statistics\n- `income_avg`: Average median household income\n- `unemployment_avg`: Average unemployment rate\n\n**Use Cases:**\n- Compare state demographics to national averages\n- Identify population distribution across districts\n- Analyze income disparities\n\n**Examples:**\n```\nGET /demographics/aggregations              # National statistics\nGET /demographics/aggregations?state_fips=06  # California only\n```","operationId":"getDemographicAggregations","parameters":[{"name":"state_fips","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":2},{"type":"null"}],"description":"Filter aggregations to a specific state","examples":["06","48"],"title":"State Fips"},"description":"Filter aggregations to a specific state"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__DemographicsAggregation"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/demographics/variables":{"get":{"tags":["cbdistricts","Demographics"],"summary":"List Available ACS Variables","description":"Get metadata for all available ACS demographic variables.\n\n**Variable Categories:**\n| Profile | Category | Examples |\n|---------|----------|----------|\n| DP02 | Social | Education, ancestry, language |\n| DP03 | Economic | Income, employment, occupation |\n| DP04 | Housing | Units, tenure, value, costs |\n| DP05 | Demographic | Age, sex, race, population |\n\n**Variable Naming Convention:**\n- Format: `DP{profile}_{sequence}E` for estimates\n- Format: `DP{profile}_{sequence}PE` for percentages\n- Example: `DP05_0001E` = Total Population\n\n**Response:**\nList of variables with code, name, category, and unit.","operationId":"listDemographicVariables","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbdistricts__DemographicVariable"},"type":"array","title":"Response Listdemographicvariables"}}}}}}},"/cbdistricts/api/v1/demographics/{geoid}":{"get":{"tags":["cbdistricts","Demographics"],"summary":"Get District Demographics","description":"Retrieve complete ACS demographics for a specific congressional district.\n\n**Response Includes All ACS Data Profiles:**\n- **DP02**: Social characteristics (households, education)\n- **DP03**: Economic characteristics (income, employment)\n- **DP04**: Housing characteristics (units, values)\n- **DP05**: Demographic characteristics (population, age, sex)\n\n**Variable Naming:**\nFields use ACS variable codes (e.g., `dp05_0001e` = Total Population).\nSee `/demographics/variables` for full variable metadata.\n\n**Example:**\n```\nGET /demographics/0601  # California's 1st District\n```","operationId":"getDistrictDemographics","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","description":"District GEOID (state FIPS + district number)","examples":["0601","3614"],"title":"Geoid"},"description":"District GEOID (state FIPS + district number)"}],"responses":{"200":{"description":"Complete demographics for the district","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__DemographicsDetail"},"example":{"geoid":"0601","year":2023,"name":"California's 1st Congressional District","dp05_0001e":761620,"dp05_0018e":42.1,"dp03_0062e":75000,"dp02_0068e":35.2,"dp03_0009pe":4.2}}}},"404":{"description":"District not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/districts":{"get":{"tags":["cbdistricts","Districts"],"summary":"List Congressional Districts","description":"Retrieve a paginated list of all 441 U.S. congressional districts (119th Congress).\n\n**Output Formats:**\n- `json` (default): Paginated response with district summaries\n- `geojson`: GeoJSON FeatureCollection for mapping applications\n\n**Filtering:**\n- Filter by state using `state_fips` (2-digit FIPS code)\n- Filter by population range using `population_min` and `population_max`\n\n**Sorting:**\n- Sort by any field using `sort` parameter\n- Control order with `order` (asc/desc)\n\n**Pagination:**\n- Use `limit` and `offset` for pagination\n- Default limit is 50, maximum is 500\n\n**Example Requests:**\n```\nGET /districts                              # All districts (first 50)\nGET /districts?state_fips=06                # California districts\nGET /districts?format=geojson&state_fips=26 # Michigan as GeoJSON\nGET /districts?population_min=800000        # High-population districts\n```","operationId":"listDistricts","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum number of districts to return per page","examples":[50,100,10],"default":50,"title":"Limit"},"description":"Maximum number of districts to return per page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of districts to skip for pagination","examples":[0,50,100],"default":0,"title":"Offset"},"description":"Number of districts to skip for pagination"},{"name":"state_fips","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":2},{"type":"null"}],"description":"Filter by 2-digit state FIPS code (e.g., '06' for California)","examples":["06","36","48"],"title":"State Fips"},"description":"Filter by 2-digit state FIPS code (e.g., '06' for California)"},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|geojson)$","description":"Output format: 'json' for paginated list, 'geojson' for GeoJSON FeatureCollection","examples":["json","geojson"],"default":"json","title":"Format"},"description":"Output format: 'json' for paginated list, 'geojson' for GeoJSON FeatureCollection"},{"name":"include_geometry","in":"query","required":false,"schema":{"type":"boolean","description":"Include GeoJSON geometry in JSON response (always true for geojson format)","default":false,"title":"Include Geometry"},"description":"Include GeoJSON geometry in JSON response (always true for geojson format)"},{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"Field to sort by","examples":["geoid","population","median_income"],"default":"geoid","title":"Sort"},"description":"Field to sort by"},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","description":"Sort order: 'asc' (ascending) or 'desc' (descending)","default":"asc","title":"Order"},"description":"Sort order: 'asc' (ascending) or 'desc' (descending)"},{"name":"population_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Minimum population filter","examples":[500000,800000],"title":"Population Min"},"description":"Minimum population filter"},{"name":"population_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Maximum population filter","examples":[1000000],"title":"Population Max"},"description":"Maximum population filter"},{"name":"tolerance","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"description":"Geometry simplification tolerance in degrees (geojson / include_geometry only). 0 = full resolution; > 0 shrinks the GeoJSON for vector rendering (~0.005 ≈ street-level, ~0.01 ≈ map-accurate, ~0.02 ≈ thumbnail). Topology-preserving.","examples":[0.0,0.005,0.01],"default":0.0,"title":"Tolerance"},"description":"Geometry simplification tolerance in degrees (geojson / include_geometry only). 0 = full resolution; > 0 shrinks the GeoJSON for vector rendering (~0.005 ≈ street-level, ~0.01 ≈ map-accurate, ~0.02 ≈ thumbnail). Topology-preserving."}],"responses":{"200":{"description":"List of congressional districts","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbdistricts__PaginatedResponse_DistrictSummary_"},{"$ref":"#/components/schemas/cbdistricts__GeoJSONFeatureCollection"}],"title":"Response Listdistricts"},"example":{"data":[{"geoid":"0601","namelsad":"Congressional District 1","state_fips":"06","district_num":"01","population":761620,"median_age":42.1,"median_income":75000}],"meta":{"total":441,"limit":50,"offset":0,"has_more":true}}}}},"400":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}},"x-code-samples":[{"lang":"Python","source":"\nimport requests\n\nresponse = requests.get(\n    \"https://districts.campaignbrain.dev/api/v1/districts\",\n    params={\"state_fips\": \"06\", \"limit\": 10}\n)\ndistricts = response.json()[\"data\"]\n"},{"lang":"JavaScript","source":"\nconst response = await fetch(\n  \"https://districts.campaignbrain.dev/api/v1/districts?state_fips=06\"\n);\nconst { data: districts } = await response.json();\n"},{"lang":"cURL","source":"curl \"https://districts.campaignbrain.dev/api/v1/districts?state_fips=06&limit=10\""}]}},"/cbdistricts/api/v1/districts/lookup":{"get":{"tags":["cbdistricts","Districts"],"summary":"Lookup District by Coordinates","description":"Find the congressional district whose boundary contains a latitude/longitude\npoint (point-in-polygon over the cartographic boundaries).\n\n**Example:**\n```\nGET /districts/lookup?lat=30.2672&lon=-97.7431\n```\nReturns the full district record (same shape as `GET /districts/{geoid}`).\nReturns 404 when the point falls outside every district (e.g. in the ocean).","operationId":"lookupDistrictByCoords","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","maximum":90.0,"minimum":-90.0,"description":"Latitude (WGS84/NAD83 degrees)","examples":[30.2672],"title":"Lat"},"description":"Latitude (WGS84/NAD83 degrees)"},{"name":"lon","in":"query","required":true,"schema":{"type":"number","maximum":180.0,"minimum":-180.0,"description":"Longitude (WGS84/NAD83 degrees)","examples":[-97.7431],"title":"Lon"},"description":"Longitude (WGS84/NAD83 degrees)"}],"responses":{"200":{"description":"District containing the point","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__DistrictSummary"}}}},"404":{"description":"No district contains the point (e.g. offshore)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/districts/search":{"get":{"tags":["cbdistricts","Districts"],"summary":"Search Districts by Name","description":"Search congressional districts by name using fuzzy matching.\n\n**Search Examples:**\n- \"California\" - finds all California districts\n- \"14th\" - finds all 14th districts across states\n- \"at large\" - finds at-large districts\n\n**Response:**\nReturns a list of matching districts sorted by relevance.\nLimited to the specified number of results (default 10, max 50).\n\n**Example:**\n```\nGET /districts/search?q=California%2014th\n```","operationId":"searchDistricts","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"description":"Search query (minimum 2 characters)","examples":["California","14th District","at large"],"title":"Q"},"description":"Search query (minimum 2 characters)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Maximum number of results to return","default":10,"title":"Limit"},"description":"Maximum number of results to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbdistricts__DistrictSummary"},"title":"Response Searchdistricts"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/districts/{geoid}":{"get":{"tags":["cbdistricts","Districts"],"summary":"Get District Details","description":"Retrieve complete information for a specific congressional district.\n\n**GEOID Format:**\nThe GEOID is a 4-5 character identifier:\n- First 2 digits: State FIPS code (e.g., '06' = California)\n- Last 2 digits: District number (e.g., '01' = 1st District)\n- At-large districts use '00' (e.g., '5000' = Vermont At-Large)\n\n**Response Includes:**\n- District identification (geoid, name, state, number)\n- Geographic area (land area, water area in square meters)\n- Demographics (population, median age, income, education, unemployment)\n\n**Example:**\n```\nGET /districts/0601  # California's 1st District\nGET /districts/5000  # Vermont At-Large\n```","operationId":"getDistrict","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","minLength":4,"maxLength":5,"description":"District GEOID (state FIPS + district number)","examples":["0601","3614","5000"],"title":"Geoid"},"description":"District GEOID (state FIPS + district number)"}],"responses":{"200":{"description":"Complete district information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__DistrictDetail"},"example":{"geoid":"0601","namelsad":"Congressional District 1","state_fips":"06","district_num":"01","aland":15285123456,"awater":123456789,"population":761620,"median_age":42.1,"median_income":75000,"bachelors_plus":35.2,"unemployment_rate":4.2}}}},"404":{"description":"District not found","content":{"application/json":{"example":{"error":"not_found","detail":"District 9999 not found","status_code":404},"schema":{"$ref":"#/components/schemas/cbdistricts__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/districts/{geoid}/geometry":{"get":{"tags":["cbdistricts","Districts"],"summary":"Get District Geometry","description":"Retrieve the GeoJSON geometry (boundary polygon) for a congressional district.\n\n**Response:**\nReturns a GeoJSON Feature with:\n- Feature type and ID\n- Properties (geoid, name)\n- Geometry (Polygon or MultiPolygon)\n\n**Content Type:**\nResponse uses `application/geo+json` media type per RFC 7946.\n\n**CRS:** Coordinates are `[lon, lat]` in NAD83 (EPSG:4269), which is\ninterchangeable with WGS84 at rendering scale (sub-meter difference). Source\nis the Census 1:500k cartographic boundaries.\n\n**Stable identity:** The Feature `id` and `properties.geoid` are both the\nGEOID. `ST_AsGeoJSON` emits deterministic coordinate ordering, and\nsimplification is deterministic for a given `tolerance` — same request →\nsame bytes, which Weave's render pipeline relies on.\n\n**Simplification:** Pass `?tolerance=` (degrees) to shrink the payload for\nvector rendering — see the parameter docs below.\n\n**Usage with MapLibre GL JS:**\n```javascript\nconst response = await fetch('/api/v1/districts/0601/geometry?tolerance=0.01');\nconst feature = await response.json();\nmap.addSource('district', { type: 'geojson', data: feature });\n```","operationId":"getDistrictGeometry","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","description":"District GEOID","examples":["0601","3614"],"title":"Geoid"},"description":"District GEOID"},{"name":"tolerance","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"description":"Douglas-Peucker simplification tolerance in degrees. 0 (default) = full resolution. > 0 shrinks the GeoJSON for vector rendering (e.g. Weave SVG): ~0.005 ≈ street-level, ~0.01 ≈ map-accurate (a typical district drops from ~100-270 KB to ~7-35 KB), ~0.02 ≈ thumbnail. Topology-preserving (polygons stay valid).","examples":[0.0,0.005,0.01],"default":0.0,"title":"Tolerance"},"description":"Douglas-Peucker simplification tolerance in degrees. 0 (default) = full resolution. > 0 shrinks the GeoJSON for vector rendering (e.g. Weave SVG): ~0.005 ≈ street-level, ~0.01 ≈ map-accurate (a typical district drops from ~100-270 KB to ~7-35 KB), ~0.02 ≈ thumbnail. Topology-preserving (polygons stay valid)."}],"responses":{"200":{"description":"GeoJSON Feature with district boundary polygon","content":{"application/json":{"schema":{}},"application/geo+json":{"example":{"type":"Feature","id":"0601","properties":{"geoid":"0601","namelsad":"Congressional District 1"},"geometry":{"type":"Polygon","coordinates":[[[-122.42,37.78],[-122.41,37.77],[-122.42,37.78]]]}}}}},"404":{"description":"District not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/districts/{geoid}/wiki":{"get":{"tags":["cbdistricts","Districts"],"summary":"Get District Wikipedia Data","description":"Retrieve Wikipedia-sourced information for a congressional district.\n\n**Data Includes:**\n- **Political Info**: Current representative, party affiliation\n- **Partisan Lean**: Cook Partisan Voting Index (e.g., R+5, D+10, EVEN)\n- **Geography**: Counties/areas within the district\n- **Summary**: 2-3 sentence description from Wikipedia\n\n**Data Freshness:**\nWikipedia data is cached with a 48-hour TTL. The response includes\n`cached_at` and `cache_expires_at` timestamps.\n\n**Example:**\n```\nGET /districts/1903/wiki  # Iowa's 3rd District\n```\n\n**Response Example:**\n```json\n{\n  \"representative\": \"Zach Nunn\",\n  \"party\": \"Republican\",\n  \"cook_pvi\": \"R+3\",\n  \"summary\": \"Iowa's 3rd congressional district is...\"\n}\n```","operationId":"getDistrictWikipedia","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","description":"District GEOID","examples":["0601","1903","3614"],"title":"Geoid"},"description":"District GEOID"}],"responses":{"200":{"description":"Wikipedia-sourced data for the district","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__WikipediaDistrictData"},"example":{"geoid":"1903","state_code":"ia","state_name":"Iowa","district_number":3,"is_at_large":false,"title":"Iowa's 3rd Congressional District","summary":"Iowa's 3rd congressional district is located in...","population":"818,737","representative":"Zach Nunn","party":"Republican","cook_pvi":"R+3","counties":["Dallas","Polk","Warren"],"wikipedia_url":"https://en.wikipedia.org/wiki/Iowa%27s_3rd_congressional_district","cached_at":"2024-01-15T10:30:00Z","cache_expires_at":"2024-01-17T10:30:00Z"}}}},"404":{"description":"District not found or Wikipedia data unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/health":{"get":{"tags":["cbdistricts","Health"],"summary":"Health Check","description":"Check API and database health status.\n\n**Response:**\n- `status`: Overall health status (healthy/degraded/unhealthy)\n- `timestamp`: Current server time (UTC ISO 8601)\n- `database`: Database connection pool status\n\n**Use for:**\n- Load balancer health checks\n- Monitoring and alerting\n- Uptime tracking","operationId":"healthCheck","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbdistricts/api/v1/info":{"get":{"tags":["cbdistricts","Health"],"summary":"API Information","description":"Get API version, data statistics, and cache status.\n\n**Response Includes:**\n- API version and name\n- Database record counts (districts, demographics, states)\n- Cache connection status\n- Data source information (Congress number, ACS year)\n\n**Use for:**\n- Verifying data freshness\n- Monitoring data counts\n- Integration diagnostics","operationId":"getApiInfo","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbdistricts/api/v1/intersections/compute/{layer_a}/{layer_b}":{"post":{"tags":["cbdistricts","Intersections"],"summary":"Compute Layer Intersections","description":"Trigger computation of intersections between two layers.\n\n**Parameters:**\n- `layer_a`: First layer ID (e.g., 'radio-coverage')\n- `layer_b`: Second layer ID (e.g., 'michigan-state-house')\n- `state`: Optional 2-letter state code to filter computation\n\n**Example:**\n```\nPOST /intersections/compute/radio-coverage/michigan-state-house?state=MI\n```\n\n**Response:**\nComputation results including count and duration.\n\n**Note:** This is a potentially long-running operation. For large layers,\nconsider using the CLI script instead.","operationId":"computeIntersections","parameters":[{"name":"layer_a","in":"path","required":true,"schema":{"type":"string","description":"First layer ID","title":"Layer A"},"description":"First layer ID"},{"name":"layer_b","in":"path","required":true,"schema":{"type":"string","description":"Second layer ID","title":"Layer B"},"description":"Second layer ID"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"2-letter state code to filter computation","examples":["MI","OH"],"title":"State"},"description":"2-letter state code to filter computation"}],"responses":{"200":{"description":"Computation results including intersection count and duration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__ComputeIntersectionsResponse"}}}},"404":{"description":"Layer not found","content":{"application/json":{"example":{"detail":"Layer 'unknown-layer' not found"}}}},"500":{"description":"Computation failed","content":{"application/json":{"example":{"detail":"Computation failed: insufficient memory"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/intersections/coverage/{layer}/{feature_id}":{"get":{"tags":["cbdistricts","Intersections"],"summary":"Get Features Covering a Feature","description":"Get all features from a specified layer that cover/overlap the target feature.\n\n**Use Case:** \"Which radio stations cover this district?\"\n\n**Parameters:**\n- `layer`: Target feature's layer ID (e.g., `michigan-state-house`)\n- `feature_id`: Target feature ID (e.g., `26020` for MI District 20)\n- `covering_layer`: Layer to get covering features from (e.g., `radio-coverage`)\n- `min_coverage`: Minimum coverage percentage filter (0-100, default 0)\n\n**Example Request:**\n```\nGET /intersections/coverage/michigan-state-house/26020?covering_layer=radio-coverage\n```\n\n**Example Response:**\n```json\n{\n  \"target_layer\": \"michigan-state-house\",\n  \"target_id\": \"26020\",\n  \"target_name\": \"State House District 20\",\n  \"covering_layer\": \"radio-coverage\",\n  \"coverage\": [\n    {\"layer\": \"radio-coverage\", \"feature_id\": \"5348\", \"name\": \"WNZK\", \"pct_coverage\": 100.0},\n    {\"layer\": \"radio-coverage\", \"feature_id\": \"42081\", \"name\": \"WLQV\", \"pct_coverage\": 100.0}\n  ],\n  \"total_features\": 7\n}\n```\n\n**Feature ID Format:**\n- District IDs include state FIPS prefix: `26020` = Michigan (26) + District 20\n- Radio station IDs are FCC facility IDs: `5348` = WNZK","operationId":"getCoveringFeatures","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","description":"Target feature's layer ID","title":"Layer"},"description":"Target feature's layer ID"},{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","description":"Target feature ID","title":"Feature Id"},"description":"Target feature ID"},{"name":"covering_layer","in":"query","required":true,"schema":{"type":"string","description":"Layer containing covering features","examples":["radio-coverage"],"title":"Covering Layer"},"description":"Layer containing covering features"},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage percentage (0-100)","default":0,"title":"Min Coverage"},"description":"Minimum coverage percentage (0-100)"}],"responses":{"200":{"description":"List of features from covering_layer that overlap the target feature","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__FeatureCoverageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/intersections/coverage/{layer}/{feature_id}/geojson":{"get":{"tags":["cbdistricts","Intersections"],"summary":"Get Coverage as GeoJSON","description":"Get intersection polygons for all features covering a target feature as GeoJSON.\n\n**Parameters:**\n- `layer`: Target feature's layer ID\n- `feature_id`: Target feature ID\n- `covering_layer`: Layer to get covering features from (required)\n- `min_coverage`: Minimum coverage percentage (0-100, default 0)\n\n**Example:**\n```\nGET /intersections/coverage/michigan-state-house/26020/geojson?covering_layer=radio-coverage\n```\n\n**Response:**\nGeoJSON FeatureCollection with intersection polygons.\n\n**Use with MapLibre GL JS:**\n```javascript\nmap.addSource('coverage', {\n  type: 'geojson',\n  data: '/api/v1/intersections/coverage/michigan-state-house/26020/geojson?covering_layer=radio-coverage'\n});\n```","operationId":"getCoverageGeoJSON","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","description":"Target feature's layer ID","title":"Layer"},"description":"Target feature's layer ID"},{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","description":"Target feature ID","title":"Feature Id"},"description":"Target feature ID"},{"name":"covering_layer","in":"query","required":true,"schema":{"type":"string","description":"Layer containing covering features","title":"Covering Layer"},"description":"Layer containing covering features"},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage percentage","default":0,"title":"Min Coverage"},"description":"Minimum coverage percentage"}],"responses":{"200":{"description":"GeoJSON FeatureCollection with intersection polygon geometries","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/intersections/covered/{layer}/{feature_id}":{"get":{"tags":["cbdistricts","Intersections"],"summary":"Get Features Covered by a Feature","description":"Get all features from a specified layer that are covered/overlapped by the target feature.\nThis is the inverse of `/coverage` - returns features that the target covers.\n\n**Use Case:** \"Which districts does this radio station reach?\"\n\n**Parameters:**\n- `layer`: Target feature's layer ID (e.g., `radio-coverage`)\n- `feature_id`: Target feature ID (e.g., `5348` for station WNZK)\n- `covered_layer`: Layer to get covered features from (e.g., `michigan-state-house`)\n- `min_coverage`: Minimum coverage percentage filter (0-100, default 0)\n\n**Example Request:**\n```\nGET /intersections/covered/radio-coverage/5348?covered_layer=michigan-state-house\n```\n\n**Example Response:**\n```json\n{\n  \"target_layer\": \"radio-coverage\",\n  \"target_id\": \"5348\",\n  \"target_name\": \"WNZK\",\n  \"covering_layer\": \"michigan-state-house\",\n  \"coverage\": [\n    {\"layer\": \"michigan-state-house\", \"feature_id\": \"26020\", \"name\": \"State House District 20\", \"pct_coverage\": 100.0},\n    {\"layer\": \"michigan-state-house\", \"feature_id\": \"26021\", \"name\": \"State House District 21\", \"pct_coverage\": 100.0},\n    {\"layer\": \"michigan-state-house\", \"feature_id\": \"26096\", \"name\": \"State House District 96\", \"pct_coverage\": 97.27}\n  ],\n  \"total_features\": 75\n}\n```\n\n**Note:** Station WNZK (facility 5348) covers 75 state house districts in Michigan,\nwith varying coverage percentages based on overlap area.","operationId":"getCoveredFeatures","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","description":"Target feature's layer ID","title":"Layer"},"description":"Target feature's layer ID"},{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","description":"Target feature ID","title":"Feature Id"},"description":"Target feature ID"},{"name":"covered_layer","in":"query","required":true,"schema":{"type":"string","description":"Layer containing covered features","examples":["michigan-state-house"],"title":"Covered Layer"},"description":"Layer containing covered features"},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage percentage (0-100)","default":0,"title":"Min Coverage"},"description":"Minimum coverage percentage (0-100)"}],"responses":{"200":{"description":"List of features from covered_layer that the target feature overlaps","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__FeatureCoverageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/intersections/polygon/{layer_a}/{id_a}/{layer_b}/{id_b}":{"get":{"tags":["cbdistricts","Intersections"],"summary":"Get Single Intersection Polygon","description":"Get the intersection geometry between two specific features as GeoJSON.\n\n**Parameters:**\n- `layer_a`: First feature's layer ID\n- `id_a`: First feature ID\n- `layer_b`: Second feature's layer ID\n- `id_b`: Second feature ID\n\n**Example:**\n```\nGET /intersections/polygon/radio-coverage/12345/michigan-state-house/26020\n```\n\n**Response:**\nGeoJSON Feature with intersection polygon and coverage metrics.","operationId":"getIntersectionPolygon","parameters":[{"name":"layer_a","in":"path","required":true,"schema":{"type":"string","description":"First feature's layer ID","title":"Layer A"},"description":"First feature's layer ID"},{"name":"id_a","in":"path","required":true,"schema":{"type":"string","description":"First feature ID","title":"Id A"},"description":"First feature ID"},{"name":"layer_b","in":"path","required":true,"schema":{"type":"string","description":"Second feature's layer ID","title":"Layer B"},"description":"Second feature's layer ID"},{"name":"id_b","in":"path","required":true,"schema":{"type":"string","description":"Second feature ID","title":"Id B"},"description":"Second feature ID"}],"responses":{"200":{"description":"GeoJSON Feature with intersection polygon and coverage metrics","content":{"application/json":{"schema":{}}}},"404":{"description":"No intersection found between the two features","content":{"application/json":{"example":{"detail":"No intersection found between radio-coverage/12345 and michigan-state-house/26020"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/intersections/stats":{"get":{"tags":["cbdistricts","Intersections"],"summary":"Get Intersection Cache Statistics","description":"Get aggregate statistics about the intersection cache.\n\n**Response Includes:**\n- Total cached intersections\n- Layer pairs with intersection counts\n- Unique feature counts\n- Database size\n- Last computation timestamp\n\n**Example:**\n```\nGET /intersections/stats\n```","operationId":"getIntersectionStats","responses":{"200":{"description":"Intersection cache statistics including layer pair counts and database size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__IntersectionStatsResponse"}}}}}}},"/cbdistricts/api/v1/layers":{"get":{"tags":["cbdistricts","Layers"],"summary":"List Available Layers","description":"List all available geographic data layers.\n\n**Layer Types:**\n| Scope | Description | Examples |\n|-------|-------------|----------|\n| `federal` | National boundaries | Congressional districts |\n| `state` | State-specific layers | State legislative districts |\n| `local` | Local boundaries | Counties, cities |\n\n**Response:**\n- Registry version for cache validation\n- Count and array of layer summaries\n- Each layer includes id, name, scope, and feature count","operationId":"listLayers","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__LayerListResponse"}}}}}}},"/cbdistricts/api/v1/layers/batch":{"post":{"tags":["cbdistricts","Layers"],"summary":"Batch Fetch Multiple Layers","description":"Fetch multiple layers in a single request.\n\n**Request Body:**\nArray of layer IDs to fetch.\n\n**Response:**\n- `count`: Number of successfully fetched layers\n- `layers`: Dictionary of layer_id -> GeoJSON FeatureCollection\n- `errors`: Array of failed layer requests (if any)\n\n**Example Request:**\n```json\n[\"mi_counties\", \"mi_state_house\", \"mi_cities\"]\n```\n\n**Use for:**\n- Loading multiple layers for a map\n- Reducing HTTP requests\n- Atomic layer fetching","operationId":"batchFetchLayers","parameters":[{"name":"include_geometry","in":"query","required":false,"schema":{"type":"boolean","description":"Include GeoJSON geometry in responses","default":true,"title":"Include Geometry"},"description":"Include GeoJSON geometry in responses"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"description":"List of layer IDs to fetch","examples":[["mi_counties","mi_cities"]],"title":"Layer Ids"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/layers/{layer_id}":{"get":{"tags":["cbdistricts","Layers"],"summary":"Get Layer Metadata","description":"Get detailed metadata for a specific geographic layer.\n\n**Response Includes:**\n- Layer identification (id, name, description)\n- Scope and state (for state/local layers)\n- Feature count and geometry type\n- Property schema (field names, types, descriptions)\n- Data source information\n\n**Use for:**\n- Understanding layer structure before fetching data\n- Building dynamic property displays\n- Validating layer availability","operationId":"getLayerMetadata","parameters":[{"name":"layer_id","in":"path","required":true,"schema":{"type":"string","description":"Layer identifier","examples":["mi_counties","cd119"],"title":"Layer Id"},"description":"Layer identifier"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__LayerMetadata"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/layers/{layer_id}/features":{"get":{"tags":["cbdistricts","Layers"],"summary":"List Layer Features","description":"List features in a layer with optional geometry.\n\n**Pagination:**\nUse `limit` and `offset` for large layers.\n\n**Geometry:**\nSet `include_geometry=true` to get GeoJSON geometry objects.\nGeometry is excluded by default for performance.\n\n**Example:**\n```\nGET /layers/mi_counties/features?limit=10&include_geometry=true\n```","operationId":"listLayerFeatures","parameters":[{"name":"layer_id","in":"path","required":true,"schema":{"type":"string","description":"Layer identifier","title":"Layer Id"},"description":"Layer identifier"},{"name":"include_geometry","in":"query","required":false,"schema":{"type":"boolean","description":"Include GeoJSON geometry in response","default":false,"title":"Include Geometry"},"description":"Include GeoJSON geometry in response"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"description":"Maximum features to return","default":100,"title":"Limit"},"description":"Maximum features to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Offset for pagination","default":0,"title":"Offset"},"description":"Offset for pagination"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__LayerFeatureListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/layers/{layer_id}/features/{feature_id}":{"get":{"tags":["cbdistricts","Layers"],"summary":"Get Single Feature","description":"Get a single feature by its ID.\n\n**Response:**\n- Feature ID and display name\n- All property values\n- GeoJSON geometry (if requested)\n\n**Example:**\n```\nGET /layers/mi_counties/features/26001  # Alcona County\n```","operationId":"getLayerFeature","parameters":[{"name":"layer_id","in":"path","required":true,"schema":{"type":"string","description":"Layer identifier","title":"Layer Id"},"description":"Layer identifier"},{"name":"feature_id","in":"path","required":true,"schema":{"type":"string","description":"Feature identifier","title":"Feature Id"},"description":"Feature identifier"},{"name":"include_geometry","in":"query","required":false,"schema":{"type":"boolean","description":"Include GeoJSON geometry in response","default":true,"title":"Include Geometry"},"description":"Include GeoJSON geometry in response"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__LayerFeature"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/layers/{layer_id}/geojson":{"get":{"tags":["cbdistricts","Layers"],"summary":"Get Layer as GeoJSON","description":"Get all layer features as a GeoJSON FeatureCollection.\n\n**Response:**\nRFC 7946 compliant GeoJSON FeatureCollection with all features.\n\n**Use with MapLibre GL JS:**\n```javascript\nmap.addSource('counties', {\n  type: 'geojson',\n  data: '/api/v1/layers/mi_counties/geojson'\n});\n```\n\n**Note:** Large layers may take longer to load.\nConsider using the `/features` endpoint with pagination for very large layers.","operationId":"getLayerGeoJSON","parameters":[{"name":"layer_id","in":"path","required":true,"schema":{"type":"string","description":"Layer identifier","title":"Layer Id"},"description":"Layer identifier"},{"name":"include_geometry","in":"query","required":false,"schema":{"type":"boolean","description":"Include GeoJSON geometry in response","default":true,"title":"Include Geometry"},"description":"Include GeoJSON geometry in response"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/layers/{layer_id}/refresh":{"post":{"tags":["cbdistricts","Layers"],"summary":"Refresh Layer Cache","description":"Clear all cached data for a specific layer.\n\n**What This Does:**\n- Invalidates all cached responses for the layer\n- Next request will fetch fresh data from database\n\n**Use When:**\n- Layer data has been updated\n- Cache appears stale\n- Troubleshooting display issues","operationId":"refreshLayerCache","parameters":[{"name":"layer_id","in":"path","required":true,"schema":{"type":"string","description":"Layer identifier to refresh","title":"Layer Id"},"description":"Layer identifier to refresh"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/contours":{"get":{"tags":["cbdistricts","Radio"],"summary":"List Available Contours","description":"List all available coverage contours with filtering.\n\n**Filtering Options:**\n- `service_type`: 'am' or 'fm'\n- `contour_type`: 'day', 'night', or 'service' (AM has all three, FM only has service)\n\n**Example:**\n```\nGET /radio/contours?service_type=am&contour_type=night&limit=50\n```","operationId":"listContours","parameters":[{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by am or fm","examples":["am","fm"],"title":"Service Type"},"description":"Filter by am or fm"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service","examples":["day","night","service"],"title":"Contour Type"},"description":"Contour type: day, night, or service"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Paginated list of coverage contours matching filters","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Listcontours"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/contours/geojson":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get Contours as GeoJSON","description":"Get coverage contours as a GeoJSON FeatureCollection.\n\n**Filtering Options:**\n- `service_type`: 'am' or 'fm'\n- `contour_type`: 'day', 'night', or 'service'\n\n**AM Contour Types:**\n- `day`: Daytime coverage (0.5 mV/m) - larger coverage area\n- `night`: Nighttime coverage (2.0 mV/m) - smaller coverage area\n- `service`: Default service contour\n\n**Example:**\n```\nGET /radio/contours/geojson?service_type=am&contour_type=day&limit=100\n```","operationId":"getContoursGeoJSON","parameters":[{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by am or fm","examples":["am","fm"],"title":"Service Type"},"description":"Filter by am or fm"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service","examples":["day","night","service"],"title":"Contour Type"},"description":"Contour type: day, night, or service"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum features","default":100,"title":"Limit"},"description":"Maximum features"}],"responses":{"200":{"description":"GeoJSON FeatureCollection with coverage contour polygons","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/contours/stats":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get Contour Statistics","description":"Get statistics about available contour data.\n\n**Response includes:**\n- Total contour counts by service type\n- AM contour breakdown by type (day/night/service)\n- Storage size information","operationId":"getContourStats","responses":{"200":{"description":"Contour counts by service type, AM breakdown by contour type, storage size","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Getcontourstats"}}}}}}},"/cbdistricts/api/v1/radio/contours/{callsign}":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get Contour by Callsign","description":"Get coverage contour for a station by callsign.\n\n**Parameters:**\n- `callsign`: Station callsign (e.g., WOOD, WXYZ, WSM)\n- `service_type`: 'am' or 'fm' (required for disambiguation)\n- `contour_type`: 'day', 'night', or 'service' (AM only)\n\n**Example:**\n```\nGET /radio/contours/WOOD?service_type=am&contour_type=day\n```","operationId":"getContourByCallsign","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","description":"Station callsign","title":"Callsign"},"description":"Station callsign"},{"name":"service_type","in":"query","required":false,"schema":{"type":"string","description":"Service type: am or fm","examples":["am","fm"],"default":"fm","title":"Service Type"},"description":"Service type: am or fm"},{"name":"contour_type","in":"query","required":false,"schema":{"type":"string","description":"Contour type: day, night, or service","examples":["day","night","service"],"default":"service","title":"Contour Type"},"description":"Contour type: day, night, or service"}],"responses":{"200":{"description":"GeoJSON Feature with the station's coverage contour polygon","content":{"application/json":{"schema":{}}}},"404":{"description":"Contour not found for callsign/type combination","content":{"application/json":{"example":{"detail":"Contour not found for WXYZ (fm/service)"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/districts/{geoid}":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get Stations Covering District","description":"Get all radio stations that cover a congressional district.","operationId":"getDistrictRadioCoverage","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","description":"Congressional district GEOID","title":"Geoid"},"description":"Congressional district GEOID"}],"responses":{"200":{"description":"Radio stations covering the district with signal strength data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__DistrictRadioCoverage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/districts/{geoid}/geojson":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get District Radio Coverage GeoJSON","description":"Get coverage contours for all stations covering a district as GeoJSON.\n\n**Filtering Options:**\n- `service_type`: Filter by 'am' or 'fm'\n- `contour_type`: For AM stations: 'day', 'night', or 'service'\n\n**Example:**\n```\nGET /radio/districts/2601/geojson?service_type=am&contour_type=night\n```","operationId":"getDistrictRadioGeoJSON","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","description":"Congressional district GEOID","title":"Geoid"},"description":"Congressional district GEOID"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by am or fm","examples":["am","fm"],"title":"Service Type"},"description":"Filter by am or fm"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service (AM only)","examples":["day","night","service"],"title":"Contour Type"},"description":"Contour type: day, night, or service (AM only)"}],"responses":{"200":{"description":"GeoJSON FeatureCollection with coverage contour polygons for the district","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/states/{state}/coverage":{"get":{"tags":["cbdistricts","Radio"],"summary":"State Coverage FeatureCollection","description":"cbdistricts#14: returns a GeoJSON FeatureCollection of every station in the\ngiven state, each with a (canonical or synthesized) coverage polygon. Used by\ncustomer-facing coverage maps to ensure 100% of in-network stations show a\ncontour — cbradio's wrapper filters down to in-network before serving.\n\nEach Feature's `properties.contour_source` ∈ {`fcc`, `class_transmitter`,\n`class_city`, `default_city`}. The collection's top-level `properties`\nincludes `feature_count`, `source_counts` (per-tier breakdown), and\n`synthesis_failed` (callsigns for which no tier was satisfiable).","operationId":"getStateCoverage","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","description":"2-letter state code (e.g. AZ)","title":"State"},"description":"2-letter state code (e.g. AZ)"}],"responses":{"200":{"description":"GeoJSON FeatureCollection with one Feature per station in the state","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/stations":{"get":{"tags":["cbdistricts","Radio"],"summary":"List Radio Stations","description":"List FCC-licensed radio stations with optional filtering.\n\n**Filtering Options:**\n- `state`: 2-letter state code (e.g., MI, OH)\n- `service_type`: am or fm\n- `min_power`: Minimum transmit power in kW\n- `has_contour`: Only stations with coverage polygon data\n\n**Response:**\nPaginated list of stations with callsign, frequency, power, and location.\n\n**Example:**\n```\nGET /radio/stations?state=MI&service_type=fm&min_power=10\n```","operationId":"listRadioStations","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by 2-letter state code","examples":["MI","OH","CA"],"title":"State"},"description":"Filter by 2-letter state code"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by am or fm","examples":["am","fm"],"title":"Service Type"},"description":"Filter by am or fm"},{"name":"min_power","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum transmit power in kilowatts","examples":[10,50],"title":"Min Power"},"description":"Minimum transmit power in kilowatts"},{"name":"has_contour","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only stations with coverage contour data","title":"Has Contour"},"description":"Only stations with coverage contour data"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Paginated list of radio stations matching filters","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Listradiostations"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/stations/by-callsign/{callsign}/contour":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get Station Coverage Contour by Callsign","description":"cbdistricts#14: callsign-keyed contour lookup. Always returns a Feature when\nthe station exists — canonical FCC polygon when available, otherwise a\nsynthesized polygon from the tier ladder (class_transmitter → class_city →\ndefault_city). `properties.contour_source` carries the tier.","operationId":"getStationContourByCallsign","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","description":"Station callsign (e.g. WMAC-AM)","title":"Callsign"},"description":"Station callsign (e.g. WMAC-AM)"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service (AM only)","examples":["day","night","service"],"title":"Contour Type"},"description":"Contour type: day, night, or service (AM only)"}],"responses":{"200":{"description":"GeoJSON Feature with coverage polygon geometry","content":{"application/json":{"schema":{}}}},"404":{"description":"Station not found or synthesis impossible"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/stations/geojson":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get Stations as GeoJSON","description":"Get all station locations as a GeoJSON FeatureCollection.\n\n**Response:**\nGeoJSON FeatureCollection with Point geometries for each station.\nProperties include callsign, frequency, power, and service type.\n\n**Use with MapLibre GL JS:**\n```javascript\nmap.addSource('stations', {\n  type: 'geojson',\n  data: '/api/v1/radio/stations/geojson?state=MI'\n});\n```","operationId":"getStationsGeoJSON","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"State"},"description":"Filter by state"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by am or fm","title":"Service Type"},"description":"Filter by am or fm"}],"responses":{"200":{"description":"GeoJSON FeatureCollection with Point geometries for each station","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/stations/{facility_id}":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get Station Details","description":"Get complete information for a single radio station by FCC facility ID.","operationId":"getRadioStation","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","description":"FCC facility identifier","title":"Facility Id"},"description":"FCC facility identifier"}],"responses":{"200":{"description":"Station details including callsign, frequency, power, and location","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__RadioStation"}}}},"404":{"description":"Station not found","content":{"application/json":{"example":{"detail":"Station 99999 not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/stations/{facility_id}/contour":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get Station Coverage Contour","description":"Get coverage contour polygon for a station as GeoJSON Feature.\n\n**For AM stations**, you can specify the contour type:\n- `day`: Daytime coverage (0.5 mV/m field strength)\n- `night`: Nighttime coverage (2.0 mV/m field strength)\n- `service`: Default service contour\n\n**For FM stations**, only `service` contour is available.\n\n**Example:**\n```\nGET /radio/stations/73604/contour?contour_type=day\n```","operationId":"getStationContour","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","description":"FCC facility identifier","title":"Facility Id"},"description":"FCC facility identifier"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service (AM only)","examples":["day","night","service"],"title":"Contour Type"},"description":"Contour type: day, night, or service (AM only)"}],"responses":{"200":{"description":"GeoJSON Feature with coverage polygon geometry","content":{"application/json":{"schema":{}}}},"404":{"description":"Contour not available for station","content":{"application/json":{"example":{"detail":"Contour not available for station 99999"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/stations/{facility_id}/districts":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get Districts Covered by Station","description":"Get all congressional districts covered by a station's signal.","operationId":"getStationDistricts","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","description":"FCC facility identifier","title":"Facility Id"},"description":"FCC facility identifier"}],"responses":{"200":{"description":"List of congressional districts covered by the station's signal","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__StationDistrictCoverage"}}}},"404":{"description":"Station not found","content":{"application/json":{"example":{"detail":"Station 99999 not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/radio/stats":{"get":{"tags":["cbdistricts","Radio"],"summary":"Get Radio Statistics","description":"Get aggregate statistics about the radio coverage dataset.\n\n**Response Includes:**\n- Total station count\n- Stations with coverage contours\n- Coverage relationships (station-district pairs)\n- Breakdown by service type and state\n- Power statistics (min, max, avg)","operationId":"getRadioStats","responses":{"200":{"description":"Aggregate statistics about the radio coverage dataset","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__RadioStatsResponse"}}}}}}},"/cbdistricts/api/v1/states":{"get":{"tags":["cbdistricts","States"],"summary":"List All States","description":"List all states with congressional district counts and aggregate demographics.\n\n**Response Includes:**\n- State FIPS code and name\n- Number of congressional districts (1-52)\n- Total population across all districts\n- Average median household income\n\n**Sorting:**\nResults are sorted by state name alphabetically.\n\n**Note:**\nAt-large states (1 district) include: Alaska, Delaware, Montana,\nNorth Dakota, South Dakota, Vermont, Wyoming, and DC.","operationId":"listStates","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbdistricts__StateInfo"},"type":"array","title":"Response Liststates"}}}}}}},"/cbdistricts/api/v1/states/{state_fips}":{"get":{"tags":["cbdistricts","States"],"summary":"Get State Details","description":"Get detailed information for a specific state including demographics and area.\n\n**Response Includes:**\n- State identification (FIPS, name)\n- District count\n- Population and demographic averages\n- Total land and water area\n\n**Common State FIPS Codes:**\n| State | FIPS | State | FIPS |\n|-------|------|-------|------|\n| California | 06 | Texas | 48 |\n| Florida | 12 | New York | 36 |\n| Pennsylvania | 42 | Illinois | 17 |\n\n**Example:**\n```\nGET /states/06  # California\nGET /states/48  # Texas\n```","operationId":"getState","parameters":[{"name":"state_fips","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":2,"description":"2-digit state FIPS code","examples":["06","36","48"],"title":"State Fips"},"description":"2-digit state FIPS code"}],"responses":{"200":{"description":"Detailed state information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__StateDetail"}}}},"404":{"description":"State not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/states/{state_fips}/boundary":{"get":{"tags":["cbdistricts","States"],"summary":"Get State Boundary (GeoJSON FeatureCollection)","description":"Get **all** of a state's congressional-district boundaries as a single GeoJSON\nFeatureCollection — the geographic sibling of `GET /districts/{geoid}/geometry`,\nbuilt for vector rendering (Weave SVG choropleths) and slippy-map sources.\n\nUnlike `GET /states/{state_fips}/districts` (paginated summaries) this returns\nthe whole state in one un-paginated payload (no state has more than 52\ndistricts) with full geometry.\n\n**Simplification:** pass `?tolerance=` (degrees) to shrink the payload — e.g.\nCalifornia's 52 districts are ~103 KB at `tolerance=0.01`. 0 (default) is full\nresolution. Topology-preserving.\n\n**CRS:** coordinates are `[lon, lat]` in NAD83 (EPSG:4269), interchangeable\nwith WGS84 at rendering scale (Census 1:500k cartographic boundaries).\n\n**Determinism:** features are ordered by GEOID ascending and each carries a\nstable `id`/`properties.geoid`; `ST_AsGeoJSON` + simplification are\ndeterministic, so the same request returns the same bytes (Weave relies on\nthis). Each feature's `properties` also include name, population, median age,\nmedian income, and party/representative/Cook PVI where available — enough to\nchoropleth without a second call.\n\n**Example:**\n```\nGET /api/v1/states/19/boundary?tolerance=0.01   # Iowa, simplified\nGET /api/v1/states/06/boundary                  # California, full resolution\n```\n\nFor the whole nation in one call, use\n`GET /api/v1/districts?format=geojson&limit=500&tolerance=0.01` (all 441\ndistricts; ~1.2 MB at tolerance 0.01).","operationId":"getStateBoundary","parameters":[{"name":"state_fips","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":2,"description":"2-digit state FIPS code","examples":["06","19","48"],"title":"State Fips"},"description":"2-digit state FIPS code"},{"name":"tolerance","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"description":"Douglas-Peucker simplification tolerance in degrees. 0 (default) = full resolution. > 0 shrinks the GeoJSON for vector rendering (~0.005 ≈ street-level, ~0.01 ≈ map-accurate, ~0.02 ≈ thumbnail). Topology-preserving.","examples":[0.0,0.005,0.01],"default":0.0,"title":"Tolerance"},"description":"Douglas-Peucker simplification tolerance in degrees. 0 (default) = full resolution. > 0 shrinks the GeoJSON for vector rendering (~0.005 ≈ street-level, ~0.01 ≈ map-accurate, ~0.02 ≈ thumbnail). Topology-preserving."}],"responses":{"200":{"description":"GeoJSON FeatureCollection of every district in the state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__GeoJSONFeatureCollection"}},"application/geo+json":{"example":{"type":"FeatureCollection","features":[{"type":"Feature","id":"1901","properties":{"geoid":"1901","name":"Congressional District 1","state_fips":"19","party":"Republican"},"geometry":{"type":"Polygon","coordinates":[[[-91.1,41.4],[-90.2,41.5]]]}}]}}}},"404":{"description":"State not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/api/v1/states/{state_fips}/districts":{"get":{"tags":["cbdistricts","States"],"summary":"Get State Districts","description":"Get all congressional districts for a specific state.\n\n**Response:**\nList of district summaries including:\n- GEOID and name\n- Population, median age, median income\n\n**Pagination:**\nUse `limit` and `offset` for large states (California has 52 districts).\n\n**Example:**\n```\nGET /states/06/districts       # All California districts\nGET /states/48/districts       # All Texas districts\nGET /states/50/districts       # Vermont (at-large, 1 district)\n```","operationId":"getStateDistricts","parameters":[{"name":"state_fips","in":"path","required":true,"schema":{"type":"string","description":"2-digit state FIPS code","examples":["06","48"],"title":"State Fips"},"description":"2-digit state FIPS code"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum districts to return","default":50,"title":"Limit"},"description":"Maximum districts to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Districts to skip","default":0,"title":"Offset"},"description":"Districts to skip"}],"responses":{"200":{"description":"List of districts in the state","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbdistricts__DistrictSummary"},"title":"Response Getstatedistricts"}}}},"404":{"description":"State not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HTTPValidationError"}}}}}}},"/cbdistricts/health":{"get":{"tags":["cbdistricts","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HealthResponse"}}}}}}},"/cbdistricts/health/detailed":{"get":{"tags":["cbdistricts","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__HealthResponse"}}}}}}},"/cbdistricts/prime":{"get":{"tags":["cbdistricts","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__PrimeResponse"}}}}}}},"/cbdistricts/status":{"get":{"tags":["cbdistricts","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbdistricts__StatusResponse"}}}}}}},"/cbea/api/v1/chat":{"post":{"tags":["cbea","Agent"],"summary":"Send a chat message","description":"Synchronous chat endpoint — send a message and receive a response. Suitable for MCP tool-use and simple integrations.","operationId":"chat_api_v1_chat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__ChatResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}}},"/cbea/api/v1/chat/stream":{"post":{"tags":["cbea","Agent"],"summary":"Streaming chat via SSE","description":"SSE streaming chat — send a message and receive streamed events (start, text, done, error) as Server-Sent Events.","operationId":"chat_stream_api_v1_chat_stream_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}}},"/cbea/api/v1/flows":{"get":{"tags":["cbea","Flows"],"summary":"List registered flows","description":"Names and descriptions of every flow in the registry.","operationId":"list_flows_api_v1_flows_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbea/api/v1/flows/claims":{"get":{"tags":["cbea","Flows"],"summary":"List active conversation claims","description":"Contacts currently claimed by a live flow (their inbound messages bypass the agent loop).","operationId":"list_claims_api_v1_flows_claims_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbea/api/v1/flows/claims/{contact_id}/release":{"post":{"tags":["cbea","Flows"],"summary":"Release a contact's active claim","description":"Force-return the contact to the agent loop (status -> released; the claim row is kept).","operationId":"release_claim_api_v1_flows_claims__contact_id__release_post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}}},"/cbea/api/v1/flows/{flow_name}/start":{"post":{"tags":["cbea","Flows"],"summary":"Start a flow for a contact","description":"Claims the contact's conversation and sends the flow's entry prompt over SMS. 409 if the contact already has a live flow — pass supersede=true to cancel it and start this one instead.","operationId":"start_flow_api_v1_flows__flow_name__start_post","parameters":[{"name":"flow_name","in":"path","required":true,"schema":{"type":"string","title":"Flow Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__StartFlowRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}}},"/cbea/api/v1/ops/claims":{"get":{"tags":["cbea","Ops"],"summary":"Live external conversation claims + bus consumer health","description":"External claims (cbea#49): while another service (cbconvo) holds a live claim on a phone — claim.taken/claim.released events on the mesh-bus topic — cbea neither answers inbounds from that phone nor initiates to it, until the release or the claim's expires_at, whichever comes first. This lists the claims currently suppressing, plus the bus consumer's connection/telemetry state. Read-only: claims are owned by their publisher and age out on their own; the manual standdown above remains the operator's lever.","operationId":"list_external_claims_api_v1_ops_claims_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbea/api/v1/ops/standdown":{"get":{"tags":["cbea","Ops"],"summary":"List live standdowns","description":"All live standdowns.","operationId":"list_standdowns_api_v1_ops_standdown_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbea/api/v1/ops/standdown/{contact_id}":{"post":{"tags":["cbea","Ops"],"summary":"Mute EA's agent loop for a contact (human has the thread)","description":"While live, inbound from this contact is logged but the agent sends nothing. Flows still route — release the contact's claims separately for total silence. A repeat call replaces the window.","operationId":"set_standdown_api_v1_ops_standdown__contact_id__post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__StanddownRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}},"delete":{"tags":["cbea","Ops"],"summary":"Lift a contact's standdown","description":"Lift a standdown early.","operationId":"lift_standdown_api_v1_ops_standdown__contact_id__delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}}},"/cbea/api/v1/sensors/observations":{"get":{"tags":["cbea","Sensors"],"summary":"List observations","description":"EA's observed world events, newest first. Filter by sensor name, actor, or an ISO `since` timestamp.","operationId":"list_observations_api_v1_sensors_observations_get","parameters":[{"name":"sensor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sensor"}},{"name":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}}},"/cbea/api/v1/sensors/sweep":{"post":{"tags":["cbea","Sensors"],"summary":"Run a sensor sweep","description":"Runs every collector (CDN clicks, inbound SMS, email sends, flow claims, search activity) and persists genuinely new observations. Idempotent — collectors re-read their whole window and dedupe on (sensor, key), so overlapping or missed cadence ticks self-heal. Triggered by cbcron every few minutes.","operationId":"run_sweep_api_v1_sensors_sweep_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbea/api/v1/sensors/sweeps":{"get":{"tags":["cbea","Sensors"],"summary":"List recent sweeps","description":"Sweep run history — cadence health, new-event counts, and per-collector errors.","operationId":"list_sweeps_api_v1_sensors_sweeps_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}}},"/cbea/api/v1/traces":{"get":{"tags":["cbea","Traces"],"summary":"List recent traces","description":"Paginated list of trace tree summaries (one entry per trace_id),\nordered by most recent root span start time.\n\nEach inbound message produces one trace whose root span is\n``cbea.handle_message``. Per-stage child spans (identity, context\nload, cbai call, render, transport send) and per-tool\n``cbea.tool.{name}`` spans nest under it.\n\nSpans are stored in the same SQLite file as conversation history\n(different table). Use ``GET /api/v1/traces/{trace_id}`` to drill\ninto one tree.","operationId":"list_traces_api_v1_traces_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"start_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Time"}},{"name":"end_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Time"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__TraceListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}}},"/cbea/api/v1/traces/{trace_id}":{"get":{"tags":["cbea","Traces"],"summary":"Get a full trace tree","description":"Return the full nested span tree for a given trace ID.\n\nThe shape is:\n\n```json\n{\n  \"span\": { ... root span ... },\n  \"children\": [\n    {\"span\": {...}, \"children\": [...]}\n  ]\n}\n```\n\nFor an inbound message you'll typically see:\n  - ``cbea.handle_message`` (root)\n    - ``cbea.identity.resolve``\n    - ``cbea.context.load_profile``\n    - ``cbea.cbai.tool_use_chat``\n      - ``cbea.tool.{name}`` (one per tool call)\n    - ``cbea.render``","operationId":"get_trace_api_v1_traces__trace_id__get","parameters":[{"name":"trace_id","in":"path","required":true,"schema":{"type":"string","title":"Trace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__SpanTree"}}}},"404":{"description":"Trace not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}}},"/cbea/api/v1/webhook/email":{"post":{"tags":["cbea","Webhook"],"summary":"Receive inbound email message","description":"Accepts one inbound email from the support@ mailbox pump (scripts/email_reply_watch, cbcron). Resolves the sender against the cbauth roster; known contacts route through the same flow/agent dispatch as SMS, with replies threaded onto the sender's message. Unknown senders are reported back as ignored so the pump can alert/ack instead (cbea#36).","operationId":"email_inbound_api_v1_webhook_email_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbea/api/v1/webhook/sms":{"post":{"tags":["cbea","Webhook"],"summary":"Receive inbound SMS message","description":"Accepts inbound SMS messages from Twilio (form-encoded) or JSON. Parses and routes to the assistant in the background.","operationId":"sms_inbound_api_v1_webhook_sms_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbea/api/v1/webhook/whatsapp":{"get":{"tags":["cbea","Webhook"],"summary":"WhatsApp webhook verification","description":"Meta/Twilio handshake endpoint — validates the webhook challenge token and returns it to complete registration.","operationId":"whatsapp_verify_api_v1_webhook_whatsapp_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["cbea","Webhook"],"summary":"Receive inbound WhatsApp message","description":"Accepts inbound WhatsApp messages from Meta/Twilio. Parses the payload and processes it in the background, returning 200 immediately.","operationId":"whatsapp_inbound_api_v1_webhook_whatsapp_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbea/api/v1/workflows/daily-digest":{"post":{"tags":["cbea","Workflows"],"summary":"Daily digest of Brad's GH activity","description":"Pulls GH issues + PRs touching `digest_brad_login` author or `digest_cj_login` assignee in the last 24h across the repo set (`digest_repos`), formats a markdown body, and delivers via cbsms to `digest_recipient_phone`. Cat 3 deterministic shape — no agent loop. Triggered by cbcron at 0 12 * * * UTC.","operationId":"daily_digest_api_v1_workflows_daily_digest_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","description":"If true, build the report but skip delivery. Useful for cbcron preflight.","default":false,"title":"Dry Run"},"description":"If true, build the report but skip delivery. Useful for cbcron preflight."},{"name":"window_hours","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"description":"Look-back window in hours. Default 24 for the daily run.","default":24,"title":"Window Hours"},"description":"Look-back window in hours. Default 24 for the daily run."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__DigestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HTTPValidationError"}}}}}}},"/cbea/health":{"get":{"tags":["cbea","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HealthResponse"}}}}}}},"/cbea/health/detailed":{"get":{"tags":["cbea","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__HealthResponse"}}}}}}},"/cbea/prime":{"get":{"tags":["cbea","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__PrimeResponse"}}}}}}},"/cbea/status":{"get":{"tags":["cbea","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbea__StatusResponse"}}}}}}},"/cbelections/api/v1/elections/calendar":{"get":{"tags":["cbelections","Elections"],"summary":"Election dates by state and cycle","description":"The fleet's election calendar, sourced from the FEC's published federal election dates (cbelections#77).\n\n**This is the federal calendar.** Most states hold their state-office primary on the same day as the federal one, so the date is right for statewide and federal contests — but it is a floor, not a ceiling, for down-ballot and municipal races. An absent row does not mean there is no election.\n\nRunoff rows (`R`, `GR`, `SR`, `SGR`) carry a scheduled date for a contest that only happens if no candidate takes a majority.","operationId":"list_calendar_api_v1_elections_calendar_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state code","title":"State"},"description":"Two-letter state code"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Election year, e.g. 2026","title":"Cycle"},"description":"Election year, e.g. 2026"},{"name":"election_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"FEC type id — one of ['C', 'CON', 'G', 'GR', 'P', 'PR', 'R', 'SC', 'SG', 'SGR', 'SP', 'SR']","title":"Election Type"},"description":"FEC type id — one of ['C', 'CON', 'G', 'GR', 'P', 'PR', 'R', 'SC', 'SG', 'SGR', 'SP', 'SR']"},{"name":"office","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"H | S | P","title":"Office"},"description":"H | S | P"},{"name":"on_or_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Only elections on or after this date","title":"On Or After"},"description":"Only elections on or after this date"},{"name":"specials_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only specials","default":false,"title":"Specials Only"},"description":"Only specials"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CalendarResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/calendar/lookup":{"get":{"tags":["cbelections","Elections"],"summary":"The single election a buy is about","description":"Resolves one election for (state, cycle, kind), for callers that need a date to derive from — cbradio uses it for the lowest-unit-charge window in 47 U.S.C. 315(b).\n\n**Returns one row or none, never a guess.** Where a state holds the same kind of election for more than one office on the same day the rows agree and the earliest is returned; where they genuinely disagree, `ambiguous` is true and `election` is null, because a consumer deriving a legal window must not be handed a coin flip.","operationId":"lookup_api_v1_elections_calendar_lookup_get","parameters":[{"name":"state","in":"query","required":true,"schema":{"type":"string","description":"Two-letter state code","title":"State"},"description":"Two-letter state code"},{"name":"cycle","in":"query","required":true,"schema":{"type":"integer","description":"Election year, e.g. 2026","title":"Cycle"},"description":"Election year, e.g. 2026"},{"name":"kind","in":"query","required":false,"schema":{"type":"string","description":"general | primary — the everyday vocabulary, not FEC type ids","default":"general","title":"Kind"},"description":"general | primary — the everyday vocabulary, not FEC type ids"},{"name":"office","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional H | S | P narrowing","title":"Office"},"description":"Optional H | S | P narrowing"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CalendarLookupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/channels":{"get":{"tags":["cbelections","Elections"],"summary":"Every election channel this engine knows about","description":"The channel directory — identity, feed shape, and current phase.\n\nRegistered ahead of the ``/{channel}/…`` routes for readability; it cannot\nactually be shadowed by them (one path segment vs two).\n\nCheap by construction: reads the channel YAMLs only, never opens a\nworkspace DB. A dormant channel's DB may not exist at all, and an operator\nlisting every election must not depend on 20 DuckDB opens.","operationId":"list_election_channels_api_v1_elections_channels_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ChannelsResponse"}}}}}}},"/cbelections/api/v1/elections/{channel}/calls":{"get":{"tags":["cbelections","Elections"],"summary":"Standing race calls made by the desk","description":"The desk's standing calls: one per contest, newest superseding. A call\nis PUBLIC once made — it is our published editorial act, and the results\npage chips it — so this read carries no gate beyond the channel's own.\nEmpty is the normal state for most of any night.","operationId":"calls_list_api_v1_elections__channel__calls_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CallsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}},"post":{"tags":["cbelections","Elections"],"summary":"Make (or retract) a race call — append-only","description":"Append one call. Same doctrine as the manual journal: corrections\nsupersede, `retract: true` hides the contest's standing call, history is\nnever rewritten. Write access rides the same auth as manual entry — the\nbearer key when configured, plus the staff-gated proxy route on the\ncbloom side.","operationId":"calls_add_api_v1_elections__channel__calls_post","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CallEntryIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CallEntry"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/clarity":{"get":{"tags":["cbelections","Elections"],"summary":"County ENR side-feed (Clarity) — official county results beside the state feed","description":"The Clarity county side table, when the channel has one.\n\nBorn on FL primary night (cbapex#10): the DOE statewide extract sat\nheader-only 50+ minutes past close while Martin County published its\ncomplete official count on Clarity. The side-feed cron\n(``cbintel/scripts/elections/fl_2026_clarity_wire.py``) lands those rows\nin a SIBLING workspace — ``<catalog>clarity/<catalog>clarity.db`` — which\nthis endpoint reads read-only via ``results_clarity_current`` (each\ncounty's newest version only). The DOE tables and their versioning are a\ndifferent feed and are untouched by any of this.\n\n404 when the channel has no side-feed workspace — most channels never\nwill, and that is a normal state, not a fault.","operationId":"clarity_side_feed_api_v1_elections__channel__clarity_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CountyFeedResponse"}}}},"404":{"description":"channel has no Clarity side-feed workspace — most channels never will; a normal state, not a fault"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/contacts":{"get":{"tags":["cbelections","Elections"],"summary":"County clerk phone numbers for the call sheet","description":"Clerk election-office numbers for click-to-call (cbelections#32): the\nRace Tracker and warroom render these as `tel:` links so staff can ring a\ncounty and key its numbers in one motion. `call_sheet: true` entries are\nthe priority counties from the Dave watchlist matrix; `verified: false`\nmeans the number came from a search snippet because the county site\nbot-gates fetchers — confirm on first dial. An absent file is the normal\nstate for channels without a registry, not an error.","operationId":"county_contacts_api_v1_elections__channel__contacts_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ContactsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/contests":{"get":{"tags":["cbelections","Elections"],"summary":"Contests available on a channel","description":"The channel's contests — what a client can request a matrix for.\n``candidate_count`` is filled from the current snapshot when the contest\nhas rows. Default scope is the headline list (7-app contract, unchanged);\n``scope=all`` adds every down-ballot contest the snapshot carries\n(state-leg, US House, ...) with synthesized labels.","operationId":"list_contests_api_v1_elections__channel__contests_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","pattern":"^(headline|all)$","description":"'headline' (default, the frozen bundle shape) or 'all' — every contest present in the current snapshot (headline first, then down-ballot by office_code), for down-ballot pickers.","default":"headline","title":"Scope"},"description":"'headline' (default, the frozen bundle shape) or 'all' — every contest present in the current snapshot (headline first, then down-ballot by office_code), for down-ballot pickers."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ContestsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/datasources":{"get":{"tags":["cbelections","Elections"],"summary":"Per-county datasource health for a county-fanout channel","description":"Which county feeds are actually live, weighted by registered voters.\n\nMichigan publishes no statewide feed, so results are assembled by fanning\nout over 83 county vehicles (cbelections#7). That makes ingest readiness a\ndistribution rather than a boolean, and it is the number an operator wants\nbefore polls close: 8 counties live is 28% of the electorate, not 10%.\n\nThe payload is the roster-autofill cron's generated status file, reshaped\ninto the response contract. **Read-only** — nothing here triggers ingest.\n\nAlways 200. ``available: false`` means the channel has no fanout layer (a\nstatewide feed needs none) or the file has not been generated yet; both are\nnormal states a dashboard should render, not errors.","operationId":"datasources_api_v1_elections__channel__datasources_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__DatasourcesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/eta":{"get":{"tags":["cbelections","Elections"],"summary":"County landing-time expectations (measured model + known publishers)","description":"The choreography model, v1: per-vehicle first-tally windows measured\nfrom the 2026-08-04 night (pre-armed feed counties only — the discovered\nregime measures our pipeline, not the county) plus the curated\nknown-publisher table. Pages turn this into \"expected by close+2h\" /\n\"overdue\" / \"do not expect election-night numbers\" badges.\n\nServed from a committed artifact (`mi-eta-model.json`) — one night is a\nbaseline, not a distribution; the file is regenerated per cycle by the\nretro's arrival analysis, and its `derived_from` says exactly which\nnight the numbers earned themselves on.","operationId":"eta_api_v1_elections__channel__eta_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__EtaResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/jobs":{"get":{"tags":["cbelections","Elections"],"summary":"The cbcron jobs that feed this channel","description":"Which scheduled jobs drive this channel, by pipeline role.\n\nThe channel YAML's ``cbcron_jobs`` block is the authoritative link — job\nregistration writes the UUIDs back into it, so it cannot drift from what\ncbcron actually holds the way a name-matching heuristic would. A channel\ncarries ~11 of these (results/social/markets each in baseline + burst form,\nplus the status file, bigboard, war-room refreshes and roster autofill).\n\nEmpty ``jobs`` (200) when a channel has not been registered with cbcron\nyet — a normal state for a config authored ahead of its election.","operationId":"channel_jobs_api_v1_elections__channel__jobs_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ChannelJobsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/manual":{"get":{"tags":["cbelections","Elections"],"summary":"Hand-entered results from the office call sheet","description":"The current hand-entered state: one entry per (office_code, party,\ncounty), the newest call winning. MI assembles results purely by county\nfanout (no statewide feed), so a county with no machine path has NO\nnumbers until staff phone the clerk and land them here. Empty is the\nnormal pre-call state, not an error.","operationId":"manual_entries_api_v1_elections__channel__manual_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ManualResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}},"post":{"tags":["cbelections","Elections"],"summary":"Record a called-in result (append-only)","description":"Append one called-in result. The store is an append-only JSONL journal —\ncorrections supersede, `retract: true` hides a key, history is never\nrewritten (who entered what, when, survives for the canvass).","operationId":"manual_add_api_v1_elections__channel__manual_post","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ManualEntryIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ManualEntry"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/markets":{"get":{"tags":["cbelections","Elections"],"summary":"Prediction-market implied odds for a channel's races","description":"Current Kalshi implied win-probabilities for the channel's configured\nraces, read from the ``markets`` table that ``markets.py`` upserts into the\nchannel workspace (idempotent — the table itself is the current snapshot).\nRows with ``delisted_at`` set are tickers that stopped coming back from\nKalshi; they are retained for audit and excluded here.\n\nThis is a **distinct race-outcome lens** (the general-election winner),\nintentionally separate from the per-county actuals matrix. Returns an empty\n``races`` list (200) when the channel has no markets configured or none\ningested yet, so the kiosk can degrade gracefully.","operationId":"markets_api_v1_elections__channel__markets_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__MarketsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/pipeline":{"get":{"tags":["cbelections","Elections"],"summary":"Live health of the cbcron jobs feeding this channel","description":"`/{channel}/jobs` enriched with live cbcron state, for the Control app.\n\nThe cbloom operator view (`/app/elections-control`) renders a Pipeline tab\nfrom exactly this shape — name, schedule, enabled, next run, last finished\nrun per role. It shipped calling this path while only bare `/jobs`\nexisted, so the tab 404'd from day one (and the app's deliberately\ntab-scoped error handling made that read as a fact about the channel).\n\nFan-out is to cbcron over the mesh (`/api/jobs/{id}` + its `/runs`), so\nthe answer is what cbcron holds *now*, not a snapshot. Failure posture:\n\n* unknown job id → that row gets ``missing: true`` (a red badge), 200;\n* any other upstream failure → **502 for the whole endpoint**. A quieter\n  fallback would render as \"never run\", which the tab treats as a healthy\n  armed burst job — silent degradation would paint a broken pipeline\n  green.\n\nEmpty ``jobs`` (200) when the channel has no ``cbcron_jobs`` block yet —\nnormal for a config authored ahead of its election; cbcron is not called.","operationId":"channel_pipeline_api_v1_elections__channel__pipeline_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__PipelineResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/reference":{"get":{"tags":["cbelections","Elections"],"summary":"AP reference snapshot as a county feed — UNOFFICIAL, provenance-tagged","description":"The latest archived AP/NYT reference snapshot, reshaped to the same\ncounty-major shape as ``/{channel}/clarity`` so one adapter serves both.\n\nUNOFFICIAL by definition and tagged so on every county\n(``source: ap-reference``) — the operator cleared unofficial data for\nthe boards on 2026-08-19 (\"use the unofficial data … something to show\nactivity\"); consumers must keep the AP label on the glass. Data comes\nfrom the reference archiver's on-disk snapshots\n(``scripts/reference_archive.py``, cbelections#40 — the same capture\nthat ran all primary night), never a live external fetch per request.\n\n404 when the channel has no archived reference — a normal state.","operationId":"reference_feed_api_v1_elections__channel__reference_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CountyFeedResponse"}}}},"404":{"description":"channel has no archived reference — a normal pre-event state, not a fault"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/results":{"get":{"tags":["cbelections","Elections"],"summary":"Candidates×counties matrix for the current snapshot","description":"Raw per-county actuals for one contest at the channel's current\nsnapshot version.\n\nWithout ``office_code``/``party`` this returns the channel's lead\nheadline race. ``level=statewide`` omits the per-county rows.","operationId":"results_matrix_api_v1_elections__channel__results_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}},{"name":"level","in":"query","required":false,"schema":{"type":"string","pattern":"^(county|statewide)$","default":"county","title":"Level"}},{"name":"office_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contest office_code (e.g. GOV, USS, PSC-05). Defaults to the channel's lead headline race.","title":"Office Code"},"description":"Contest office_code (e.g. GOV, USS, PSC-05). Defaults to the channel's lead headline race."},{"name":"party","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Party short code (R|D|...)","title":"Party"},"description":"Party short code (R|D|...)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ResultsMatrix"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/scenarios":{"get":{"tags":["cbelections","Elections Sims"],"summary":"List Scenarios","description":"List stored scenarios for the channel (each validated).\n\nThe store is one flat dir; a scenario tagged ``night: <other-channel>``\nis another night's spec — skip it rather than list it as invalid here.\nUntagged scenarios (legacy) list everywhere.","operationId":"list_scenarios_api_v1_elections__channel__scenarios_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbelections__ScenarioSummary"},"title":"Response List Scenarios Api V1 Elections  Channel  Scenarios Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}},"post":{"tags":["cbelections","Elections Sims"],"summary":"Create Scenario","description":"Create a scenario (validated). 409 if the id already exists.","operationId":"create_scenario_api_v1_elections__channel__scenarios_post","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ScenarioWrite"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ScenarioDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/scenarios/{scenario_id}":{"get":{"tags":["cbelections","Elections Sims"],"summary":"Get Scenario","description":"Fetch one scenario: raw YAML + parsed summary.","operationId":"get_scenario_api_v1_elections__channel__scenarios__scenario_id__get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}},{"name":"scenario_id","in":"path","required":true,"schema":{"type":"string","title":"Scenario Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ScenarioDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}},"put":{"tags":["cbelections","Elections Sims"],"summary":"Update Scenario","description":"Replace an existing scenario (validated).","operationId":"update_scenario_api_v1_elections__channel__scenarios__scenario_id__put","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}},{"name":"scenario_id","in":"path","required":true,"schema":{"type":"string","title":"Scenario Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ScenarioWrite"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ScenarioDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}},"delete":{"tags":["cbelections","Elections Sims"],"summary":"Delete Scenario","description":"Delete a scenario.","operationId":"delete_scenario_api_v1_elections__channel__scenarios__scenario_id__delete","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}},{"name":"scenario_id","in":"path","required":true,"schema":{"type":"string","title":"Scenario Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/sims/apply":{"post":{"tags":["cbelections","Elections Sims"],"summary":"Apply Sim","description":"Run a scenario LIVE to ``report_pct`` — writes synthetic results to the\nworkspace + broadcasts → projection → boards. Gated by the close window.","operationId":"apply_sim_api_v1_elections__channel__sims_apply_post","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__SimRunRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__SimResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/sims/preview":{"post":{"tags":["cbelections","Elections Sims"],"summary":"Preview Sim","description":"Dry-run a scenario to ``report_pct`` — compute + return the arc, NO write.\nNever gated (it touches nothing).","operationId":"preview_sim_api_v1_elections__channel__sims_preview_post","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__SimRunRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__SimResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/elections/{channel}/sims/reset":{"post":{"tags":["cbelections","Elections Sims"],"summary":"Reset Sim","description":"Clear the channel to dormant (emits a 0% snapshot so the projection\ntruly recomputes). Gated by the close window.","operationId":"reset_sim_api_v1_elections__channel__sims_reset_post","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__SimResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/callbacks/due":{"get":{"tags":["cbelections","Prospecting"],"summary":"Callbacks due — the work queue","description":"A callback stays due until a newer outcome retires it — working the\ncallback produces the next outcome, which is exactly that retirement.","operationId":"due_callbacks_api_v1_prospecting_callbacks_due_get","parameters":[{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO timestamp; only callbacks due at or before it","title":"Before"},"description":"ISO timestamp; only callbacks due at or before it"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CallbacksResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/candidates":{"get":{"tags":["cbelections","Prospecting"],"summary":"The same data, flat","description":"The flat projection — same source and filters as ``/races``.\n\nThe same rows, so the same three-valued columns, and the same rule: **do not\ncollapse a NULL into a FALSE** (#80, #104). In brief — ``/races`` carries the\nfull contract:\n\n* ``on_ballot=false`` means three different things; only ``ballot_asserted``\n  separates \"the source says out\" from \"nobody ever said\".\n* ``advanced`` — TRUE won the primary, FALSE lost it, **NULL: no published\n  result has been joined to this row.** A whole district reads NULL when the\n  results loader could not key it. NULL is never a loss.\n* ``incumbent`` — TRUE is asserted wherever it appears. FALSE is asserted only\n  by sources that publish the fact (MA OCPF, MN CFB); the mi2026 catalog and\n  the MI ballot list write FALSE as a default, sitting members included. Do\n  not build an incumbency signal on FALSE.","operationId":"list_candidates_api_v1_prospecting_candidates_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"cycle","in":"query","required":false,"schema":{"type":"integer","default":2026,"title":"Cycle"}},{"name":"stage","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"one of ('primary', 'general', 'past')","title":"Stage"},"description":"one of ('primary', 'general', 'past')"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"one of ('federal', 'state', 'county', 'municipal', 'school', 'special', 'precinct')","title":"Level"},"description":"one of ('federal', 'state', 'county', 'municipal', 'school', 'special', 'precinct')"},{"name":"county","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"}},{"name":"min_field","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Field"}},{"name":"has_contact","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Contact"}},{"name":"party_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"comma-separated, one or more of ('republican', 'democrat', 'nonpartisan', 'other'). Groups raw party codes, so `republican` matches both REP and R.","title":"Party Group"},"description":"comma-separated, one or more of ('republican', 'democrat', 'nonpartisan', 'other'). Groups raw party codes, so `republican` matches both REP and R."},{"name":"stage_date_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date. Keeps only rows whose stage_date is strictly before it — with `stage=primary` and today's date, that is the awaiting-results set. Rows with no stage_date are excluded (unknown is not stale).","examples":["2026-08-19"],"title":"Stage Date Before"},"description":"ISO date. Keeps only rows whose stage_date is strictly before it — with `stage=primary` and today's date, that is the awaiting-results set. Rows with no stage_date are excluded (unknown is not stale)."},{"name":"office_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"exact office code (NOT a seat on its own — see race_key)","title":"Office Code"},"description":"exact office code (NOT a seat on its own — see race_key)"},{"name":"race_key","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"one seat, as emitted on each /races row. `office_code` alone is not a seat: Colorado's SHERIFF spans 61 counties and Michigan's state-house code spans 37 districts (#73). This is what a collapsed race expands with.","title":"Race Key"},"description":"one seat, as emitted on each /races row. `office_code` alone is not a seat: Colorado's SHERIFF spans 61 counties and Michigan's state-house code spans 37 districts (#73). This is what a collapsed race expands with."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CandidatesPage"}}}},"400":{"description":"unknown party_group value — never silently ignored (#52)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/candidates/{candidate_id}":{"get":{"tags":["cbelections","Prospecting"],"summary":"One candidate, with provenance","description":"Detail plus every contact point and where each one came from.","operationId":"get_candidate","parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CandidateRow"}}}},"404":{"description":"unknown candidate id"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/candidates/{candidate_id}/contact":{"post":{"tags":["cbelections","Prospecting"],"summary":"Record a contact point ('I have a contact')","description":"Record a contact. A rep-supplied value is ``source_class = operator``.\n\nNo special case and no second store: the operator's number is a row in the\nsame table with the same provenance fields as a scraped one.","operationId":"add_contact_api_v1_prospecting_candidates__candidate_id__contact_post","parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ContactEnvelope"}}}},"404":{"description":"unknown candidate id"},"422":{"description":"bad kind/value/source_class — the closed vocabularies apply"}}}},"/cbelections/api/v1/prospecting/candidates/{candidate_id}/outcomes":{"post":{"tags":["cbelections","Prospecting"],"summary":"Record what came of a call","description":"The write that turns the roster into a sales tool.\n\nThe outcome row here is the record; the cbcrm forward (when\n``PROSPECTING_CRM_TENANT`` is set) and the ``interested`` notification are\nbest-effort annotations that never block or fail the write. Attribution is\nmandatory: ``X-User-Email`` or ``created_by`` in the body.","operationId":"record_outcome_api_v1_prospecting_candidates__candidate_id__outcomes_post","parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__OutcomeEnvelope"}}}},"404":{"description":"unknown candidate id"},"422":{"description":"bad channel/disposition, callback without callback_at, or missing attribution (X-User-Email or created_by)"}}},"get":{"tags":["cbelections","Prospecting"],"summary":"A candidate's outcome history","description":"Every outcome recorded against one candidate, newest first.\n\nThe history, not the latest verdict: a candidate who was unreachable twice\nand then answered is a different prospect from one who answered first time,\nand only the sequence says which. `POST` to this same path appends; nothing\nhere ever replaces a prior outcome.\n\n404 if the candidate id is unknown — an empty list means \"known candidate,\nnever called\", which is a different fact and must not be confused with it.","operationId":"list_outcomes_api_v1_prospecting_candidates__candidate_id__outcomes_get","parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__OutcomesResponse"}}}},"404":{"description":"unknown candidate id — an empty list means known-but-never-called, a different fact"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/counties":{"get":{"tags":["cbelections","Prospecting"],"summary":"Per-county rollup — the pin maps' feed","description":"One row per (state, county): distinct seats and candidates on file.\n\nRead-only and additive (cbapex#8, the locationdex). Consumers join county\nnames to centroids client-side; rows with no county are deliberately\nabsent — see :func:`store.county_counts`.","operationId":"list_counties_api_v1_prospecting_counties_get","parameters":[{"name":"cycle","in":"query","required":false,"schema":{"type":"integer","default":2026,"title":"Cycle"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__CountiesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/races":{"get":{"tags":["cbelections","Prospecting"],"summary":"One row per contested race","description":"The grouped projection, expanding to candidates.\n\n**``candidates[]`` is the whole historical roster, not the current field.**\nA race at ``stage='general'`` still carries the people who lost its primary,\nbecause they are worth serving — cbprospect wants them for history and for\n\"who ran last time\". Read the roster with that in mind and the three race\ncounts stop being interchangeable (#80):\n\n* ``field_size`` — **who is actually running.** Candidates their source has\n  established are off the ballot do not count. This is the number to rank or\n  filter on; a three-way general reads as 3 even when six rows come back.\n  It is never recomputed against the caller's filter: under ``party_group``\n  a race is selected by its matching candidates and expands to only those,\n  while ``field_size`` still describes the whole field, because a rep needs\n  to know they are calling one of nine (#52).\n* ``matching`` — how many rows this response carries for the race.\n* ``concluded`` — ``field_size == 0``: no live field for this cycle. Note\n  this covers both a race that was decided and a seat not up this year.\n\n**``on_ballot`` is ballot ACCESS, and its ``false`` carries three meanings**\nthat only ``ballot_asserted`` separates — do not read it alone:\n\n* ``on_ballot=false, ballot_asserted=true`` — the source established it and\n  says this candidate is OUT (defeated, withdrawn, or not on this year's\n  ballot). Excluded from ``field_size``.\n* ``on_ballot=false, ballot_asserted=false`` — ballot status was NEVER\n  established. Every finance-portal harvest (TX, CA, GA, NY, PA) writes this\n  for its filers: a registration is not a ballot position. Still counted in\n  ``field_size``, because \"nobody told us\" is not evidence of absence.\n* ``on_ballot=true`` — on the ballot.\n\n**Two more three-valued columns a reader must not collapse (#104):**\n\n* ``advanced`` — TRUE came out of the primary, FALSE was defeated, **NULL\n  means no published result has been joined to this row.** It is not a\n  loss. A district whose results the loader could not key reads NULL for\n  every candidate, winner included; see ``scripts/derive_primary_outcome``.\n* ``incumbent`` — TRUE is asserted by every source that writes it. **FALSE\n  is asserted only by sources that publish the fact** (MA's OCPF\n  ``isIncumbent``, MN's CFB ``Incumbent`` flag). The mi2026 catalog and\n  the MI ballot list write FALSE as a default — a sitting state\n  representative reads FALSE there — so for those sources FALSE means\n  \"not stated\", and NULL (26 states) means the same. Do not build an\n  incumbency signal on FALSE.\n\nSee :func:`store.races` and ``store._OUT_OF_FIELD``.","operationId":"list_races_api_v1_prospecting_races_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"cycle","in":"query","required":false,"schema":{"type":"integer","default":2026,"title":"Cycle"}},{"name":"stage","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"one of ('primary', 'general', 'past')","title":"Stage"},"description":"one of ('primary', 'general', 'past')"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"one of ('federal', 'state', 'county', 'municipal', 'school', 'special', 'precinct')","title":"Level"},"description":"one of ('federal', 'state', 'county', 'municipal', 'school', 'special', 'precinct')"},{"name":"county","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"}},{"name":"min_field","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"e.g. 3 for 'three or more running'","title":"Min Field"},"description":"e.g. 3 for 'three or more running'"},{"name":"has_contact","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Contact"}},{"name":"party_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"comma-separated, one or more of ('republican', 'democrat', 'nonpartisan', 'other'). Groups raw party codes, so `republican` matches both REP and R.","title":"Party Group"},"description":"comma-separated, one or more of ('republican', 'democrat', 'nonpartisan', 'other'). Groups raw party codes, so `republican` matches both REP and R."},{"name":"stage_date_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date. Keeps only rows whose stage_date is strictly before it — with `stage=primary` and today's date, that is the awaiting-results set. Rows with no stage_date are excluded (unknown is not stale).","examples":["2026-08-19"],"title":"Stage Date Before"},"description":"ISO date. Keeps only rows whose stage_date is strictly before it — with `stage=primary` and today's date, that is the awaiting-results set. Rows with no stage_date are excluded (unknown is not stale)."},{"name":"office_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"exact office code (NOT a seat on its own — see race_key)","title":"Office Code"},"description":"exact office code (NOT a seat on its own — see race_key)"},{"name":"race_key","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"one seat, as emitted on each /races row. `office_code` alone is not a seat: Colorado's SHERIFF spans 61 counties and Michigan's state-house code spans 37 districts (#73). This is what a collapsed race expands with.","title":"Race Key"},"description":"one seat, as emitted on each /races row. `office_code` alone is not a seat: Colorado's SHERIFF spans 61 counties and Michigan's state-house code spans 37 districts (#73). This is what a collapsed race expands with."},{"name":"expand","in":"query","required":false,"schema":{"type":"boolean","description":"embed each race's candidates and their contacts. `false` returns counts only (`shown`, `with_contact`) and no `candidates` key — page 1 drops from ~5.5MB to ~30KB, for a collapsed table that lazy-loads a race's slate from /candidates?state=&office_code=","default":true,"title":"Expand"},"description":"embed each race's candidates and their contacts. `false` returns counts only (`shown`, `with_contact`) and no `candidates` key — page 1 drops from ~5.5MB to ~30KB, for a collapsed table that lazy-loads a race's slate from /candidates?state=&office_code="},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__RacesPage"}}}},"400":{"description":"unknown party_group value — never silently ignored (#52)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/states":{"get":{"tags":["cbelections","Prospecting"],"summary":"The 50-state coverage table","description":"One row per state; the columns are the pipeline stages.\n\nAnswers the two questions that actually get asked: *where can we sell right\nnow*, and *what would it cost to add a state*.","operationId":"list_states_api_v1_prospecting_states_get","parameters":[{"name":"cycle","in":"query","required":false,"schema":{"type":"integer","default":2026,"title":"Cycle"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__StatesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/states/{state}":{"get":{"tags":["cbelections","Prospecting"],"summary":"One state: ladder, profile, deltas, expected vs actual per tier","description":"The state detail drawer.\n\n``gap`` per tier is the work queue. A null ``gap`` means the skeleton never\ncarried an expected count for that tier — a priming gap, which must read as\nunknown rather than as zero.","operationId":"get_prospecting_state","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"cycle","in":"query","required":false,"schema":{"type":"integer","default":2026,"title":"Cycle"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__StateDetail"}}}},"404":{"description":"unknown state code"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/states/{state}/prime":{"post":{"tags":["cbelections","Prospecting"],"summary":"Dispatch a priming pass for a state","description":"Dispatch the research pass that produces a state's skeleton.\n\nThis is the *interface* to the 50-state research programme, not its\ncompletion: filling the remaining states is a documented research job with a\nreal per-state cost. A state already covered by the DC0 research kit is far\ncheaper to import than to re-research — see ``scripts/import_skeletons.py``.","operationId":"prime_state_api_v1_prospecting_states__state__prime_post","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__UnitEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/units":{"get":{"tags":["cbelections","Prospecting"],"summary":"The work-unit queue","description":"Every dispatched unit, newest first — the cbelections/cbintel seam.\n\nScanning for problems takes **both** ``failed_only`` and ``warned_only``. A\nqueue filtered only on failure showed Indiana as clean while none of its\n14,141 candidates was callable (cbelections#63).","operationId":"list_units_api_v1_prospecting_units_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"one of ('prime', 'harvest', 'enrich')","title":"Kind"},"description":"one of ('prime', 'harvest', 'enrich')"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"one of ('queued', 'running', 'done', 'failed')","title":"Status"},"description":"one of ('queued', 'running', 'done', 'failed')"},{"name":"failed_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Failed Only"}},{"name":"warned_only","in":"query","required":false,"schema":{"type":"boolean","description":"units carrying a warning — one of ('barren',). These closed `done`: they ran, wrote their rows, and still went wrong. Separate axis from `failed_only`; the two never overlap.","default":false,"title":"Warned Only"},"description":"units carrying a warning — one of ('barren',). These closed `done`: they ran, wrote their rows, and still went wrong. Separate axis from `failed_only`; the two never overlap."},{"name":"unread","in":"query","required":false,"schema":{"type":"boolean","description":"only units the enrich extract has never read (read_at IS NULL). The extract passes this so a pass resumes instead of restarting","default":false,"title":"Unread"},"description":"only units the enrich extract has never read (read_at IS NULL). The extract passes this so a pass resumes instead of restarting"},{"name":"superseded","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"false = only units no retry has replaced (superseded_by IS NULL); true = only units that were retried; omitted = both. `failed_only=true&superseded=false` is the failures still waiting for a retry","title":"Superseded"},"description":"false = only units no retry has replaced (superseded_by IS NULL); true = only units that were retried; omitted = both. `failed_only=true&superseded=false` is the failures still waiting for a retry"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__UnitsPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}},"post":{"tags":["cbelections","Prospecting"],"summary":"Dispatch a work unit","description":"Dispatch one unit: ``{kind, scope: {...}, cycle, stage?, source_class?}``.","operationId":"create_unit_api_v1_prospecting_units_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__UnitEnvelope"}}}},"422":{"description":"unknown kind, missing state, or a scope the kind does not take"}}}},"/cbelections/api/v1/prospecting/units/fan-out":{"post":{"tags":["cbelections","Prospecting"],"summary":"Dispatch enrich crawls","description":"Crawl contactless candidates who have never had an enrich unit.\n\nThe stage that makes the line self-running — until now every enrich unit was\ndispatched by hand. Targets are picked once: a candidate with any prior\nenrich unit is never re-dispatched here, so a nightly run converges instead\nof re-crawling the same people forever. Retries have their own path.\n\nOmit ``state`` to run the whole store in one pass — the intended nightly\nshape (cbelections#101). Targets are ordered by field size across all\nstates, so the contested races win the batch wherever they are, and a state\nwith an adapter but no cron job stops being invisible. ``by_state`` in the\nresponse says where the batch actually went.\n\nCapped at ``FAN_OUT_MAX`` however much is asked for, and ``dry_run`` by\ndefault — every target is a real cbintel crawl.","operationId":"dispatch_fan_out","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":2},{"type":"null"}],"description":"one state; omit to select across the whole store","title":"State"},"description":"one state; omit to select across the whole store"},{"name":"cycle","in":"query","required":false,"schema":{"type":"integer","default":2026,"title":"Cycle"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":0,"default":50,"title":"Limit"}},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Dry Run"}},{"name":"dispatched_by","in":"query","required":false,"schema":{"type":"string","default":"fan_out","title":"Dispatched By"}},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"narrow to one tier","title":"Level"},"description":"narrow to one tier"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__FanOutResponse"}}}},"422":{"description":"unknown level — a caller typo, not an empty tier"}}}},"/cbelections/api/v1/prospecting/units/reconcile":{"post":{"tags":["cbelections","Prospecting"],"summary":"Poll every open unit that has a job","description":"Ask cbintel how every open job ended, and record the answers.\n\nThe half of dispatch that was never built. Without it a unit goes\n``running`` and stays there: 48 sat open for two days whose jobs had all\ncompleted within the hour. Safe to run on any cadence — a late pass is\nstill correct and an interrupted one just leaves work for the next.\n``offset`` skips the oldest N open units so a chunked caller walks the\nwhole backlog instead of re-asking about the oldest hundred (cbelections\n2026-09-02 14:00Z).","operationId":"reconcile_units_api_v1_prospecting_units_reconcile_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"default":200,"title":"Limit"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":2},{"type":"null"}],"title":"State"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ReconcileResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/units/sweep":{"post":{"tags":["cbelections","Prospecting"],"summary":"Close units stranded by a killed process","description":"Close open units that have **no job behind them** and are past the age.\n\nOnly job-less units: anything with a ``job_id`` is answerable by\n``/units/reconcile`` and guessing at it would bury real results. Defaults to\n``dry_run`` — read the list before closing it.","operationId":"sweep_units_api_v1_prospecting_units_sweep_post","parameters":[{"name":"older_than_hours","in":"query","required":false,"schema":{"type":"integer","maximum":720,"minimum":1,"default":6,"title":"Older Than Hours"}},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Dry Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__SweepResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/units/{unit_id}":{"get":{"tags":["cbelections","Prospecting"],"summary":"One unit: job id, request, raw result","description":"Full unit detail, including the exact payload sent and the yield arithmetic.","operationId":"get_work_unit","parameters":[{"name":"unit_id","in":"path","required":true,"schema":{"type":"string","title":"Unit Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__WorkUnit"}}}},"404":{"description":"unknown unit id"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/units/{unit_id}/poll":{"post":{"tags":["cbelections","Prospecting"],"summary":"Refresh a unit from its cbintel job","description":"Ask cbintel where the job got to and record the answer.","operationId":"poll_unit_api_v1_prospecting_units__unit_id__poll_post","parameters":[{"name":"unit_id","in":"path","required":true,"schema":{"type":"string","title":"Unit Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__WorkUnit"}}}},"404":{"description":"unknown unit id"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/units/{unit_id}/read":{"post":{"tags":["cbelections","Prospecting"],"summary":"Mark a unit read by the extract","description":"The enrich extract calls this after reading a unit's crawl.\n\n``{\"hits\": n}`` — contact points written from this unit. Replaces a JSON\nledger the extract kept on the box, which did not persist under cron\n(cbelections#110): the store is the one place both the fan-out and the\nextract can see, so it is where \"already read\" belongs.","operationId":"mark_unit_read_api_v1_prospecting_units__unit_id__read_post","parameters":[{"name":"unit_id","in":"path","required":true,"schema":{"type":"string","title":"Unit Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ReadAck"}}}},"404":{"description":"unknown unit id"},"422":{"description":"hits must be an integer"}}}},"/cbelections/api/v1/prospecting/units/{unit_id}/retry":{"post":{"tags":["cbelections","Prospecting"],"summary":"Re-dispatch a unit","description":"Re-dispatch as a NEW unit linked by ``retry_of``.\n\nThe original failure stays in the yield record — overwriting a miss is how a\nsource class comes to look better than it is. It is marked\n``superseded_by`` the new unit in the same transaction, and a unit already\nretried is refused with 409 rather than dispatched twice (cbintel#272).","operationId":"retry_unit_api_v1_prospecting_units__unit_id__retry_post","parameters":[{"name":"unit_id","in":"path","required":true,"schema":{"type":"string","title":"Unit Id"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__UnitEnvelope"}}}},"404":{"description":"unknown unit id"},"409":{"description":"already retried: `detail.superseded_by` names the retry to follow, and nothing was dispatched"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/prospecting/vocabulary":{"get":{"tags":["cbelections","Prospecting"],"summary":"The closed vocabularies clients must use","description":"Every enumerated value in the contract, in one place.\n\nThe UI reads its filter options from here rather than hardcoding them, so a\nnew office tier or source class cannot drift between the two halves.","operationId":"vocabulary_api_v1_prospecting_vocabulary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__VocabularyResponse"}}}}}}},"/cbelections/api/v1/prospecting/yield":{"get":{"tags":["cbelections","Prospecting"],"summary":"Attempts vs hits per source class","description":"The decide-or-pivot number, kept permanently rather than as a spike.\n\n45 of 50 fans out to the rest; 6 of 50 means the source class is wrong and\nthe *adapter* changes — not the schema, and not the UI.\n\nRead **three** fields before calling a state healthy, not one (cbelections#63):\n\n``failed``\n    the unit errored.\n``barren``\n    the unit completed, looked at least 25 times, and found nothing. Indiana\n    had two and ``failed`` was 0, so the row read clean while none of its\n    14,141 candidates was callable.\n``unqueued``\n    states holding roster rows with **no unit ever dispatched**. Iowa and\n    Nebraska were invisible here because a report grouped over work units\n    cannot show a state that has none.","operationId":"get_yield_api_v1_prospecting_yield_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"source_class","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"one of ('soe_filing', 'finance_portal', 'campaign_site', 'news', 'clerk', 'operator')","title":"Source Class"},"description":"one of ('soe_filing', 'finance_portal', 'campaign_site', 'news', 'clerk', 'operator')"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__YieldResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/races":{"get":{"tags":["cbelections","CBELECTIONS"],"summary":"List registered races","description":"Every race the engine can render.\n\nA race is a `StateConfig` in `cbelections.engine.states` — adding one is a\nconfig entry, not a code change.","operationId":"list_races_api_v1_races_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__RacesResponse"}}}}}}},"/cbelections/api/v1/races/{code}":{"get":{"tags":["cbelections","CBELECTIONS"],"summary":"Get one race config","description":"Full config for one race by 2-letter state code (case-insensitive).","operationId":"get_race_api_v1_races__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__RaceConfig"}}}},"404":{"description":"Unknown race code."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/tool-dispatch":{"post":{"tags":["cbelections","Elections"],"summary":"Execute one election_night tool call (read-only)","description":"Route a tool invocation to the frozen live-API read handlers.\n\nErrors come back as ``{\"error\": ...}`` with HTTP 200 — a tool result the\nmodel can read and recover from beats an opaque 4xx swallowed by the\nagent loop (404 \"no snapshot yet\" is a NORMAL pre-election state).","operationId":"tool_dispatch_api_v1_tool_dispatch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__DispatchInput"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Tool Dispatch Api V1 Tool Dispatch Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HTTPValidationError"}}}}}}},"/cbelections/api/v1/tool-schema":{"get":{"tags":["cbelections","Elections"],"summary":"Claude tool descriptor for the election_night curated catalog","description":"The ready-baked descriptor cbai fetches at catalog resolution.","operationId":"tool_schema_api_v1_tool_schema_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__ToolDescriptor"}}}}}}},"/cbelections/api/v1/tuner/stations":{"get":{"tags":["cbelections","Elections Radio"],"summary":"Known radio stations (via cbintel)","description":"Station roster for the radio-search callsign picker.","operationId":"stations_api_v1_tuner_stations_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Stations Api V1 Tuner Stations Get"}}}}}}},"/cbelections/api/v1/tuner/transcripts":{"get":{"tags":["cbelections","Elections Radio"],"summary":"Radio transcript corpus search (via cbintel)","description":"Search the live radio transcript corpus (``q``, ``status``,\n``callsign``, ``limit`` — passed through verbatim).","operationId":"transcripts_api_v1_tuner_transcripts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Transcripts Api V1 Tuner Transcripts Get"}}}}}}},"/cbelections/health":{"get":{"tags":["cbelections","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HealthResponse"}}}}}}},"/cbelections/health/detailed":{"get":{"tags":["cbelections","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__HealthResponse"}}}}}}},"/cbelections/prime":{"get":{"tags":["cbelections","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__PrimeResponse"}}}}}}},"/cbelections/pub/county-feeds":{"get":{"tags":["cbelections","CBELECTIONS"],"summary":"The county-feed differences publication","description":"The county-feed differences analysis (retro 06's planned publication):\nhow Michigan's 83 counties actually publish results, rendered by\n``scripts/publication/build.py`` from ``docs/publication/`` plus\nregistry/coverage tables generated at build time. Stable URL is\n``elections.campaignbrain.dev/pub/county-feeds``.","operationId":"pub_county_feeds_pub_county_feeds_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"503":{"description":"Page not built yet."}}}},"/cbelections/status":{"get":{"tags":["cbelections","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbelections__StatusResponse"}}}}}}},"/cbelections/warroom":{"get":{"tags":["cbelections","CBELECTIONS"],"summary":"Live war-room boards page","description":"The MI-2026 election-night war-room page, served as built on the\nnight (the builder, ``scripts/warroom/build.py``, is retired to git\nhistory — the page is a frozen record now). Stable URL is\n``elections.campaignbrain.dev/warroom`` behind the cbauth gate.","operationId":"warroom_warroom_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"503":{"description":"Page not built yet."}}}},"/cbemail/api/internal/send":{"post":{"tags":["cbemail","internal"],"summary":"Send email (internal, no auth)","description":"Localhost-only endpoint for service-to-service email. No Bearer token required. Pass `?from=<address>` to send from a specific active session; if omitted, falls back to the most-recently-used session (logged).","operationId":"internal_send_api_internal_send_post","parameters":[{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Explicit sender address — selects the matching active session. Omit to use the most-recently-used session (non-deterministic).","title":"From"},"description":"Explicit sender address — selects the matching active session. Omit to use the most-recently-used session (non-deterministic)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__SendEmail"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__EmailResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}}},"/cbemail/api/internal/sessions/prune":{"post":{"tags":["cbemail","internal"],"summary":"Soft-revoke stale sessions (internal, no auth)","description":"Localhost-only hygiene endpoint. Marks `is_active = false` on sessions whose last activity (`last_used_at`, falling back to `created_at`) is older than `days`. Rows are never deleted — the audit trail stays. Sessions in regular use are untouched because every send/read bumps `last_used_at`. Use `dry_run=true` to preview counts.","operationId":"prune_sessions_api_internal_sessions_prune_post","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Inactivity threshold in days","default":7,"title":"Days"},"description":"Inactivity threshold in days"},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","description":"Report counts without revoking","default":false,"title":"Dry Run"},"description":"Report counts without revoking"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__PruneResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}}},"/cbemail/api/mailbox/folders":{"get":{"tags":["cbemail","mailbox"],"summary":"List all mailbox folders","description":"Retrieve a list of all folders (mailboxes) in the email account.\n\nReturns folder metadata including name, hierarchy delimiter, IMAP flags,\nand message counts. Common folders include INBOX, Sent, Drafts, Trash,\nand provider-specific folders like [Gmail]/All Mail.\n\n**Folder naming conventions:**\n- Standard folders: `INBOX`, `Sent`, `Drafts`, `Trash`, `Junk`\n- Gmail-specific: `[Gmail]/All Mail`, `[Gmail]/Starred`, `[Gmail]/Important`\n- Custom folders: Created by user, may have hierarchy like `Projects/2024`\n\n**IMAP flags explained:**\n- `\\HasChildren` - Folder contains subfolders\n- `\\HasNoChildren` - Folder has no subfolders\n- `\\Noselect` - Cannot be selected (virtual container)\n- `\\Sent`, `\\Drafts`, `\\Trash`, `\\Junk` - Special-use indicators\n\n**LLM Guidance:** Call this endpoint first to discover available folders\nbefore listing or moving messages. Use the exact folder name from this\nresponse in subsequent operations.","operationId":"list_folders_api_mailbox_folders_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"mailbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox.","title":"Mailbox"},"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Folders retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__FolderList"},"example":{"folders":[{"name":"INBOX","delimiter":"/","flags":["\\HasNoChildren"],"message_count":1542,"unread_count":23},{"name":"[Gmail]/Sent Mail","delimiter":"/","flags":["\\HasNoChildren","\\Sent"],"message_count":3421,"unread_count":0},{"name":"[Gmail]/Drafts","delimiter":"/","flags":["\\HasNoChildren","\\Drafts"],"message_count":5,"unread_count":0}]}}}},"401":{"description":"Invalid or missing authentication token"},"500":{"description":"IMAP connection or operation failed","content":{"application/json":{"example":{"detail":"Failed to list folders: Connection timed out"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}},"post":{"tags":["cbemail","mailbox"],"summary":"Create a new folder","description":"Create a new folder (mailbox) in the email account.\n\n**Folder naming:**\n- Use simple names like `Projects`, `Archive`, `Newsletters`\n- Create nested folders with delimiter: `Projects/2024`, `Archive/Personal`\n- Avoid special characters that may not be supported by all mail servers\n\n**Provider considerations:**\n- Gmail: Folders appear as labels\n- Outlook: Standard IMAP folder behavior\n- Some providers may have folder creation limits\n\n**LLM Guidance:** After creating a folder, you can move messages to it\nusing the `/messages/{uid}/move` endpoint. Use the exact folder name\nreturned in this response for subsequent operations.","operationId":"create_folder_api_mailbox_folders_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"mailbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox.","title":"Mailbox"},"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__FolderCreate"}}}},"responses":{"201":{"description":"Folder created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__Folder"},"example":{"name":"Projects","delimiter":"/","flags":[],"message_count":0,"unread_count":0}}}},"400":{"description":"Folder creation failed","content":{"application/json":{"example":{"detail":"Failed to create folder"}}}},"401":{"description":"Invalid or missing authentication token"},"500":{"description":"IMAP connection or operation failed"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}}},"/cbemail/api/mailbox/folders/{name}":{"delete":{"tags":["cbemail","mailbox"],"summary":"Delete a folder","description":"Delete a folder from the email account.\n\n**Warning:** Deleting a folder may also delete all messages contained within it,\ndepending on the mail server configuration. Some servers move messages to Trash\ninstead of permanent deletion.\n\n**Restrictions:**\n- Cannot delete system folders (INBOX, Sent, Drafts, Trash)\n- Cannot delete non-empty folders on some servers\n- Provider-specific folders ([Gmail]/*) may not be deletable\n\n**LLM Guidance:** Before deleting a folder, consider moving important messages\nto another folder. Use the list_folders endpoint to verify the folder exists\nand get the exact name (including any hierarchy).","operationId":"delete_folder_api_mailbox_folders__name__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"mailbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox.","title":"Mailbox"},"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"204":{"description":"Folder deleted successfully"},"400":{"description":"Folder deletion failed","content":{"application/json":{"example":{"detail":"Failed to delete folder"}}}},"401":{"description":"Invalid or missing authentication token"},"500":{"description":"IMAP connection or operation failed"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}}},"/cbemail/api/mailbox/messages":{"get":{"tags":["cbemail","mailbox"],"summary":"List messages in a folder","description":"Retrieve a paginated list of message summaries from a specific folder.\n\nReturns message metadata (UID, subject, sender, date, flags, size) without\nthe full body content. Use the get_message endpoint with a specific UID\nto retrieve the complete message including body and attachments.\n\n**Pagination:**\n- Use `limit` to control how many messages to return (1-100, default 50)\n- Use `offset` to skip messages for pagination\n- The `total` field in the response indicates total messages in folder\n\n**Message ordering:**\nMessages are returned in reverse chronological order (newest first) based\non their position in the mailbox.\n\n**IMAP flags in response:**\n- `\\Seen` - Message has been read\n- `\\Flagged` - Message is starred/flagged\n- `\\Answered` - Message has been replied to\n- `\\Draft` - Message is a draft\n- `\\Deleted` - Message is marked for deletion\n\n**LLM Guidance:** Use pagination for large mailboxes. The response includes\nmessage UIDs which are required for operations like get, delete, move, and mark.\nUIDs are folder-specific - the same message in different folders may have\ndifferent UIDs.","operationId":"list_messages_api_mailbox_messages_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"folder","in":"query","required":false,"schema":{"type":"string","description":"Folder to list messages from","default":"INBOX","title":"Folder"},"description":"Folder to list messages from"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Number of messages to return","default":50,"title":"Limit"},"description":"Number of messages to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Offset for pagination","default":0,"title":"Offset"},"description":"Offset for pagination"},{"name":"mailbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox.","title":"Mailbox"},"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Messages retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__MessageList"},"example":{"folder":"INBOX","messages":[{"uid":"12345","message_id":"<CAH1rXxJ8hFn3qKE@mail.gmail.com>","subject":"Weekly Project Update","from_address":"alice@example.com","to_address":"bob@example.com","date":"2024-01-15T09:30:00Z","flags":["\\Seen"],"size":15234}],"total":1542,"offset":0,"limit":50}}}},"401":{"description":"Invalid or missing authentication token"},"500":{"description":"IMAP connection or operation failed","content":{"application/json":{"example":{"detail":"Failed to list messages: Folder not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}}},"/cbemail/api/mailbox/messages/{uid}":{"get":{"tags":["cbemail","mailbox"],"summary":"Get a single message","description":"Retrieve the complete content of a single message by its UID.\n\nReturns the full message including headers, plain text body, HTML body (if available),\nand attachment metadata. This is the endpoint to use when you need to read\nthe actual content of an email.\n\n**Response includes:**\n- Full headers (From, To, CC, BCC, Reply-To, Date, Subject)\n- Plain text body (`body_text`)\n- HTML body (`body_html`) if the message has HTML content\n- Attachment list with filename, content type, and size\n- Selected additional headers\n\n**Important notes:**\n- UIDs are folder-specific. Always specify the correct folder.\n- Fetching a message typically marks it as read (\\Seen flag) on most servers\n- Attachment content is not included - only metadata\n\n**LLM Guidance:** Get the UID from the list_messages endpoint first.\nIf processing email content, prefer `body_text` for reliable text extraction.\nUse `body_html` when rendering is needed. For large mailboxes, fetch\nmessages selectively rather than downloading everything.","operationId":"get_message_api_mailbox_messages__uid__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string","title":"Uid"}},{"name":"folder","in":"query","required":false,"schema":{"type":"string","description":"Folder containing the message","default":"INBOX","title":"Folder"},"description":"Folder containing the message"},{"name":"mailbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox.","title":"Mailbox"},"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Message retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__Message"},"example":{"uid":"12345","message_id":"<CAH1rXxJ8hFn3qKE@mail.gmail.com>","subject":"Weekly Project Update","from_address":"alice@example.com","to_address":["bob@example.com"],"cc_address":["team@example.com"],"bcc_address":[],"reply_to":"alice@example.com","date":"2024-01-15T09:30:00Z","flags":["\\Seen"],"body_text":"Hi Bob,\n\nHere's the weekly update...","body_html":"<html><body><p>Hi Bob,</p>...</body></html>","attachments":[{"filename":"report.pdf","content_type":"application/pdf","size":245678}],"headers":{"X-Priority":"3"}}}}},"401":{"description":"Invalid or missing authentication token"},"404":{"description":"Message not found","content":{"application/json":{"example":{"detail":"Message not found"}}}},"500":{"description":"IMAP connection or operation failed"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}},"delete":{"tags":["cbemail","mailbox"],"summary":"Delete a message","description":"Delete a message from the mailbox.\n\n**Deletion behavior varies by provider:**\n- Gmail: Moves message to Trash (permanent delete after 30 days)\n- Outlook: Moves message to Deleted Items\n- Other providers: May mark for deletion or move to Trash\n\n**To permanently delete:**\n1. Delete from original folder (moves to Trash)\n2. Delete again from Trash folder (permanent)\n\n**LLM Guidance:** Use this endpoint to clean up processed messages.\nFor Gmail, use `[Gmail]/Trash` as the folder name to permanently delete.\nConsider moving messages to an Archive folder instead of deleting if\nyou might need them later.","operationId":"delete_message_api_mailbox_messages__uid__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string","title":"Uid"}},{"name":"folder","in":"query","required":false,"schema":{"type":"string","description":"Folder containing the message","default":"INBOX","title":"Folder"},"description":"Folder containing the message"},{"name":"mailbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox.","title":"Mailbox"},"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"204":{"description":"Message deleted successfully"},"400":{"description":"Message deletion failed","content":{"application/json":{"example":{"detail":"Failed to delete message"}}}},"401":{"description":"Invalid or missing authentication token"},"500":{"description":"IMAP connection or operation failed"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}}},"/cbemail/api/mailbox/messages/{uid}/attachments/{index}":{"get":{"tags":["cbemail","mailbox"],"summary":"Download an attachment's bytes","description":"Return the raw bytes of one attachment, identified by its ordinal position in the message's `attachments` list (0-based). The message detail endpoint returns attachment *metadata* only; this returns the content. Index rather than Content-ID because scanner attachments (e.g. Ricoh PDFs) often carry no Content-ID.","operationId":"get_attachment_api_mailbox_messages__uid__attachments__index__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string","title":"Uid"}},{"name":"index","in":"path","required":true,"schema":{"type":"integer","title":"Index"}},{"name":"folder","in":"query","required":false,"schema":{"type":"string","description":"Folder containing the message","default":"INBOX","title":"Folder"},"description":"Folder containing the message"},{"name":"mailbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox.","title":"Mailbox"},"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}},"application/octet-stream":{}}},"404":{"description":"Message or attachment index not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}}},"/cbemail/api/mailbox/messages/{uid}/mark":{"post":{"tags":["cbemail","mailbox"],"summary":"Mark a message (read/unread/flagged)","description":"Change the flags on a message to mark it as read, unread, flagged, or unflagged.\n\n**Available actions:**\n- `read` - Mark as read (adds \\Seen flag)\n- `unread` - Mark as unread (removes \\Seen flag)\n- `flagged` - Star/flag the message (adds \\Flagged flag)\n- `unflagged` - Remove star/flag (removes \\Flagged flag)\n\n**How flags appear in email clients:**\n- `\\Seen` (read) - Message appears as read (not bold) in most clients\n- `\\Flagged` (flagged) - Appears as starred in Gmail, flagged in Outlook\n\n**Common use cases:**\n- Mark processed messages as read\n- Keep messages unread for later attention\n- Flag important messages for follow-up\n- Bulk update message status in automation workflows\n\n**LLM Guidance:** Use marking to track message processing state.\nFor example, mark messages as read after processing them, or flag\nmessages that need human review. Multiple mark operations can be\ncalled in sequence for different actions.","operationId":"mark_message_api_mailbox_messages__uid__mark_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string","title":"Uid"}},{"name":"folder","in":"query","required":false,"schema":{"type":"string","description":"Folder containing the message","default":"INBOX","title":"Folder"},"description":"Folder containing the message"},{"name":"mailbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox.","title":"Mailbox"},"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__MessageMark"}}}},"responses":{"204":{"description":"Message marked successfully"},"400":{"description":"Mark operation failed","content":{"application/json":{"example":{"detail":"Failed to mark message"}}}},"401":{"description":"Invalid or missing authentication token"},"422":{"description":"Invalid action value","content":{"application/json":{"example":{"detail":[{"loc":["body","action"],"msg":"value is not a valid enumeration member","type":"type_error.enum"}]}}}},"500":{"description":"IMAP connection or operation failed"}}}},"/cbemail/api/mailbox/messages/{uid}/move":{"post":{"tags":["cbemail","mailbox"],"summary":"Move a message to another folder","description":"Move a message from one folder to another.\n\nThis operation copies the message to the destination folder and marks the\noriginal for deletion. The message will have a new UID in the destination\nfolder.\n\n**Common use cases:**\n- Archive messages: Move from INBOX to Archive\n- Organize by project: Move to custom folders\n- Move spam to Junk or legitimate mail to INBOX\n- Move processed emails out of inbox\n\n**Important notes:**\n- The message gets a new UID in the destination folder\n- Use the exact folder name from the list_folders endpoint\n- For Gmail labels, moving applies/removes the label\n\n**LLM Guidance:** After moving, if you need to reference the message again,\nyou'll need to search for it in the destination folder by Message-ID or\nother identifying criteria, as the UID will be different.","operationId":"move_message_api_mailbox_messages__uid__move_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string","title":"Uid"}},{"name":"folder","in":"query","required":false,"schema":{"type":"string","description":"Source folder","default":"INBOX","title":"Folder"},"description":"Source folder"},{"name":"mailbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox.","title":"Mailbox"},"description":"Mailbox address to read as. Required for cbauth cbemail.mailbox.read callers (fleet agents); must match a provisioned active session. Ignored for session-token / API-key auth, which reads the session's own mailbox."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__MessageMove"}}}},"responses":{"204":{"description":"Message moved successfully"},"400":{"description":"Move operation failed","content":{"application/json":{"examples":{"move_failed":{"summary":"Move failed","value":{"detail":"Failed to move message"}},"folder_not_found":{"summary":"Destination folder not found","value":{"detail":"Failed to move message: Folder does not exist"}}}}}},"401":{"description":"Invalid or missing authentication token"},"500":{"description":"IMAP connection or operation failed"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}}},"/cbemail/api/send":{"post":{"tags":["cbemail","send"],"summary":"Send a single email","description":"Send an email through the SMTP server configured for your session.\n\n**Email composition:**\n- `to` - Required. Single email or array of emails\n- `subject` - Required. Email subject line\n- `body` - Required. Plain text content\n- `body_html` - Optional. HTML content (sent as multipart/alternative)\n- `cc` - Optional. CC recipients\n- `bcc` - Optional. BCC recipients (hidden from other recipients)\n- `reply_to` - Optional. Address for replies (defaults to sender)\n\n**Sender address:**\nThe email is sent from the address associated with your session.\nThe From header is automatically set and cannot be overridden.\n\n**HTML emails:**\nWhen both `body` and `body_html` are provided, the email is sent as\nmultipart/alternative, allowing email clients to display either version.\nAlways include a plain text body for accessibility and clients that\ndon't render HTML.\n\n**Rate limits:**\nSending rates depend on your email provider:\n- Gmail: ~500/day for personal, ~2000/day for Workspace\n- Outlook: ~300/day\n- Other providers vary\n\n**LLM Guidance:** For single notifications or simple emails, use this endpoint.\nFor sending to multiple recipients with different content, use the batch\nendpoint instead to reduce API calls. Always handle the response to check\nfor delivery success.","operationId":"send_email_api_send_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__SendEmail"}}}},"responses":{"200":{"description":"Email sent successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__EmailResult"},"example":{"success":true,"message_id":"<20240115093000.ABC123@smtp.gmail.com>"}}}},"401":{"description":"Invalid or missing authentication token"},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","to"],"msg":"value is not a valid email address","type":"value_error.email"}]}}}},"500":{"description":"SMTP connection or send operation failed","content":{"application/json":{"examples":{"connection_failed":{"summary":"Connection failed","value":{"detail":"Failed to send email: Connection refused"}},"recipient_rejected":{"summary":"Recipient rejected","value":{"detail":"Failed to send email: Recipient address rejected"}}}}}}}}},"/cbemail/api/send/batch":{"post":{"tags":["cbemail","send"],"summary":"Send multiple emails in a batch","description":"Send multiple emails in a single API call.\n\nThis endpoint is optimized for sending multiple emails efficiently. It maintains\na single SMTP connection and sends all emails in sequence, returning individual\nresults for each email.\n\n**Use cases:**\n- Personalized emails to multiple recipients (each with unique content)\n- Notification emails to different users\n- Bulk email campaigns with individual customization\n\n**How it works:**\n1. Opens a single SMTP connection\n2. Sends each email in the batch sequentially\n3. Returns individual success/failure status for each email\n4. Continues sending even if some emails fail\n\n**Response breakdown:**\n- `total` - Total number of emails in the batch\n- `successful` - Number of emails sent successfully\n- `failed` - Number of emails that failed\n- `results` - Array with individual result for each email (in same order as input)\n\n**Partial failures:**\nThe batch operation is atomic per-email, not per-batch. If email 2 of 5 fails,\nemails 1, 3, 4, and 5 are still sent. Check the `results` array to identify\nwhich emails failed and why.\n\n**Limits:**\n- No hard limit on batch size, but very large batches may timeout\n- Provider rate limits still apply (spread large batches over time)\n\n**LLM Guidance:** Use batch sending when you have multiple emails with different\nrecipients or content. For the same content to multiple recipients, you can\neither use batch or use a single email with multiple addresses in the `to` field.\nAlways inspect the `results` array to handle partial failures appropriately.","operationId":"send_batch_api_send_batch_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__SendEmailBatch"}}}},"responses":{"200":{"description":"Batch completed (may include partial failures)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__BatchEmailResult"},"examples":{"all_success":{"summary":"All emails sent","value":{"total":3,"successful":3,"failed":0,"results":[{"success":true,"message_id":"<MSG001@smtp.gmail.com>"},{"success":true,"message_id":"<MSG002@smtp.gmail.com>"},{"success":true,"message_id":"<MSG003@smtp.gmail.com>"}]}},"partial_failure":{"summary":"Some emails failed","value":{"total":3,"successful":2,"failed":1,"results":[{"success":true,"message_id":"<MSG001@smtp.gmail.com>"},{"success":false,"error":"Recipient address rejected"},{"success":true,"message_id":"<MSG003@smtp.gmail.com>"}]}}}}}},"401":{"description":"Invalid or missing authentication token"},"422":{"description":"Validation error in request body"},"500":{"description":"SMTP connection failed (no emails sent)","content":{"application/json":{"example":{"detail":"Failed to send batch emails: Connection refused"}}}}}}},"/cbemail/api/send/list":{"post":{"tags":["cbemail","send"],"summary":"Send a templated email to every member of a cblist","description":"Resolve a **cblist** to its email recipients and send each a templated email —\nso a caller sends to a durable, reusable list by id instead of assembling a\nrecipient array. Recipients are resolved live from cblist at send time via\n`GET /lists/{id}/members?channel=email`, so the list is always current.\n\n- **`dry_run: true`** — resolve + preview recipients and the first rendered\n  email **without sending**. No SMTP session required.\n- **`dry_run: false`** — requires a session (Bearer token or API key +\n  X-Session-ID) whose SMTP credentials do the sending.\n\n**Templating:** `{first_name}`, `{last_name}`, and any `merge_vars` key in the\nsubject/body/body_html are substituted per recipient; unknown placeholders are\nleft intact.","operationId":"send_to_list_api_send_list_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__SendToList"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__SendToListResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbemail/api/sessions":{"post":{"tags":["cbemail","sessions"],"summary":"Create a new email session","description":"Register email credentials and create a new session for accessing mailbox operations.\n\n**How it works:**\n\n1. The API auto-detects your email provider based on the domain (gmail.com, outlook.com, etc.)\n2. It validates credentials by testing connections to both IMAP and SMTP servers\n3. Your password is encrypted using Fernet symmetric encryption before storage\n4. A JWT token is generated and returned for authenticating subsequent requests\n\n**Provider Detection:**\n\n- `gmail.com`, `googlemail.com` → Gmail (imap.gmail.com, smtp.gmail.com)\n- `outlook.com`, `hotmail.com`, `live.com` → Outlook (outlook.office365.com)\n- `yahoo.com`, `ymail.com` → Yahoo\n- `icloud.com`, `me.com`, `mac.com` → iCloud\n- Other domains → Custom (must provide server config)\n\n**For Gmail users:** You must use an App Password, not your regular password.\nGenerate one at https://myaccount.google.com/apppasswords\n\n**Custom server configuration:** For domains not in the auto-detection list,\nprovide `imap_server`, `smtp_server`, and optionally the ports.\n\n**LLM Guidance:** This is the first endpoint to call in any email workflow.\nStore the returned `token` securely and include it as a Bearer token in all\nsubsequent API requests.","operationId":"create_session_api_sessions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__SessionCreate"}}},"required":true},"responses":{"201":{"description":"Session created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__SessionResponse"},"example":{"session_id":"550e8400-e29b-41d4-a716-446655440000","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","email":"user@gmail.com","provider":"gmail","expires_in":1800}}}},"400":{"description":"Invalid credentials or connection failed","content":{"application/json":{"examples":{"imap_failed":{"summary":"IMAP connection failed","value":{"detail":"IMAP connection failed: Authentication failed"}},"smtp_failed":{"summary":"SMTP connection failed","value":{"detail":"SMTP connection failed: Connection refused"}},"custom_missing":{"summary":"Custom provider missing config","value":{"detail":"Custom provider requires imap_server and smtp_server"}}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","email"],"msg":"value is not a valid email address","type":"value_error.email"}]}}}}}}},"/cbemail/api/sessions/{session_id}":{"get":{"tags":["cbemail","sessions"],"summary":"Get session information","description":"Retrieve detailed information about a session.\n\nReturns metadata about the session including email address, provider, server\nconfiguration, creation time, and last activity. Useful for verifying session\nstate and debugging connectivity issues.\n\n**Security:** You can only retrieve information about your own session.\nAttempting to access another session's information returns a 403 Forbidden error.\n\n**LLM Guidance:** Use this endpoint to verify session configuration before\nperforming operations. The `is_active` field indicates whether the session\nis still valid.","operationId":"get_session_api_sessions__session_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}}],"responses":{"200":{"description":"Session information retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__SessionInfo"},"example":{"session_id":"550e8400-e29b-41d4-a716-446655440000","email":"user@gmail.com","provider":"gmail","imap_server":"imap.gmail.com","smtp_server":"smtp.gmail.com","created_at":"2024-01-15T10:30:00Z","last_used_at":"2024-01-15T14:22:00Z","is_active":true}}}},"401":{"description":"Invalid or missing authentication token","content":{"application/json":{"example":{"detail":"Could not validate credentials"}}}},"403":{"description":"Cannot access another session's information","content":{"application/json":{"example":{"detail":"Cannot access other sessions"}}}},"404":{"description":"Session not found","content":{"application/json":{"example":{"detail":"Session not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}},"delete":{"tags":["cbemail","sessions"],"summary":"Delete/revoke a session","description":"Permanently delete a session and its stored credentials.\n\nThis endpoint removes the session record from the database, including the\nencrypted credentials. After deletion, the JWT token associated with this\nsession will no longer be valid for any API operations.\n\n**What happens:**\n- Session record is deleted from the database\n- Encrypted credentials are permanently removed\n- Associated JWT token becomes invalid\n- Any subsequent API calls with this token will fail\n\n**Use cases:**\n- Logging out of the email service\n- Rotating credentials (delete old session, create new one)\n- Security: revoking access if credentials are compromised\n\n**Security:** You can only delete your own session.\n\n**LLM Guidance:** Call this endpoint when you're done with an email workflow\nand want to clean up. Note that this is irreversible - you'll need to create\na new session to continue using the API.","operationId":"delete_session_api_sessions__session_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}}],"responses":{"204":{"description":"Session deleted successfully"},"401":{"description":"Invalid or missing authentication token","content":{"application/json":{"example":{"detail":"Could not validate credentials"}}}},"403":{"description":"Cannot delete another session","content":{"application/json":{"example":{"detail":"Cannot delete other sessions"}}}},"404":{"description":"Session not found","content":{"application/json":{"example":{"detail":"Session not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}}},"/cbemail/api/sessions/{session_id}/health":{"get":{"tags":["cbemail","sessions"],"summary":"Check session connectivity","description":"Test IMAP and SMTP server connectivity for a session.\n\nThis endpoint verifies that the stored credentials can still successfully\nconnect to both the IMAP (for reading) and SMTP (for sending) servers.\nIt's useful for diagnosing issues and verifying connectivity before\nperforming batch operations.\n\n**What it tests:**\n- IMAP connection: Can we connect and authenticate to read emails?\n- SMTP connection: Can we connect and authenticate to send emails?\n\n**Response interpretation:**\n- Both `imap_connected` and `smtp_connected` true → Fully operational\n- `imap_connected` true, `smtp_connected` false → Can read but not send\n- Both false → Credentials may have expired or been revoked\n\n**Common issues:**\n- \"Authentication failed\" → Password changed or app password revoked\n- \"Connection refused\" → Server temporarily unavailable or blocked\n- \"SSL certificate error\" → Network or server configuration issue\n\n**Side effects:** This endpoint updates the `last_used_at` timestamp for the session.\n\n**LLM Guidance:** Call this endpoint before performing important operations,\nespecially batch sends, to verify connectivity. If health check fails, you\nmay need to delete the session and create a new one with updated credentials.","operationId":"check_session_health_api_sessions__session_id__health_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"x-session-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Session-Id"}}],"responses":{"200":{"description":"Health check completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HealthCheckResponse"},"examples":{"healthy":{"summary":"Fully connected","value":{"session_id":"550e8400-e29b-41d4-a716-446655440000","imap_connected":true,"smtp_connected":true}},"partial":{"summary":"Partial connectivity","value":{"session_id":"550e8400-e29b-41d4-a716-446655440000","imap_connected":true,"smtp_connected":false,"smtp_error":"Connection refused: SMTP server unreachable"}},"failed":{"summary":"Authentication failed","value":{"session_id":"550e8400-e29b-41d4-a716-446655440000","imap_connected":false,"smtp_connected":false,"imap_error":"Authentication failed: Invalid credentials","smtp_error":"Authentication failed: Invalid credentials"}}}}}},"401":{"description":"Invalid or missing authentication token","content":{"application/json":{"example":{"detail":"Could not validate credentials"}}}},"403":{"description":"Cannot check health of another session","content":{"application/json":{"example":{"detail":"Cannot check health of other sessions"}}}},"404":{"description":"Session not found","content":{"application/json":{"example":{"detail":"Session not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HTTPValidationError"}}}}}}},"/cbemail/health":{"get":{"tags":["cbemail","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HealthResponse"}}}}}}},"/cbemail/health/detailed":{"get":{"tags":["cbemail","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__HealthResponse"}}}}}}},"/cbemail/prime":{"get":{"tags":["cbemail","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__PrimeResponse"}}}}}}},"/cbemail/status":{"get":{"tags":["cbemail","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbemail__StatusResponse"}}}}}}},"/cbenrichment/api/v1/jobs":{"post":{"tags":["cbenrichment","CBENRICHMENT"],"summary":"Create an enrichment job from a probe-acceptable contact list","description":"Probe first (same loud failures), then persist the input and queue the\nmapped lanes. The cbcron tick drains it stage by stage; poll GET /jobs/{id}.","operationId":"create_job_api_v1_jobs_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbenrichment__Body_create_job_api_v1_jobs_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbenrichment__HTTPValidationError"}}}}}},"get":{"tags":["cbenrichment","CBENRICHMENT"],"summary":"List enrichment jobs (aggregates only)","operationId":"list_jobs_api_v1_jobs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbenrichment__HTTPValidationError"}}}}}}},"/cbenrichment/api/v1/jobs/probe":{"post":{"tags":["cbenrichment","CBENRICHMENT"],"summary":"Probe a contact list — profile only, no writes","description":"Sniff, map, and profile a contact list without enriching anything.\n\nReturns rows, per-key coverage, and what each later stage WOULD do.\nAmbiguity fails loudly (422) with the way out; no guessed columns.","operationId":"probe_job_api_v1_jobs_probe_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbenrichment__Body_probe_job_api_v1_jobs_probe_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbenrichment__HTTPValidationError"}}}}}}},"/cbenrichment/api/v1/jobs/{job_id}":{"get":{"tags":["cbenrichment","CBENRICHMENT"],"summary":"Job status + aggregate result","operationId":"get_job_api_v1_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbenrichment__HTTPValidationError"}}}}}}},"/cbenrichment/health":{"get":{"tags":["cbenrichment","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbenrichment__HealthResponse"}}}}}}},"/cbenrichment/health/detailed":{"get":{"tags":["cbenrichment","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbenrichment__HealthResponse"}}}}}}},"/cbenrichment/internal/enrich/janitor":{"post":{"summary":"PII retention drain (cbcron, daily)","description":"Purge finished jobs' local files + bucket objects past retention_days;\nrows are soft-deleted and kept forever as the aggregate audit ledger.","operationId":"run_janitor_internal_enrich_janitor_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["cbenrichment"]}},"/cbenrichment/internal/enrich/retry/{job_id}":{"post":{"summary":"Re-arm an errored job","description":"Clear the error and resume at the failed stage (the cursor only\nadvances on success, so retry means re-running exactly that stage).","operationId":"retry_job_internal_enrich_retry__job_id__post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbenrichment__HTTPValidationError"}}}}},"tags":["cbenrichment"]}},"/cbenrichment/internal/enrich/run/{job_id}":{"post":{"summary":"Drain one job to completion","description":"Ops/parity convenience; production uses the tick.","operationId":"run_job_internal_enrich_run__job_id__post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbenrichment__HTTPValidationError"}}}}},"tags":["cbenrichment"]}},"/cbenrichment/internal/enrich/tick":{"post":{"summary":"Advance the oldest active job one stage","description":"cbcron drain — cblist's proven shape. Safe to call any time.","operationId":"tick_internal_enrich_tick_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["cbenrichment"]}},"/cbenrichment/prime":{"get":{"tags":["cbenrichment","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbenrichment__PrimeResponse"}}}}}}},"/cbenrichment/status":{"get":{"tags":["cbenrichment","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbenrichment__StatusResponse"}}}}}}},"/cbesig/api/v1/archive/envelopes":{"get":{"tags":["cbesig","Archive"],"summary":"List archived envelopes","description":"Search and filter across BoldSign and DocuSign documents.","operationId":"list_envelopes_api_v1_archive_envelopes_get","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"","title":"Source"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"","title":"Status"}},{"name":"search","in":"query","required":false,"schema":{"type":"string","default":"","title":"Search"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__EnvelopeListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HTTPValidationError"}}}}}}},"/cbesig/api/v1/archive/envelopes/{envelope_id}":{"get":{"tags":["cbesig","Archive"],"summary":"Get envelope by ID","description":"Retrieve a single archived envelope by its unique identifier.","operationId":"get_envelope_api_v1_archive_envelopes__envelope_id__get","parameters":[{"name":"envelope_id","in":"path","required":true,"schema":{"type":"string","title":"Envelope Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__Envelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HTTPValidationError"}}}}}}},"/cbesig/api/v1/archive/stats":{"get":{"tags":["cbesig","Archive"],"summary":"Archive statistics","description":"Summary counts by source and status for dashboard widgets.","operationId":"get_stats_api_v1_archive_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__ArchiveStats"}}}}}}},"/cbesig/api/v1/documents":{"get":{"tags":["cbesig","Documents"],"summary":"List documents","description":"Paginated list of BoldSign documents with optional status and search filters.","operationId":"list_documents_api_v1_documents_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"","title":"Status"}},{"name":"search","in":"query","required":false,"schema":{"type":"string","default":"","title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__DocumentListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HTTPValidationError"}}}}}}},"/cbesig/api/v1/documents/send":{"post":{"tags":["cbesig","Documents"],"summary":"Send document","description":"Send a raw PDF document for e-signature with specified signers.","operationId":"send_document_api_v1_documents_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__SendDocumentRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__DocumentCreated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HTTPValidationError"}}}}}}},"/cbesig/api/v1/documents/send-from-template":{"post":{"tags":["cbesig","Documents"],"summary":"Send from template","description":"Create and send a document for e-signature using a BoldSign template.","operationId":"send_from_template_api_v1_documents_send_from_template_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__SendFromTemplateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__DocumentCreated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HTTPValidationError"}}}}}}},"/cbesig/api/v1/documents/{document_id}/download":{"get":{"tags":["cbesig","Documents"],"summary":"Download signed document","description":"Download the completed signed PDF for a given document.","operationId":"download_document_api_v1_documents__document_id__download_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HTTPValidationError"}}}}}}},"/cbesig/api/v1/documents/{document_id}/remind":{"post":{"tags":["cbesig","Documents"],"summary":"Send BoldSign reminder for outstanding document","description":"Re-emails pending signers without invalidating signatures already collected. Use to nudge an envelope sitting in a recipient's inbox; do not use on signed/cancelled/expired documents (BoldSign returns an error in those cases). cbpulse#385.","operationId":"remind_document_api_v1_documents__document_id__remind_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbesig__RemindRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__RemindResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HTTPValidationError"}}}}}}},"/cbesig/api/v1/documents/{document_id}/status":{"get":{"tags":["cbesig","Documents"],"summary":"Get document status","description":"Retrieve signing status and signer details for a specific document.","operationId":"get_document_status_api_v1_documents__document_id__status_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__DocumentStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HTTPValidationError"}}}}}}},"/cbesig/api/v1/hello":{"get":{"tags":["cbesig","CBESIG"],"summary":"Hello world","description":"Return a greeting.","operationId":"hello_api_v1_hello_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbesig/api/v1/templates":{"get":{"tags":["cbesig","Templates"],"summary":"List templates","description":"Paginated list of available BoldSign templates with optional search.","operationId":"list_templates_api_v1_templates_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}},{"name":"search","in":"query","required":false,"schema":{"type":"string","default":"","title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__TemplateListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HTTPValidationError"}}}}}}},"/cbesig/api/v1/templates/detailed":{"get":{"tags":["cbesig","Templates"],"summary":"List templates with full details","description":"Returns all templates with roles, files, and metadata — everything needed to set up a send-from-template call.","operationId":"list_templates_detailed_api_v1_templates_detailed_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}},{"name":"search","in":"query","required":false,"schema":{"type":"string","default":"","title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbesig__TemplateDetail"},"title":"Response List Templates Detailed Api V1 Templates Detailed Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HTTPValidationError"}}}}}}},"/cbesig/api/v1/webhooks/boldsign":{"post":{"tags":["cbesig","Webhooks"],"summary":"Receive a BoldSign event (via cbwebhook fan-out)","description":"Authenticated receiver for BoldSign signing events fanned out by cbwebhook. Verifies the shared X-BoldSign-Verification-Token, then acknowledges the event. Returns 403 on a missing/invalid token and 400 on a malformed body; a 2xx tells cbwebhook the delivery succeeded (non-2xx triggers its retry/backoff).","operationId":"receive_boldsign_webhook_api_v1_webhooks_boldsign_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Receive Boldsign Webhook Api V1 Webhooks Boldsign Post"}}}}}}},"/cbesig/health":{"get":{"tags":["cbesig","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HealthResponse"}}}}}}},"/cbesig/health/detailed":{"get":{"tags":["cbesig","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__HealthResponse"}}}}}}},"/cbesig/prime":{"get":{"tags":["cbesig","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__PrimeResponse"}}}}}}},"/cbesig/status":{"get":{"tags":["cbesig","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbesig__StatusResponse"}}}}}}},"/cbetl/api/addresses/normalize":{"post":{"tags":["cbetl","addresses"],"summary":"Normalize a single address","description":"Normalize a single address.\n\nStandardizes address components including:\n- State abbreviations (e.g., \"Michigan\" -> \"MI\")\n- Street type abbreviations\n- ZIP code formatting\n- Returns a hash for deduplication","operationId":"normalize_address_api_addresses_normalize_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__AddressNormalizeRequest"}}},"required":true},"responses":{"200":{"description":"Successfully normalized address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__AddressResponse"},"example":{"street":"123 Main St","city":"Detroit","state":"MI","postal_code":"48201","country":"US","address_type":"home","hash":"123 main street|detroit|mi|48201"}}}},"422":{"description":"Address normalization failed"}}}},"/cbetl/api/addresses/normalize/batch":{"post":{"tags":["cbetl","addresses"],"summary":"Normalize multiple addresses","description":"Normalize a batch of addresses.\n\nProcesses multiple addresses in a single request.\nReturns normalized addresses along with success count.","operationId":"normalize_addresses_batch_api_addresses_normalize_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__BatchNormalizeRequest"}}},"required":true},"responses":{"200":{"description":"Successfully processed batch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__BatchNormalizeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__HTTPValidationError"}}}}}}},"/cbetl/api/addresses/parse":{"post":{"tags":["cbetl","addresses"],"summary":"Parse a raw address string","description":"Parse a raw address string into components.\n\nUses libpostal for parsing when available, with regex fallback.\nUseful for processing unstructured address data.","operationId":"parse_address_api_addresses_parse_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__AddressParseRequest"}}},"required":true},"responses":{"200":{"description":"Successfully parsed address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__AddressResponse"}}}},"422":{"description":"Address parsing failed"}}}},"/cbetl/health":{"get":{"tags":["cbetl","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__HealthResponse"}}}}}}},"/cbetl/health/detailed":{"get":{"tags":["cbetl","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__HealthResponse"}}}}}}},"/cbetl/prime":{"get":{"tags":["cbetl","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__PrimeResponse"}}}}}}},"/cbetl/status":{"get":{"tags":["cbetl","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbetl__StatusResponse"}}}}}}},"/cbfiles/api/v1/admin/buckets/{bucket_name}/derivatives":{"post":{"tags":["cbfiles","Admin"],"summary":"Generate JPEG twins for images browsers cannot decode","description":"Some images are indexed and searchable but cannot be shown: browsers do not decode HEIC/HEIF/TIFF in an `<img>`. Measured on `vip-marketing` 2026-08-16, that was **1,268 of 3,817 indexed files** — a third of the image library, described beautifully by the vision model and then discarded by every consumer.\n\nThis writes a JPEG twin under the reserved `_derived/` prefix, which search then hands out in place of the original. Idempotent, additive, and it never touches an original. Runs in the background; poll the bucket or watch the log for progress.","operationId":"generate_derivatives_api_v1_admin_buckets__bucket_name__derivatives_post","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Limit"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Generate Derivatives Api V1 Admin Buckets  Bucket Name  Derivatives Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/minio/buckets":{"get":{"tags":["cbfiles","Admin"],"summary":"List MinIO buckets directly","description":"List all buckets directly from MinIO, bypassing the registry.","operationId":"list_minio_buckets_api_v1_admin_minio_buckets_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/minio/health":{"get":{"tags":["cbfiles","Admin"],"summary":"Check MinIO health","description":"Verify that the MinIO server is reachable and healthy.","operationId":"check_minio_health_api_v1_admin_minio_health_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/minio/restart":{"post":{"tags":["cbfiles","Admin"],"summary":"Restart MinIO server","description":"Restart the MinIO storage server process.","operationId":"restart_minio_api_v1_admin_minio_restart_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MinioServerStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/minio/start":{"post":{"tags":["cbfiles","Admin"],"summary":"Start MinIO server","description":"Start the MinIO storage server process.","operationId":"start_minio_api_v1_admin_minio_start_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MinioServerStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/minio/status":{"get":{"tags":["cbfiles","Admin"],"summary":"Get MinIO server status","description":"Return the current status of the MinIO storage server process.","operationId":"get_minio_status_api_v1_admin_minio_status_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MinioServerStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/minio/stop":{"post":{"tags":["cbfiles","Admin"],"summary":"Stop MinIO server","description":"Stop the MinIO storage server process.","operationId":"stop_minio_api_v1_admin_minio_stop_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MinioServerStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/registry/drive-backends":{"get":{"tags":["cbfiles","Admin"],"summary":"List all drive backends (operator view)","description":"Operator-only: list every registered drive backend across all owners. Intended for debugging registry state without needing to open registry.duckdb directly (which would conflict with the API's writer lock). See cbfiles#55 bug 3.","operationId":"admin_list_drive_backends_api_v1_admin_registry_drive_backends_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/registry/reap-stale-syncs":{"post":{"tags":["cbfiles","Admin"],"summary":"Reap stale pending/running sync jobs","description":"Operator-only: manually trigger the sync-job staleness reaper. Marks pending jobs older than pending_timeout_seconds (with NULL started_at) and running jobs older than running_timeout_seconds as failed. Returns the count reaped.","operationId":"admin_reap_stale_syncs_api_v1_admin_registry_reap_stale_syncs_post","parameters":[{"name":"pending_timeout_seconds","in":"query","required":false,"schema":{"type":"integer","default":1800,"title":"Pending Timeout Seconds"}},{"name":"running_timeout_seconds","in":"query","required":false,"schema":{"type":"integer","default":7200,"title":"Running Timeout Seconds"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/registry/sync-jobs":{"get":{"tags":["cbfiles","Admin"],"summary":"List recent sync jobs across all backends","description":"Operator-only: list the N most recent drive_sync_jobs rows regardless of owner/backend. Useful for spotting stuck pending jobs without touching registry.duckdb directly.","operationId":"admin_list_sync_jobs_api_v1_admin_registry_sync_jobs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"status_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Filter"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/screenshot/scan-publish":{"post":{"tags":["cbfiles","Admin"],"summary":"Publish new Loom screenshot reports to the mesh (user issue channel)","description":"System scan of the `loom-screenshots` bucket; publishes `service.cbfiles.screenshot.uploaded` to the apimesh bus for each capture new since the watermark, then advances it. First sight of the bucket baselines (no backlog replay). The user's note/email are read from each capture's `<key>.json` sidecar. cbhive subscribes and files a work-request — it never reads the bucket. cbfiles owns `loom-screenshots`, so this reads system-level (not the cbcron caller's grant). Registered with cbcron. Returns {published, baselined}.","operationId":"screenshot_scan_publish_api_v1_admin_screenshot_scan_publish_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/admin/stash/scan-publish":{"post":{"tags":["cbfiles","Admin"],"summary":"Publish new Stash doc-drops to the mesh (Kevin push pipeline)","description":"System scan of the watched `home/<workspace>` prefixes; publishes `cbfiles.home.uploaded` to the apimesh bus for each doc new since the per-workspace watermark, then advances it. First sight of a workspace baselines (no history replay). cbhive subscribes and files a work-request — it never reads the bucket. cbfiles owns `home`, so this reads system-level (not the cbcron caller's grant). Registered with cbcron. Returns {published, baselined, by_workspace}.","operationId":"stash_scan_publish_api_v1_admin_stash_scan_publish_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/buckets":{"get":{"tags":["cbfiles","Buckets"],"summary":"List buckets","description":"List storage buckets accessible to the current user.","operationId":"list_buckets_api_v1_buckets_get","parameters":[{"name":"include_public","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include Public"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__Bucket"},"title":"Response List Buckets Api V1 Buckets Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"post":{"tags":["cbfiles","Buckets"],"summary":"Create bucket","description":"Create a new storage bucket with the given name and access mode.","operationId":"create_bucket_api_v1_buckets_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__BucketCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__Bucket"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/buckets/{bucket_name}":{"get":{"tags":["cbfiles","Buckets"],"summary":"Get bucket details","description":"Retrieve details of a specific storage bucket by name.","operationId":"get_bucket_api_v1_buckets__bucket_name__get","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__Bucket"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"patch":{"tags":["cbfiles","Buckets"],"summary":"Update bucket settings","description":"Update access mode or description for a bucket.","operationId":"update_bucket_api_v1_buckets__bucket_name__patch","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__BucketUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__Bucket"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"delete":{"tags":["cbfiles","Buckets"],"summary":"Delete bucket","description":"Delete a bucket. Use force=true to delete all contained files first.","operationId":"delete_bucket_api_v1_buckets__bucket_name__delete","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/buckets/{bucket_name}/files":{"get":{"tags":["cbfiles","Files"],"summary":"List files in bucket","description":"List files in a bucket with optional prefix filtering.","operationId":"list_files_api_v1_buckets__bucket_name__files_get","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"prefix","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prefix"}},{"name":"recursive","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Recursive"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__FileInfo"},"title":"Response List Files Api V1 Buckets  Bucket Name  Files Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/buckets/{bucket_name}/files/{object_name}":{"post":{"tags":["cbfiles","Files"],"summary":"Upload file","description":"Upload a file to a bucket. Triggers auto-indexing if the bucket has an index.","operationId":"upload_file_api_v1_buckets__bucket_name__files__object_name__post","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"object_name","in":"path","required":true,"schema":{"type":"string","title":"Object Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbfiles__Body_upload_file_api_v1_buckets__bucket_name__files__object_name__post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__FileUploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"get":{"tags":["cbfiles","Files"],"summary":"Download file","description":"Download a file from a bucket as binary content.","operationId":"download_file_api_v1_buckets__bucket_name__files__object_name__get","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"object_name","in":"path","required":true,"schema":{"type":"string","title":"Object Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"delete":{"tags":["cbfiles","Files"],"summary":"Delete file","description":"Delete a file from a bucket.","operationId":"delete_file_api_v1_buckets__bucket_name__files__object_name__delete","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"object_name","in":"path","required":true,"schema":{"type":"string","title":"Object Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"head":{"tags":["cbfiles","Files"],"summary":"Get file metadata","description":"Return file metadata (content type, size, ETag) via response headers.","operationId":"get_file_info_api_v1_buckets__bucket_name__files__object_name__head","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"object_name","in":"path","required":true,"schema":{"type":"string","title":"Object Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__FileInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/buckets/{bucket_name}/files/{object_name}/signed-url":{"post":{"tags":["cbfiles","Files"],"summary":"Generate signed download URL","description":"Generate a time-limited presigned URL for downloading a file.","operationId":"get_signed_download_url_api_v1_buckets__bucket_name__files__object_name__signed_url_post","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"object_name","in":"path","required":true,"schema":{"type":"string","title":"Object Name"}},{"name":"expires_in","in":"query","required":false,"schema":{"type":"integer","maximum":604800,"minimum":60,"default":3600,"title":"Expires In"}},{"name":"shorten","in":"query","required":false,"schema":{"type":"boolean","description":"Create a short URL via cblinks","default":false,"title":"Shorten"},"description":"Create a short URL via cblinks"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__SignedUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/buckets/{bucket_name}/files/{object_name}/upload-url":{"post":{"tags":["cbfiles","Files"],"summary":"Generate signed upload URL","description":"Generate a time-limited presigned URL for uploading a file directly to storage.","operationId":"get_signed_upload_url_api_v1_buckets__bucket_name__files__object_name__upload_url_post","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"object_name","in":"path","required":true,"schema":{"type":"string","title":"Object Name"}},{"name":"expires_in","in":"query","required":false,"schema":{"type":"integer","maximum":604800,"minimum":60,"default":3600,"title":"Expires In"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__SignedUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/buckets/{bucket_name}/zip":{"post":{"tags":["cbfiles","Buckets"],"summary":"Zip objects into one archive","description":"Bundle several existing objects in this bucket into a single .zip object written back into the bucket. Returns the new object's name and size. The archive is built off the event loop (asyncio.to_thread) and is NOT auto-indexed — it's a share artifact, not a media asset.","operationId":"zip_objects_api_v1_buckets__bucket_name__zip_post","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__ZipRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__FileUploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/buckets/{bucket}/grants":{"post":{"tags":["cbfiles","Shares"],"summary":"Share files with a user","description":"Grant a user read/write access. Provide either `objects` (one key for a one-off, many for a multi-select) or `prefix` (a whole folder).","operationId":"create_grants_api_v1_buckets__bucket__grants_post","parameters":[{"name":"bucket","in":"path","required":true,"schema":{"type":"string","title":"Bucket"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-on-behalf-of","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__FileGrantCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__FileGrant"},"title":"Response Create Grants Api V1 Buckets  Bucket  Grants Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"get":{"tags":["cbfiles","Shares"],"summary":"List grants on a bucket","description":"List active share grants on a bucket (owner only). Optional `path` filter.","operationId":"list_resource_grants_api_v1_buckets__bucket__grants_get","parameters":[{"name":"bucket","in":"path","required":true,"schema":{"type":"string","title":"Bucket"}},{"name":"path","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__FileGrant"},"title":"Response List Resource Grants Api V1 Buckets  Bucket  Grants Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/buckets/{bucket}/grants/{grant_id}":{"delete":{"tags":["cbfiles","Shares"],"summary":"Revoke a grant","description":"Revoke a share grant. Allowed for the grantor or the bucket owner.","operationId":"revoke_grant_api_v1_buckets__bucket__grants__grant_id__delete","parameters":[{"name":"bucket","in":"path","required":true,"schema":{"type":"string","title":"Bucket"}},{"name":"grant_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Grant Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-on-behalf-of","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/drives":{"post":{"tags":["cbfiles","Google Drive"],"summary":"Register Google Drive backend","description":"Register a new Google Shared Drive as a read-only storage backend.","operationId":"register_drive_api_v1_drives_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__GoogleDriveBackendCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__GoogleDriveBackendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"get":{"tags":["cbfiles","Google Drive"],"summary":"List Google Drive backends","description":"List all registered Google Drive backends for the current user.","operationId":"list_drives_api_v1_drives_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__GoogleDriveBackendResponse"},"title":"Response List Drives Api V1 Drives Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/drives/{backend_id}":{"get":{"tags":["cbfiles","Google Drive"],"summary":"Get Google Drive backend","description":"Retrieve details of a specific Google Drive backend.","operationId":"get_drive_api_v1_drives__backend_id__get","parameters":[{"name":"backend_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Backend Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__GoogleDriveBackendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"delete":{"tags":["cbfiles","Google Drive"],"summary":"Remove Google Drive backend","description":"Remove a Google Drive backend and its local bucket entry.","operationId":"remove_drive_api_v1_drives__backend_id__delete","parameters":[{"name":"backend_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Backend Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/drives/{backend_id}/sync":{"post":{"tags":["cbfiles","Google Drive"],"summary":"Trigger Drive sync","description":"Start a background Drive-to-MinIO sync job for this backend.","operationId":"trigger_sync_api_v1_drives__backend_id__sync_post","parameters":[{"name":"backend_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Backend Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbfiles__SyncTriggerRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__SyncJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"get":{"tags":["cbfiles","Google Drive"],"summary":"List sync jobs","description":"List recent sync jobs for a specific Google Drive backend.","operationId":"list_sync_jobs_api_v1_drives__backend_id__sync_get","parameters":[{"name":"backend_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Backend Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__SyncJobResponse"},"title":"Response List Sync Jobs Api V1 Drives  Backend Id  Sync Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/drives/{backend_id}/sync/{job_id}":{"get":{"tags":["cbfiles","Google Drive"],"summary":"Get sync job status","description":"Get status and progress of a specific sync job.","operationId":"get_sync_job_api_v1_drives__backend_id__sync__job_id__get","parameters":[{"name":"backend_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Backend Id"}},{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__SyncJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"delete":{"tags":["cbfiles","Google Drive"],"summary":"Delete sync job (admin purge)","description":"Admin-only: hard-delete a sync job row. Intended for purging orphan jobs that the staleness reaper can't clear (e.g., manual operator cleanup). Does NOT touch drive_synced_files ledger rows.","operationId":"delete_sync_job_api_v1_drives__backend_id__sync__job_id__delete","parameters":[{"name":"backend_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Backend Id"}},{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/drives/{backend_id}/test":{"post":{"tags":["cbfiles","Google Drive"],"summary":"Test Drive connectivity","description":"Test Google Drive API connectivity for a registered backend.","operationId":"test_drive_connectivity_api_v1_drives__backend_id__test_post","parameters":[{"name":"backend_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Backend Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/gdrive":{"post":{"tags":["cbfiles","GDrive"],"summary":"Register GCS backend","description":"Register a new Google Cloud Storage backend via GCS HMAC keys.","operationId":"register_drive_api_v1_gdrive_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__GDriveBackendCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__GDriveBackendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"get":{"tags":["cbfiles","GDrive"],"summary":"List GCS backends","description":"List all registered GDrive/GCS backends for the current user.","operationId":"list_drives_api_v1_gdrive_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__GDriveBackendResponse"},"title":"Response List Drives Api V1 Gdrive Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/gdrive/{backend_id}":{"get":{"tags":["cbfiles","GDrive"],"summary":"Get GCS backend","description":"Retrieve details of a specific GDrive/GCS backend.","operationId":"get_drive_api_v1_gdrive__backend_id__get","parameters":[{"name":"backend_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Backend Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__GDriveBackendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"delete":{"tags":["cbfiles","GDrive"],"summary":"Remove GCS backend","description":"Remove a GDrive/GCS backend and its local bucket entry.","operationId":"remove_drive_api_v1_gdrive__backend_id__delete","parameters":[{"name":"backend_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Backend Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/gdrive/{backend_id}/test":{"post":{"tags":["cbfiles","GDrive"],"summary":"Test GCS connectivity","description":"Test GCS connectivity for a registered backend.","operationId":"test_drive_connectivity_api_v1_gdrive__backend_id__test_post","parameters":[{"name":"backend_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Backend Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/index/{bucket_name}":{"post":{"tags":["cbfiles","Search"],"summary":"Trigger content indexing for a bucket","description":"Start a background indexing job that processes all files in the bucket. Returns immediately with a job ID for polling.","operationId":"trigger_index_api_v1_index__bucket_name__post","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"text_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only index plain text files (skip OCR for PDFs/images)","default":false,"title":"Text Only"},"description":"Only index plain text files (skip OCR for PDFs/images)"},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Force a full rebuild: clear the bucket's indexed-file dedup rows first so every file is re-processed (not skipped by etag). Use when migrating a store to a new backend or after corruption.","default":false,"title":"Force"},"description":"Force a full rebuild: clear the bucket's indexed-file dedup rows first so every file is re-processed (not skipped by etag). Use when migrating a store to a new backend or after corruption."},{"name":"heal_orphans","in":"query","required":false,"schema":{"type":"boolean","description":"Targeted heal: clear only the orphaned dedup rows (chunk_count = 0 — recorded but never vectorized, e.g. images that got no caption under the old dead-ollama path) so the next run re-processes just those, leaving real vectors untouched. Much cheaper than force when only a subset is missing. Ignored if force is set. See cbfiles#67.","default":false,"title":"Heal Orphans"},"description":"Targeted heal: clear only the orphaned dedup rows (chunk_count = 0 — recorded but never vectorized, e.g. images that got no caption under the old dead-ollama path) so the next run re-processes just those, leaving real vectors untouched. Much cheaper than force when only a subset is missing. Ignored if force is set. See cbfiles#67."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__IndexJobResponse"}}}},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__ErrorResponse"}}},"description":"Conflict"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"get":{"tags":["cbfiles","Search"],"summary":"List index jobs for a bucket","description":"List index jobs for a bucket, most recent first.","operationId":"list_index_jobs_api_v1_index__bucket_name__get","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__IndexJobResponse"},"title":"Response List Index Jobs Api V1 Index  Bucket Name  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/index/{bucket_name}/{job_id}":{"get":{"tags":["cbfiles","Search"],"summary":"Get index job status","description":"Get the status of a specific index job.","operationId":"get_index_job_api_v1_index__bucket_name___job_id__get","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__IndexJobResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/media/brands":{"get":{"tags":["cbfiles","Media"],"summary":"List brands","description":"List all brands with their media asset counts.","operationId":"list_brands_api_v1_media_brands_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__BrandSummary"},"title":"Response List Brands Api V1 Media Brands Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/media/recent":{"get":{"tags":["cbfiles","Media"],"summary":"Get recent media assets","description":"Get the most recently added media assets.","operationId":"recent_assets_api_v1_media_recent_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__MediaAssetResponse"},"title":"Response Recent Assets Api V1 Media Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/media/search":{"get":{"tags":["cbfiles","Media"],"summary":"Search media assets","description":"Search media assets by brand, tags, and asset type with pagination.","operationId":"search_media_api_v1_media_search_get","parameters":[{"name":"brand","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by brand slug","title":"Brand"},"description":"Filter by brand slug"},{"name":"tags","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated tags, e.g. content:pool,mood:luxurious","title":"Tags"},"description":"Comma-separated tags, e.g. content:pool,mood:luxurious"},{"name":"asset_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by asset type: image, video, audio, note, or document","title":"Asset Type"},"description":"Filter by asset type: image, video, audio, note, or document"},{"name":"bucket","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by MinIO bucket (e.g., home, vip-media)","title":"Bucket"},"description":"Filter by MinIO bucket (e.g., home, vip-media)"},{"name":"tenant_app","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tenant app slug (e.g., cb, greenroom)","title":"Tenant App"},"description":"Filter by tenant app slug (e.g., cb, greenroom)"},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by owning user UUID","title":"User Id"},"description":"Filter by owning user UUID"},{"name":"prefix","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by object_name prefix","title":"Prefix"},"description":"Filter by object_name prefix"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MediaSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/media/sync":{"post":{"tags":["cbfiles","Media"],"summary":"Trigger media sync","description":"Start a background Google Drive sync for brand media folders.","operationId":"trigger_sync_api_v1_media_sync_post","parameters":[{"name":"brands","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Brand slugs to sync (default: all)","title":"Brands"},"description":"Brand slugs to sync (default: all)"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MediaSyncJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"get":{"tags":["cbfiles","Media"],"summary":"List media sync jobs","description":"List recent media sync jobs.","operationId":"list_sync_jobs_api_v1_media_sync_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__MediaSyncJobResponse"},"title":"Response List Sync Jobs Api V1 Media Sync Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/media/sync/{job_id}":{"get":{"tags":["cbfiles","Media"],"summary":"Get media sync job status","description":"Get status and progress of a specific media sync job.","operationId":"get_sync_job_api_v1_media_sync__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MediaSyncJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/media/upload":{"post":{"tags":["cbfiles","Media"],"summary":"Upload media asset","description":"Upload a media asset directly with optional AI auto-tagging.","operationId":"upload_media_api_v1_media_upload_post","parameters":[{"name":"brand","in":"query","required":true,"schema":{"type":"string","description":"Brand slug","title":"Brand"},"description":"Brand slug"},{"name":"auto_tag","in":"query","required":false,"schema":{"type":"boolean","description":"Auto-tag via AI vision","default":true,"title":"Auto Tag"},"description":"Auto-tag via AI vision"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbfiles__Body_upload_media_api_v1_media_upload_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MediaUploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/media/upsert":{"post":{"tags":["cbfiles","Media"],"summary":"Upsert media asset by (bucket, object_name)","description":"Idempotent upsert for external taggers (e.g., cbintel Intel scan). Identifies the asset by (bucket, object_name); creates or updates the row with the supplied tags/description and stamps `inferred_at`.","operationId":"upsert_asset_api_v1_media_upsert_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MediaAssetUpsertRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MediaAssetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/media/{asset_id}":{"get":{"tags":["cbfiles","Media"],"summary":"Get media asset","description":"Retrieve a single media asset by its UUID.","operationId":"get_asset_api_v1_media__asset_id__get","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MediaAssetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/media/{asset_id}/tag":{"post":{"tags":["cbfiles","Media"],"summary":"Re-tag media asset","description":"Re-run AI vision tagging on a media asset.","operationId":"retag_asset_api_v1_media__asset_id__tag_post","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MediaAssetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/media/{asset_id}/tags":{"patch":{"tags":["cbfiles","Media"],"summary":"Update media tags","description":"Manually update the tags on a media asset.","operationId":"update_tags_api_v1_media__asset_id__tags_patch","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MediaTagUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__MediaAssetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/public/{bucket_name}/{object_name}":{"get":{"tags":["cbfiles","Public"],"summary":"Public file download","description":"Download a file from a public bucket without authentication.","operationId":"public_download_api_v1_public__bucket_name___object_name__get","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"object_name","in":"path","required":true,"schema":{"type":"string","title":"Object Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"head":{"tags":["cbfiles","Public"],"summary":"Public file metadata","description":"Get file metadata from a public bucket without authentication.","operationId":"public_file_info_api_v1_public__bucket_name___object_name__head","parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string","title":"Bucket Name"}},{"name":"object_name","in":"path","required":true,"schema":{"type":"string","title":"Object Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/query/sql":{"post":{"tags":["cbfiles","Query","Query"],"summary":"Execute read-only SQL","description":"Execute a read-only SQL query against the cbfiles registry DuckDB. Only SELECT/WITH/EXPLAIN allowed.","operationId":"execute_sql_api_v1_query_sql_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__SQLRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/search":{"post":{"tags":["cbfiles","Search"],"summary":"Semantic search across indexed files","description":"Search file contents by meaning using vector similarity. Returns ranked results with presigned download URLs.","operationId":"search_files_api_v1_search_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__SearchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__SearchResponse"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__ErrorResponse"}}},"description":"Bad Request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/shared-by-me":{"get":{"tags":["cbfiles","Shares"],"summary":"Files I've shared","description":"Active grants the current user has issued (manage/revoke view).","operationId":"shared_by_me_api_v1_shared_by_me_get","parameters":[{"name":"x-on-behalf-of","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__FileGrant"},"title":"Response Shared By Me Api V1 Shared By Me Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/shared-with-me":{"get":{"tags":["cbfiles","Shares"],"summary":"Files shared with me","description":"Active grants TO the current user, with grantor provenance ('from X').","operationId":"shared_with_me_api_v1_shared_with_me_get","parameters":[{"name":"x-on-behalf-of","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__SharedItem"},"title":"Response Shared With Me Api V1 Shared With Me Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/usage":{"get":{"tags":["cbfiles","Usage"],"summary":"List usage records","description":"Query paginated usage records with optional filters by user, operation, bucket, or time range.","operationId":"list_usage_records_api_v1_usage_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter by user ID","title":"User Id"},"description":"Filter by user ID"},{"name":"operation","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbfiles__FileOperation"},{"type":"null"}],"description":"Filter by operation type","title":"Operation"},"description":"Filter by operation type"},{"name":"bucket","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by bucket name","title":"Bucket"},"description":"Filter by bucket name"},{"name":"start_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of time range (ISO format)","title":"Start Time"},"description":"Start of time range (ISO format)"},{"name":"end_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of time range (ISO format)","title":"End Time"},"description":"End of time range (ISO format)"},{"name":"success","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by success status","title":"Success"},"description":"Filter by success status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum records to return","default":100,"title":"Limit"},"description":"Maximum records to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of records to skip","default":0,"title":"Offset"},"description":"Number of records to skip"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__UsageRecordResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/usage/storage":{"get":{"tags":["cbfiles","Usage"],"summary":"Get storage usage","description":"Get current storage usage by user and bucket (authenticated).","operationId":"get_storage_usage_api_v1_usage_storage_get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to specific user","title":"User Id"},"description":"Filter to specific user"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__StorageUsageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/usage/summary":{"get":{"tags":["cbfiles","Usage"],"summary":"Get usage summary","description":"Get aggregated usage statistics for a time period (authenticated).","operationId":"get_usage_summary_api_v1_usage_summary_get","parameters":[{"name":"start_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of time range (ISO format)","title":"Start Time"},"description":"Start of time range (ISO format)"},{"name":"end_time","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of time range (ISO format)","title":"End Time"},"description":"End of time range (ISO format)"},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter by user ID","title":"User Id"},"description":"Filter by user ID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__UsageSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/users":{"get":{"tags":["cbfiles","Users"],"summary":"List users","description":"List all registered users. Admin only.","operationId":"list_users_api_v1_users_get","parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__ServiceUser"},"title":"Response List Users Api V1 Users Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"post":{"tags":["cbfiles","Users"],"summary":"Create user","description":"Create a new service user account. Admin only.","operationId":"create_user_api_v1_users_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__ServiceUserCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__ServiceUser"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/users/me":{"get":{"tags":["cbfiles","Users"],"summary":"Get current user","description":"Return the profile of the currently authenticated user.","operationId":"get_current_user_api_v1_users_me_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__ServiceUser"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/users/me/keys":{"get":{"tags":["cbfiles","Users"],"summary":"List API keys","description":"List all API keys belonging to the current user.","operationId":"list_api_keys_api_v1_users_me_keys_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbfiles__APIKeyInfo"},"title":"Response List Api Keys Api V1 Users Me Keys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"post":{"tags":["cbfiles","Users"],"summary":"Create API key","description":"Create a new API key. The full key is only returned once in this response.","operationId":"create_api_key_api_v1_users_me_keys_post","parameters":[{"name":"key_name","in":"query","required":true,"schema":{"type":"string","title":"Key Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__APIKeyCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/users/me/keys/{key_id}":{"delete":{"tags":["cbfiles","Users"],"summary":"Revoke API key","description":"Revoke an API key so it can no longer be used for authentication.","operationId":"revoke_api_key_api_v1_users_me_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/api/v1/users/{user_id}":{"get":{"tags":["cbfiles","Users"],"summary":"Get user by ID","description":"Retrieve a specific user by their UUID.","operationId":"get_user_api_v1_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__ServiceUser"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}},"delete":{"tags":["cbfiles","Users"],"summary":"Deactivate user","description":"Soft-delete a user by deactivating their account.","operationId":"deactivate_user_api_v1_users__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HTTPValidationError"}}}}}}},"/cbfiles/health":{"get":{"tags":["cbfiles","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HealthResponse"}}}}}}},"/cbfiles/health/detailed":{"get":{"tags":["cbfiles","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__HealthResponse"}}}}}}},"/cbfiles/prime":{"get":{"tags":["cbfiles","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__PrimeResponse"}}}}}}},"/cbfiles/status":{"get":{"tags":["cbfiles","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfiles__StatusResponse"}}}}}}},"/cbforge/api/v1/graphs":{"get":{"tags":["cbforge","Services"],"summary":"Graph catalog — file corpus + mounted service graphs","operationId":"list_graphs_api_v1_graphs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response List Graphs Api V1 Graphs Get"}}}}}}},"/cbforge/api/v1/graphs/{graph_id}":{"get":{"tags":["cbforge","Services"],"summary":"One graph's full definition (node tree + raw source)","operationId":"get_graph_api_v1_graphs__graph_id__get","parameters":[{"name":"graph_id","in":"path","required":true,"schema":{"type":"string","title":"Graph Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Graph Api V1 Graphs  Graph Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbforge__HTTPValidationError"}}}}}}},"/cbforge/api/v1/ops":{"get":{"tags":["cbforge","Services"],"summary":"Registered operation catalog","operationId":"list_ops_api_v1_ops_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response List Ops Api V1 Ops Get"}}}}}}},"/cbforge/api/v1/services":{"get":{"tags":["cbforge","Services"],"summary":"List composed services","operationId":"list_services_api_v1_services_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response List Services Api V1 Services Get"}}}}}}},"/cbforge/api/v1/services/cbhelm/stream":{"get":{"tags":["cbforge","Services"],"summary":"cbhelm live event stream (SSE): transcript frames + decision tee-ups","operationId":"cbhelm_stream_api_v1_services_cbhelm_stream_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbforge/api/v1/services/cbhelm/transcript":{"get":{"tags":["cbforge","Services"],"summary":"cbhelm recent transcript (history the SSE stream continues from)","operationId":"cbhelm_transcript_api_v1_services_cbhelm_transcript_get","parameters":[{"name":"slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Cbhelm Transcript Api V1 Services Cbhelm Transcript Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbforge__HTTPValidationError"}}}}}}},"/cbforge/api/v1/services/{service}/{entrypoint}":{"post":{"tags":["cbforge","Services"],"summary":"Invoke an entrypoint (body = graph inputs)","operationId":"run_entrypoint_api_v1_services__service___entrypoint__post","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","title":"Service"}},{"name":"entrypoint","in":"path","required":true,"schema":{"type":"string","title":"Entrypoint"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Run Entrypoint Api V1 Services  Service   Entrypoint  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbforge__HTTPValidationError"}}}}}},"get":{"tags":["cbforge","Services"],"summary":"Invoke a read-flavored entrypoint (query params = graph inputs)","operationId":"get_entrypoint_api_v1_services__service___entrypoint__get","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","title":"Service"}},{"name":"entrypoint","in":"path","required":true,"schema":{"type":"string","title":"Entrypoint"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Entrypoint Api V1 Services  Service   Entrypoint  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbforge__HTTPValidationError"}}}}}}},"/cbforge/health":{"get":{"tags":["cbforge","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbforge__HealthResponse"}}}}}}},"/cbforge/health/detailed":{"get":{"tags":["cbforge","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbforge__HealthResponse"}}}}}}},"/cbforge/prime":{"get":{"tags":["cbforge","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbforge__PrimeResponse"}}}}}}},"/cbforge/status":{"get":{"tags":["cbforge","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbforge__StatusResponse"}}}}}}},"/cbfront/accounts":{"get":{"tags":["cbfront","Accounts"],"summary":"List accessible accounts","description":"List Front accounts the authenticated user can access.","operationId":"list_accounts_accounts_get","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Accounts Accounts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/analytics/exports":{"post":{"tags":["cbfront","Analytics"],"summary":"Create analytics export","description":"Create a new analytics export. Front processes exports asynchronously. Poll GET /analytics/exports/{export_id} for status.","operationId":"create_export_accounts__account_id__analytics_exports_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__AnalyticsExportRequest"}}}},"responses":{"200":{"description":"The created analytics export.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Export Accounts  Account Id  Analytics Exports Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/analytics/exports/{export_id}":{"get":{"tags":["cbfront","Analytics"],"summary":"Get analytics export","description":"Get the status of an analytics export. Poll until status is 'done' to get the download URL.","operationId":"get_export_accounts__account_id__analytics_exports__export_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"export_id","in":"path","required":true,"schema":{"type":"string","title":"Export Id"}}],"responses":{"200":{"description":"Analytics export status.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Export Accounts  Account Id  Analytics Exports  Export Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/channels/{channel_id}/drafts":{"post":{"tags":["cbfront","Messages","Drafts"],"summary":"Create channel draft","description":"Create a new draft email on a channel (starts a new conversation).","operationId":"create_channel_draft_accounts__account_id__channels__channel_id__drafts_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__CreateChannelDraftRequest"}}}},"responses":{"200":{"description":"The created draft message.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Channel Draft Accounts  Account Id  Channels  Channel Id  Drafts Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/channels/{channel_id}/send":{"post":{"tags":["cbfront","Messages"],"summary":"Send message","description":"Send a new email message via a channel.","operationId":"send_message_accounts__account_id__channels__channel_id__send_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__SendMessageRequest"}}}},"responses":{"200":{"description":"The sent message.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Send Message Accounts  Account Id  Channels  Channel Id  Send Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/contacts":{"get":{"tags":["cbfront","Contacts"],"summary":"List contacts","description":"List or search Front contacts with pagination.","operationId":"list_contacts_accounts__account_id__contacts_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search query for contacts.","title":"Q"},"description":"Search query for contacts."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page.","default":50,"title":"Limit"},"description":"Items per page."},{"name":"page_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque token for the next page.","title":"Page Token"},"description":"Opaque token for the next page."}],"responses":{"200":{"description":"Paginated list of contacts.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Contacts Accounts  Account Id  Contacts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/contacts/{contact_id}":{"get":{"tags":["cbfront","Contacts"],"summary":"Get contact","description":"Get details for a specific Front contact.","operationId":"get_contact_accounts__account_id__contacts__contact_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"responses":{"200":{"description":"Contact details.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Contact Accounts  Account Id  Contacts  Contact Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/conversations":{"get":{"tags":["cbfront","Conversations"],"summary":"Search conversations","description":"Search and filter Front conversations. At least one filter parameter is required.","operationId":"list_conversations_accounts__account_id__conversations_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search query.","title":"Q"},"description":"Free-text search query."},{"name":"inbox_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by inbox ID.","title":"Inbox Id"},"description":"Filter by inbox ID."},{"name":"tag_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tag ID.","title":"Tag Id"},"description":"Filter by tag ID."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status (assigned, unassigned, archived, deleted).","title":"Status"},"description":"Filter by status (assigned, unassigned, archived, deleted)."},{"name":"assignee_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by assignee ID.","title":"Assignee Id"},"description":"Filter by assignee ID."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page.","default":50,"title":"Limit"},"description":"Items per page."},{"name":"page_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque token for the next page.","title":"Page Token"},"description":"Opaque token for the next page."}],"responses":{"200":{"description":"Paginated list of conversations.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Conversations Accounts  Account Id  Conversations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/conversations/{conversation_id}":{"get":{"tags":["cbfront","Conversations"],"summary":"Get conversation detail","description":"Get a conversation with its full message history. Merges conversation metadata with messages.","operationId":"get_conversation_accounts__account_id__conversations__conversation_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Conversation detail with messages.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Conversation Accounts  Account Id  Conversations  Conversation Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}},"patch":{"tags":["cbfront","Conversations"],"summary":"Update conversation","description":"Update a conversation's status, assignee, inbox, tags, or subject.","operationId":"update_conversation_accounts__account_id__conversations__conversation_id__patch","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__UpdateConversationRequest"}}}},"responses":{"200":{"description":"Updated conversation.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Conversation Accounts  Account Id  Conversations  Conversation Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/conversations/{conversation_id}/assignee":{"put":{"tags":["cbfront","Conversations"],"summary":"Assign conversation","description":"Assign or unassign a conversation. Pass empty assignee_id to unassign.","operationId":"assign_conversation_accounts__account_id__conversations__conversation_id__assignee_put","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__AssignConversationRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/conversations/{conversation_id}/comments":{"get":{"tags":["cbfront","Conversations"],"summary":"List conversation comments","description":"List internal comments on a conversation.","operationId":"list_comments_accounts__account_id__conversations__conversation_id__comments_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page.","default":50,"title":"Limit"},"description":"Items per page."},{"name":"page_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque token for the next page.","title":"Page Token"},"description":"Opaque token for the next page."}],"responses":{"200":{"description":"Paginated list of comments.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Comments Accounts  Account Id  Conversations  Conversation Id  Comments Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}},"post":{"tags":["cbfront","Conversations"],"summary":"Create comment","description":"Add an internal comment (note) to a conversation.","operationId":"create_comment_accounts__account_id__conversations__conversation_id__comments_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__CreateCommentRequest"}}}},"responses":{"200":{"description":"The created comment.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Comment Accounts  Account Id  Conversations  Conversation Id  Comments Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/conversations/{conversation_id}/drafts":{"post":{"tags":["cbfront","Conversations","Drafts"],"summary":"Create conversation draft","description":"Create a draft reply on a conversation.","operationId":"create_conversation_draft_accounts__account_id__conversations__conversation_id__drafts_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__CreateDraftRequest"}}}},"responses":{"200":{"description":"The created draft message.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Conversation Draft Accounts  Account Id  Conversations  Conversation Id  Drafts Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/conversations/{conversation_id}/reply":{"post":{"tags":["cbfront","Conversations","Messages"],"summary":"Reply to conversation","description":"Send a reply message to a conversation.","operationId":"reply_to_conversation_accounts__account_id__conversations__conversation_id__reply_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__ReplyRequest"}}}},"responses":{"200":{"description":"The sent reply message.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reply To Conversation Accounts  Account Id  Conversations  Conversation Id  Reply Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/conversations/{conversation_id}/tags":{"post":{"tags":["cbfront","Conversations","Tags"],"summary":"Apply tags to conversation","description":"Add one or more tags to a conversation.","operationId":"apply_tags_accounts__account_id__conversations__conversation_id__tags_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__TagIdsRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}},"delete":{"tags":["cbfront","Conversations","Tags"],"summary":"Remove tags from conversation","description":"Remove one or more tags from a conversation.","operationId":"remove_tags_accounts__account_id__conversations__conversation_id__tags_delete","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__TagIdsRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/drafts/{draft_id}":{"delete":{"tags":["cbfront","Drafts"],"summary":"Delete draft","description":"Delete a draft message. Requires the draft version for optimistic concurrency.","operationId":"delete_draft_accounts__account_id__drafts__draft_id__delete","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__DeleteDraftRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/inboxes":{"get":{"tags":["cbfront","Inboxes"],"summary":"List inboxes","description":"List all accessible Front inboxes with pagination.","operationId":"list_inboxes_accounts__account_id__inboxes_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page.","default":50,"title":"Limit"},"description":"Items per page."},{"name":"page_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque token for the next page.","title":"Page Token"},"description":"Opaque token for the next page."}],"responses":{"200":{"description":"Paginated list of inboxes.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Inboxes Accounts  Account Id  Inboxes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/inboxes/{inbox_id}":{"get":{"tags":["cbfront","Inboxes"],"summary":"Get inbox","description":"Get details for a specific Front inbox.","operationId":"get_inbox_accounts__account_id__inboxes__inbox_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"inbox_id","in":"path","required":true,"schema":{"type":"string","title":"Inbox Id"}}],"responses":{"200":{"description":"Inbox details.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Inbox Accounts  Account Id  Inboxes  Inbox Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/inboxes/{inbox_id}/channels":{"get":{"tags":["cbfront","Inboxes"],"summary":"List inbox channels","description":"List the channels (sending addresses) attached to an inbox. A draft or a send needs a `channel_id`, an inbox may carry several, and the id is not derivable from the inbox — so this is how a caller finds it.","operationId":"list_inbox_channels_accounts__account_id__inboxes__inbox_id__channels_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"inbox_id","in":"path","required":true,"schema":{"type":"string","title":"Inbox Id"}}],"responses":{"200":{"description":"The inbox's channels.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Inbox Channels Accounts  Account Id  Inboxes  Inbox Id  Channels Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/messages/{message_id}":{"get":{"tags":["cbfront","Messages"],"summary":"Get message","description":"Get a single message by ID with HTML-to-text conversion.","operationId":"get_message_accounts__account_id__messages__message_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"message_id","in":"path","required":true,"schema":{"type":"string","title":"Message Id"}}],"responses":{"200":{"description":"Message details with both HTML body and plain text.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Message Accounts  Account Id  Messages  Message Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/responses":{"get":{"tags":["cbfront","Canned Responses"],"summary":"List message templates","description":"List all Front message templates (canned responses) with pagination.","operationId":"list_responses_accounts__account_id__responses_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page.","default":50,"title":"Limit"},"description":"Items per page."},{"name":"page_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque token for the next page.","title":"Page Token"},"description":"Opaque token for the next page."}],"responses":{"200":{"description":"Paginated list of message templates.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Responses Accounts  Account Id  Responses Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/responses/{response_id}":{"get":{"tags":["cbfront","Canned Responses"],"summary":"Get message template","description":"Get a specific Front message template (canned response).","operationId":"get_response_accounts__account_id__responses__response_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"response_id","in":"path","required":true,"schema":{"type":"string","title":"Response Id"}}],"responses":{"200":{"description":"Message template details.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Response Accounts  Account Id  Responses  Response Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/tags":{"get":{"tags":["cbfront","Tags"],"summary":"List tags","description":"List all Front tags with pagination.","operationId":"list_tags_accounts__account_id__tags_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page.","default":50,"title":"Limit"},"description":"Items per page."},{"name":"page_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque token for the next page.","title":"Page Token"},"description":"Opaque token for the next page."}],"responses":{"200":{"description":"Paginated list of tags.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Tags Accounts  Account Id  Tags Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/accounts/{account_id}/teammates":{"get":{"tags":["cbfront","Teammates"],"summary":"List teammates","description":"List all Front teammates with pagination.","operationId":"list_teammates_accounts__account_id__teammates_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page.","default":50,"title":"Limit"},"description":"Items per page."},{"name":"page_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque token for the next page.","title":"Page Token"},"description":"Opaque token for the next page."}],"responses":{"200":{"description":"Paginated list of teammates.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Teammates Accounts  Account Id  Teammates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/admin/reload-accounts":{"post":{"tags":["cbfront","Admin"],"summary":"Reload account registry from disk + env","description":"Re-reads ``accounts.yaml`` and rebuilds every Front API client from the current process environment, then invalidates the inbox-membership cache. Use after rotating a Front token or editing the slug→teammate map; avoids a full service restart.","operationId":"reload_accounts_admin_reload_accounts_post","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Reload summary with the resulting account list.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reload Accounts Admin Reload Accounts Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HTTPValidationError"}}}}}}},"/cbfront/health":{"get":{"tags":["cbfront","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HealthResponse"}}}}}}},"/cbfront/health/detailed":{"get":{"tags":["cbfront","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__HealthResponse"}}}}}}},"/cbfront/prime":{"get":{"tags":["cbfront","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__PrimeResponse"}}}}}}},"/cbfront/status":{"get":{"tags":["cbfront","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbfront__StatusResponse"}}}}}}},"/cbgeo/health":{"get":{"tags":["cbgeo","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgeo__HealthResponse"}}}}}}},"/cbgeo/health/detailed":{"get":{"tags":["cbgeo","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgeo__HealthResponse"}}}}}}},"/cbgeo/lookup":{"get":{"tags":["cbgeo","Geocoding"],"summary":"Lookup OSM objects by ID","description":"Retrieve place details for specific OpenStreetMap objects by their OSM IDs.","operationId":"lookup_lookup_get","parameters":[{"name":"osm_ids","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated OSM IDs with type prefix","examples":["R113314,W123456,N789"],"title":"Osm Ids"},"description":"Comma-separated OSM IDs with type prefix"},{"name":"format","in":"query","required":false,"schema":{"type":"string","description":"Output format","default":"json","title":"Format"},"description":"Output format"},{"name":"addressdetails","in":"query","required":false,"schema":{"type":"integer","maximum":1,"minimum":0,"description":"Include address breakdown (0 or 1)","default":0,"title":"Addressdetails"},"description":"Include address breakdown (0 or 1)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgeo__SearchResult"},"title":"Response Lookup Lookup Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgeo__HTTPValidationError"}}}}}}},"/cbgeo/prime":{"get":{"tags":["cbgeo","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgeo__PrimeResponse"}}}}}}},"/cbgeo/reverse":{"get":{"tags":["cbgeo","Geocoding"],"summary":"Reverse geocode coordinates","description":"Convert latitude/longitude coordinates to a human-readable address using Nominatim.","operationId":"reverse_reverse_get","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","description":"Latitude","examples":[30.2747],"title":"Lat"},"description":"Latitude"},{"name":"lon","in":"query","required":true,"schema":{"type":"number","description":"Longitude","examples":[-97.7404],"title":"Lon"},"description":"Longitude"},{"name":"format","in":"query","required":false,"schema":{"type":"string","description":"Output format","enum":["json","jsonv2","geojson"],"default":"json","title":"Format"},"description":"Output format"},{"name":"addressdetails","in":"query","required":false,"schema":{"type":"integer","maximum":1,"minimum":0,"description":"Include address breakdown (0 or 1)","default":1,"title":"Addressdetails"},"description":"Include address breakdown (0 or 1)"},{"name":"zoom","in":"query","required":false,"schema":{"type":"integer","maximum":18,"minimum":0,"description":"Level of detail (0-18)","default":18,"title":"Zoom"},"description":"Level of detail (0-18)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgeo__ReverseResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgeo__HTTPValidationError"}}}}}}},"/cbgeo/search":{"get":{"tags":["cbgeo","Geocoding"],"summary":"Forward geocode address or place","description":"Convert an address or place name to coordinates. Returns ranked results with place details.","operationId":"search_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","description":"Search query (address or place name)","examples":["1600 Congress Ave, Austin, TX"],"title":"Q"},"description":"Search query (address or place name)"},{"name":"format","in":"query","required":false,"schema":{"type":"string","description":"Output format","enum":["json","jsonv2","geojson"],"default":"json","title":"Format"},"description":"Output format"},{"name":"addressdetails","in":"query","required":false,"schema":{"type":"integer","maximum":1,"minimum":0,"description":"Include address breakdown (0 or 1)","default":0,"title":"Addressdetails"},"description":"Include address breakdown (0 or 1)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Maximum number of results","default":10,"title":"Limit"},"description":"Maximum number of results"},{"name":"countrycodes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Limit to countries (comma-separated ISO codes)","examples":["us"],"title":"Countrycodes"},"description":"Limit to countries (comma-separated ISO codes)"},{"name":"viewbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Preferred area: west,south,east,north","examples":["-97.8,30.2,-97.7,30.3"],"title":"Viewbox"},"description":"Preferred area: west,south,east,north"},{"name":"bounded","in":"query","required":false,"schema":{"type":"integer","maximum":1,"minimum":0,"description":"Restrict to viewbox (0 or 1)","default":0,"title":"Bounded"},"description":"Restrict to viewbox (0 or 1)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgeo__SearchResult"},"title":"Response Search Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgeo__HTTPValidationError"}}}}}}},"/cbgeo/status":{"get":{"tags":["cbgeo","Status"],"summary":"Get Nominatim API status","description":"Returns current status of the Nominatim geocoding API including database version and data freshness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgeo__StatusResult"}}}}}}},"/cbgreenroom/api/v1/approvals":{"post":{"tags":["cbgreenroom","Approvals"],"summary":"Request a dual approval","description":"Open a dual-approval request for a Hit.\n\nUse this endpoint when a Hit needs counsel/operator approval before\nadvancing toward BOOKED. Two distinct approver user_ids are required;\nboth decisions start at PENDING. Either-clears semantics: any APPROVED\nflips outcome to CLEARED.\n\nWhen ``enable_cbsms=True`` AND the tenant is LIVE + outbound unlocked,\neach approver with a User.phone on file gets a notification SMS via\ncbsms. Best-effort: SMS failures don't fail the approval request.","operationId":"request_approval_api_v1_approvals_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__RequestApprovalBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Approval"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Approvals"],"summary":"List Approvals","description":"List Approvals in the request tenant.\n\nFilter by ``outcome`` and/or ``stale``. ``stale=True`` returns only\nPENDING approvals whose ``expires_at`` is already past — operators\nsurface these to re-request or cancel. ``stale=False`` excludes\nthem. Omitted (default) is unfiltered.","operationId":"list_approvals_api_v1_approvals_get","parameters":[{"name":"outcome","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbgreenroom__ApprovalOutcome"},{"type":"null"}],"title":"Outcome"}},{"name":"stale","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stale"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__Approval"},"title":"Response List Approvals Api V1 Approvals Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/approvals/by-hit/{hit_id}":{"get":{"tags":["cbgreenroom","Approvals"],"summary":"List Approvals for a Hit","description":"List all Approvals opened against a given Hit.","operationId":"list_approvals_for_hit_api_v1_approvals_by_hit__hit_id__get","parameters":[{"name":"hit_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hit Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__Approval"},"title":"Response List Approvals For Hit Api V1 Approvals By Hit  Hit Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/approvals/{approval_id}":{"get":{"tags":["cbgreenroom","Approvals"],"summary":"Get an Approval","description":"Return a single Approval scoped to the request tenant.","operationId":"get_approval_api_v1_approvals__approval_id__get","parameters":[{"name":"approval_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Approval Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Approval"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/approvals/{approval_id}/decisions":{"post":{"tags":["cbgreenroom","Approvals"],"summary":"Record an approver's decision","description":"Record one approver's APPROVE or DECLINE.\n\nUse this endpoint when an approver acts. Service recomputes the\naggregate outcome and emits :class:`ApprovalDecisionEvent` to the audit\nlog. The mobile no-login flow lands here with ``signed_token_id`` set;\nwhen ``enable_cblinks=True`` the service verifies-and-consumes the\ntoken via cblinks before applying the decision.","operationId":"record_decision_api_v1_approvals__approval_id__decisions_post","parameters":[{"name":"approval_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Approval Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__DecisionBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Approval"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/briefings/by-hit/{hit_id}":{"get":{"tags":["cbgreenroom","Briefings"],"summary":"List BriefingPackets for a Hit","description":"List all BriefingPackets generated for a given Hit.","operationId":"list_for_hit_api_v1_briefings_by_hit__hit_id__get","parameters":[{"name":"hit_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hit Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__BriefingPacket"},"title":"Response List For Hit Api V1 Briefings By Hit  Hit Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/briefings/generate-for-hit/{hit_id}":{"post":{"tags":["cbgreenroom","Briefings"],"summary":"Generate a BriefingPacket for a Hit","description":"Generate a BriefingPacket for a Hit, manually.\n\nUse this endpoint when an operator needs to generate a packet on demand\n(walk-in bookings, last-minute appearances). The cbcron-driven T-2hr\nschedule trigger is a separate path.\n\nWhen a cbcalendar slug + calendar_id resolve (from body override or\ntenant default) AND the ``enable_cbcalendar`` feature flag is on,\nthis endpoint also creates an appearance event + prep block in\ncbcalendar via mesh and stamps the event ids onto the packet's\ndelivery_status.","operationId":"generate_for_hit_api_v1_briefings_generate_for_hit__hit_id__post","parameters":[{"name":"hit_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hit Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__GenerateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__BriefingPacket"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/briefings/{packet_id}":{"get":{"tags":["cbgreenroom","Briefings"],"summary":"Get a BriefingPacket","description":"Return a single BriefingPacket scoped to the request tenant.","operationId":"get_packet_api_v1_briefings__packet_id__get","parameters":[{"name":"packet_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Packet Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__BriefingPacket"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/briefings/{packet_id}/emailed":{"post":{"tags":["cbgreenroom","Briefings"],"summary":"Mark emailed","description":"Mark a briefing packet as emailed, recording the delivery message id.","operationId":"mark_emailed_api_v1_briefings__packet_id__emailed_post","parameters":[{"name":"packet_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Packet Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__MarkEmailedRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__BriefingPacket"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/briefings/{packet_id}/pdf-rendered":{"post":{"tags":["cbgreenroom","Briefings"],"summary":"Mark PDF rendered","description":"Stamp the rendered-PDF URI onto a briefing packet's delivery status.","operationId":"mark_pdf_rendered_api_v1_briefings__packet_id__pdf_rendered_post","parameters":[{"name":"packet_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Packet Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__MarkPdfRenderedRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__BriefingPacket"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/compliance/disclosure-templates":{"get":{"tags":["cbgreenroom","Compliance"],"summary":"List DisclosureTemplates in the request tenant","description":"Return DisclosureTemplates in the request tenant.\n\nFilter knobs:\n\n- ``only_active`` (default ``true``) — exclude templates marked inactive.\n- ``counsel_approved=true`` — only templates already counsel-approved.\n- ``counsel_approved=false`` — only templates without counsel approval\n  (the cbloom counsel UI surfaces this list to drive the approval queue).\n- ``counsel_approved`` omitted — no filter on approval state.","operationId":"list_disclosure_templates_api_v1_compliance_disclosure_templates_get","parameters":[{"name":"only_active","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Only Active"}},{"name":"counsel_approved","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Counsel Approved"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__DisclosureTemplate"},"title":"Response List Disclosure Templates Api V1 Compliance Disclosure Templates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"post":{"tags":["cbgreenroom","Compliance"],"summary":"Create a DisclosureTemplate","description":"Create a new template at version 1. Counsel-approval lands separately.\n\nThe request body's ``tenant_id`` must match the request's\n``X-CB-Tenant`` header — refuses cross-tenant inserts.\n\n``counsel_approved_at`` and ``counsel_approval_reference`` SHOULD\nbe ``None`` on create; counsel signs off via the dedicated\n``/counsel-approve`` endpoint, not by stuffing values in the\ncreate payload. Callers that include them get them stored, but\nthe canonical workflow is create-then-approve.\n\nPermission: ``greenroom.counsel.write`` or ``platform.admin`` —\ncounsel writes the policy text; tenant operators don't author it.","operationId":"create_disclosure_template_api_v1_compliance_disclosure_templates_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__DisclosureTemplate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__DisclosureTemplate"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/compliance/disclosure-templates/{template_id}":{"get":{"tags":["cbgreenroom","Compliance"],"summary":"Get a DisclosureTemplate by id","description":"Return a single template scoped to the request tenant.\n\nCounsel UI's drilldown calls this to render the body and show the\ncurrent ``counsel_approved_at`` / ``counsel_approval_reference``\nstate alongside the counsel-approve button.","operationId":"get_disclosure_template_api_v1_compliance_disclosure_templates__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__DisclosureTemplate"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"put":{"tags":["cbgreenroom","Compliance"],"summary":"Replace a DisclosureTemplate (clears counsel approval on body change)","description":"Replace the template at ``template_id``.\n\n**Counsel approval semantics:** if any of ``name``, ``body``,\n``version_label``, or ``placeholder_keys`` differs from the\npersisted row, the update clears ``counsel_approved_at`` +\n``counsel_approval_reference`` regardless of what the caller\npassed. The \"counsel signed off on *this text*\" invariant\nrequires re-approval after a content change. ``active``-only\nflips preserve counsel state.\n\nBody's ``id`` must match the path; body's ``tenant_id`` must\nmatch the header. Cross-tenant rewrites are refused.","operationId":"update_disclosure_template_api_v1_compliance_disclosure_templates__template_id__put","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__DisclosureTemplate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__DisclosureTemplate"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/compliance/disclosure-templates/{template_id}/counsel-approve":{"post":{"tags":["cbgreenroom","Compliance"],"summary":"Stamp counsel approval onto a DisclosureTemplate","description":"Stamp ``counsel_approved_at`` and ``counsel_approval_reference`` onto a template.\n\nRequired to inject the template in LIVE mode when the tenant's\n``compliance_config.require_counsel_approved_disclosures`` is True\n(the default). Idempotent: re-approving overwrites the reference\nbut does not move the original ``counsel_approved_at`` — the first\napproval timestamp stands.\n\nPermission: ``greenroom.counsel.write`` or ``platform.admin``.\nCounsel is a separate role from compliance admin; the bundle should\ngrant ``counsel.write`` only to actual legal counsel principals.","operationId":"counsel_approve_template_api_v1_compliance_disclosure_templates__template_id__counsel_approve_post","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__CounselApproveBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__DisclosureTemplate"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/cron/briefings/t-minus-2h/{hit_id}":{"post":{"tags":["cbgreenroom","Cron"],"summary":"cbcron T-2hr trigger: generate a briefing packet for a hit","description":"Run the T-2hr briefing trigger for a hit. Idempotent.\n\nIf the hit already has a briefing packet attached, the existing\npacket is returned without producing side effects. Otherwise a new\nBriefingPacket is generated through the same code path as the\noperator-triggered endpoint, including the optional cbcalendar\nintegration when ``cbcalendar_calendar_id`` is provided.","operationId":"briefing_t_minus_two_hours_api_v1_cron_briefings_t_minus_2h__hit_id__post","parameters":[{"name":"hit_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hit Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TMinusTwoHRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__BriefingPacket"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/cron/touchpoints/weekly-digest":{"post":{"tags":["cbgreenroom","Cron"],"summary":"cbcron weekly trigger: compose a touchpoint digest for an operator","description":"Compose a weekly touchpoint digest for ``target_user_id``.\n\nv1 produces an empty-touchpoints digest as a placeholder; signal-\nextraction logic is upstream of this endpoint and lands when the\ncron-fired extraction queries are written. The endpoint exists today\nso cbcron has a stable URL to call.","operationId":"weekly_touchpoint_digest_api_v1_cron_touchpoints_weekly_digest_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__WeeklyDigestRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TouchpointDigest"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/hello":{"get":{"tags":["cbgreenroom","CBGREENROOM"],"summary":"Hello world","description":"Return a greeting.","operationId":"hello_api_v1_hello_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbgreenroom/api/v1/hits":{"get":{"tags":["cbgreenroom","Hits"],"summary":"List Hits","description":"List Hits for the current tenant, optionally filtered by state, show, or source.","operationId":"list_hits_api_v1_hits_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbgreenroom__HitState"},{"type":"null"}],"title":"State"}},{"name":"show_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Show Id"}},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbgreenroom__HitSource"},{"type":"null"}],"title":"Source"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__Hit"},"title":"Response List Hits Api V1 Hits Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/hits/referral":{"post":{"tags":["cbgreenroom","Hits"],"summary":"Record a referral","description":"Record a referral. Hit starts at IDENTIFIED with source=REFERRAL.\n\nEither ``referrer_user_id`` or ``referrer_external_name`` must be set.","operationId":"create_referral_api_v1_hits_referral_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__ReferralRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Hit"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/hits/walk-in":{"post":{"tags":["cbgreenroom","Hits"],"summary":"Record a walk-in booking","description":"Record a walk-in booking. The Hit is created at BOOKED with source=WALKED_IN_EXTERNAL.","operationId":"create_walk_in_api_v1_hits_walk_in_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__WalkInRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Hit"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/hits/{hit_id}":{"get":{"tags":["cbgreenroom","Hits"],"summary":"Get a Hit","description":"Fetch a single Hit by id within the current tenant.","operationId":"get_hit_api_v1_hits__hit_id__get","parameters":[{"name":"hit_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hit Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Hit"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/hits/{hit_id}/transition":{"post":{"tags":["cbgreenroom","Hits"],"summary":"Advance a Hit to a new state","description":"Advance a Hit to a new state. Validates state-machine edges.","operationId":"transition_hit_api_v1_hits__hit_id__transition_post","parameters":[{"name":"hit_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Hit Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TransitionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Hit"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging-snapshots":{"post":{"tags":["cbgreenroom","Messaging Snapshots"],"summary":"Create a MessagingSnapshot","description":"Pin every current messaging-record version into an immutable snapshot.\n\nBriefing packets read through a snapshot so they cite a reproducible\nstate of the messaging hub regardless of later edits.","operationId":"create_snapshot_api_v1_messaging_snapshots_post","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__CreateSnapshotRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__MessagingSnapshot"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Messaging Snapshots"],"summary":"List MessagingSnapshots","description":"List MessagingSnapshots for the current tenant, most recent first, up to ``limit``.","operationId":"list_snapshots_api_v1_messaging_snapshots_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__MessagingSnapshot"},"title":"Response List Snapshots Api V1 Messaging Snapshots Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging-snapshots/{snapshot_id}":{"get":{"tags":["cbgreenroom","Messaging Snapshots"],"summary":"Get a MessagingSnapshot","description":"Fetch a single MessagingSnapshot by id within the current tenant.","operationId":"get_snapshot_api_v1_messaging_snapshots__snapshot_id__get","parameters":[{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Snapshot Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__MessagingSnapshot"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging-snapshots/{snapshot_id}/entries":{"get":{"tags":["cbgreenroom","Messaging Snapshots"],"summary":"List entries pinned by a snapshot","description":"List the messaging-record versions pinned by a snapshot, optionally filtered by kind.","operationId":"list_entries_api_v1_messaging_snapshots__snapshot_id__entries_get","parameters":[{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Snapshot Id"}},{"name":"record_kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Record Kind"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__MessagingSnapshotEntry"},"title":"Response List Entries Api V1 Messaging Snapshots  Snapshot Id  Entries Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging/no-go-topics":{"post":{"tags":["cbgreenroom","Messaging"],"summary":"Create a No-Go Topic","description":"Create a No-Go Topic at version 1.","operationId":"create_no_go_topic_api_v1_messaging_no_go_topics_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__NoGoTopic"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__NoGoTopic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Messaging"],"summary":"List current No-Go Topics","description":"List the current version of every No-Go Topic for the tenant.","operationId":"list_no_go_topics_api_v1_messaging_no_go_topics_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__NoGoTopic"},"title":"Response List No Go Topics Api V1 Messaging No Go Topics Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging/no-go-topics/{root_id}":{"put":{"tags":["cbgreenroom","Messaging"],"summary":"Update a No-Go Topic (creates a new version)","description":"Append a new version of a No-Go Topic; the prior version is superseded.","operationId":"update_no_go_topic_api_v1_messaging_no_go_topics__root_id__put","parameters":[{"name":"root_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Root Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__NoGoTopic"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__NoGoTopic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging/opposition-contrasts":{"post":{"tags":["cbgreenroom","Messaging"],"summary":"Create an Opposition Contrast","description":"Create an Opposition Contrast at version 1.","operationId":"create_opposition_contrast_api_v1_messaging_opposition_contrasts_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__OppositionContrast"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__OppositionContrast"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Messaging"],"summary":"List current Opposition Contrasts","description":"List the current version of every Opposition Contrast for the tenant.","operationId":"list_opposition_contrasts_api_v1_messaging_opposition_contrasts_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__OppositionContrast"},"title":"Response List Opposition Contrasts Api V1 Messaging Opposition Contrasts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging/opposition-contrasts/{root_id}":{"put":{"tags":["cbgreenroom","Messaging"],"summary":"Update an Opposition Contrast (creates a new version)","description":"Append a new version of an Opposition Contrast; the prior version is superseded.","operationId":"update_opposition_contrast_api_v1_messaging_opposition_contrasts__root_id__put","parameters":[{"name":"root_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Root Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__OppositionContrast"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__OppositionContrast"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging/pillars":{"post":{"tags":["cbgreenroom","Messaging"],"summary":"Create a Pillar (v1)","description":"Create a Pillar at version 1. Updates create new versions via PUT.","operationId":"create_pillar_api_v1_messaging_pillars_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Pillar"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Pillar"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Messaging"],"summary":"List current Pillars","description":"List the current version of every Pillar for the tenant.","operationId":"list_pillars_api_v1_messaging_pillars_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__Pillar"},"title":"Response List Pillars Api V1 Messaging Pillars Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging/pillars/{root_id}":{"put":{"tags":["cbgreenroom","Messaging"],"summary":"Update a Pillar (creates a new version)","description":"Append a new version of a Pillar. Prior version is marked superseded.","operationId":"update_pillar_api_v1_messaging_pillars__root_id__put","parameters":[{"name":"root_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Root Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Pillar"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Pillar"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Messaging"],"summary":"Get the current Pillar version","description":"Return the current (latest) version of a Pillar by its root id.","operationId":"get_current_pillar_api_v1_messaging_pillars__root_id__get","parameters":[{"name":"root_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Root Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Pillar"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging/rapid-response-lines":{"post":{"tags":["cbgreenroom","Messaging"],"summary":"Create a Rapid Response Line","description":"Create a Rapid Response Line at version 1.","operationId":"create_rapid_response_line_api_v1_messaging_rapid_response_lines_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__RapidResponseLine"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__RapidResponseLine"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Messaging"],"summary":"List current Rapid Response Lines","description":"List the current version of every Rapid Response Line for the tenant.","operationId":"list_rapid_response_lines_api_v1_messaging_rapid_response_lines_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__RapidResponseLine"},"title":"Response List Rapid Response Lines Api V1 Messaging Rapid Response Lines Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging/rapid-response-lines/{root_id}":{"put":{"tags":["cbgreenroom","Messaging"],"summary":"Update a Rapid Response Line (creates a new version)","description":"Append a new version of a Rapid Response Line; the prior version is superseded.","operationId":"update_rapid_response_line_api_v1_messaging_rapid_response_lines__root_id__put","parameters":[{"name":"root_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Root Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__RapidResponseLine"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__RapidResponseLine"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging/talking-points":{"post":{"tags":["cbgreenroom","Messaging"],"summary":"Create a Talking Point","description":"Create a Talking Point at version 1.","operationId":"create_talking_point_api_v1_messaging_talking_points_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TalkingPoint"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TalkingPoint"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Messaging"],"summary":"List current Talking Points","description":"List the current version of every Talking Point for the tenant.","operationId":"list_talking_points_api_v1_messaging_talking_points_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__TalkingPoint"},"title":"Response List Talking Points Api V1 Messaging Talking Points Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/messaging/talking-points/{root_id}":{"put":{"tags":["cbgreenroom","Messaging"],"summary":"Update a Talking Point (creates a new version)","description":"Append a new version of a Talking Point; the prior version is superseded.","operationId":"update_talking_point_api_v1_messaging_talking_points__root_id__put","parameters":[{"name":"root_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Root Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TalkingPoint"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TalkingPoint"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/pitches":{"post":{"tags":["cbgreenroom","Pitches"],"summary":"Create a Pitch and its initial Hit","description":"Initiate a pitch sequence. Produces a Pitch and a linked Hit at IDENTIFIED.","operationId":"create_pitch_api_v1_pitches_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Pitch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__CreatePitchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Pitches"],"summary":"List Pitches","description":"List Pitches in the request tenant, optionally filtered by show or open state.","operationId":"list_pitches_api_v1_pitches_get","parameters":[{"name":"show_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Show Id"}},{"name":"only_open","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Only Open"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__Pitch"},"title":"Response List Pitches Api V1 Pitches Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/pitches/outbound/{outbound_id}/response":{"post":{"tags":["cbgreenroom","Pitches"],"summary":"Mark an outbound as having received a response","description":"Mark an outbound record as having received a response from the contact.","operationId":"record_response_api_v1_pitches_outbound__outbound_id__response_post","parameters":[{"name":"outbound_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Outbound Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__PitchOutboundRecord"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/pitches/{pitch_id}":{"get":{"tags":["cbgreenroom","Pitches"],"summary":"Get a Pitch","description":"Return a single Pitch scoped to the request tenant.","operationId":"get_pitch_api_v1_pitches__pitch_id__get","parameters":[{"name":"pitch_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Pitch Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Pitch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/pitches/{pitch_id}/close":{"post":{"tags":["cbgreenroom","Pitches"],"summary":"Close a Pitch","description":"Close a Pitch, recording the supplied closure reason.","operationId":"close_pitch_api_v1_pitches__pitch_id__close_post","parameters":[{"name":"pitch_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Pitch Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__CloseRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Pitch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/pitches/{pitch_id}/outbound":{"get":{"tags":["cbgreenroom","Pitches"],"summary":"List outbound records for a Pitch","description":"List all outbound send records attached to a Pitch.","operationId":"list_outbound_for_pitch_api_v1_pitches__pitch_id__outbound_get","parameters":[{"name":"pitch_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Pitch Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__PitchOutboundRecord"},"title":"Response List Outbound For Pitch Api V1 Pitches  Pitch Id  Outbound Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/pitches/{pitch_id}/send":{"post":{"tags":["cbgreenroom","Pitches"],"summary":"Send a pitch outbound","description":"Record an outbound send. Reads the tenant to determine demo/locked suppression.\n\nSMS delivery routes through cbsms when ``enable_cbsms=True`` AND the\ntenant is LIVE + outbound unlocked. Suppressed paths (DEMO or\noutbound_locked) skip the mesh call; the audit row records the\nintent. cbsms 5xx propagates as ``ServiceUnavailable``.","operationId":"send_outbound_api_v1_pitches__pitch_id__send_post","parameters":[{"name":"pitch_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Pitch Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__SendOutboundRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__PitchOutboundRecord"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows":{"post":{"tags":["cbgreenroom","Show Graph"],"summary":"Ingest a Show","description":"Ingest a Show. Tier 3 sources are blocked at this boundary.","operationId":"ingest_show_api_v1_shows_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Show-Input"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Show-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Show Graph"],"summary":"List Shows","description":"List Shows for the current tenant, optionally filtered by type or conflict status.","operationId":"list_shows_api_v1_shows_get","parameters":[{"name":"show_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbgreenroom__ShowType"},{"type":"null"}],"title":"Show Type"}},{"name":"only_conflicted","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Only Conflicted"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__Show-Output"},"title":"Response List Shows Api V1 Shows Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/bookers":{"post":{"tags":["cbgreenroom","Show Graph"],"summary":"Ingest a Booker","description":"Ingest a Booker record.","operationId":"ingest_booker_api_v1_shows_bookers_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Booker-Input"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Booker-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/bookers/{booker_id}":{"get":{"tags":["cbgreenroom","Show Graph"],"summary":"Get a Booker","description":"Fetch a single Booker by id within the current tenant.","operationId":"get_booker_api_v1_shows_bookers__booker_id__get","parameters":[{"name":"booker_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Booker Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Booker-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/episodes":{"post":{"tags":["cbgreenroom","Show Graph"],"summary":"Ingest an Episode","description":"Ingest an Episode record.","operationId":"ingest_episode_api_v1_shows_episodes_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Episode-Input"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Episode-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/hosts":{"post":{"tags":["cbgreenroom","Show Graph"],"summary":"Ingest a Host","description":"Ingest a Host record.","operationId":"ingest_host_api_v1_shows_hosts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Host-Input"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Host-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/hosts/{host_id}":{"get":{"tags":["cbgreenroom","Show Graph"],"summary":"Get a Host","description":"Fetch a single Host by id within the current tenant.","operationId":"get_host_api_v1_shows_hosts__host_id__get","parameters":[{"name":"host_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Host Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Host-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/producers":{"post":{"tags":["cbgreenroom","Show Graph"],"summary":"Ingest a Producer","description":"Ingest a Producer record.","operationId":"ingest_producer_api_v1_shows_producers_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Producer-Input"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Producer-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/producers/{producer_id}":{"get":{"tags":["cbgreenroom","Show Graph"],"summary":"Get a Producer","description":"Fetch a single Producer by id within the current tenant.","operationId":"get_producer_api_v1_shows_producers__producer_id__get","parameters":[{"name":"producer_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Producer Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Producer-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/{show_id}":{"get":{"tags":["cbgreenroom","Show Graph"],"summary":"Get a Show","description":"Fetch a single Show by id within the current tenant.","operationId":"get_show_api_v1_shows__show_id__get","parameters":[{"name":"show_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Show Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Show-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"put":{"tags":["cbgreenroom","Show Graph"],"summary":"Update a Show","description":"Replace a Show; the body id must match the path id.","operationId":"update_show_api_v1_shows__show_id__put","parameters":[{"name":"show_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Show Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Show-Input"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Show-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/{show_id}/conflict-flag":{"post":{"tags":["cbgreenroom","Show Graph"],"summary":"Flag a Show as a conflict of interest","description":"Flag a Show as a conflict; creates a ConflictFlag and flips Show.conflict_flag.","operationId":"set_conflict_flag_api_v1_shows__show_id__conflict_flag_post","parameters":[{"name":"show_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Show Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__SetConflictFlagRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__SetConflictFlagResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"delete":{"tags":["cbgreenroom","Show Graph"],"summary":"Clear all active conflict flags on a Show","description":"Clear all active conflict flags on a Show and reset its conflict_flag.","operationId":"clear_conflict_flag_api_v1_shows__show_id__conflict_flag_delete","parameters":[{"name":"show_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Show Id"}},{"name":"cleared_by","in":"query","required":true,"schema":{"type":"string","format":"uuid","title":"Cleared By"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Show-Output"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/{show_id}/episodes":{"get":{"tags":["cbgreenroom","Show Graph"],"summary":"List Episodes for a Show","description":"List Episodes for a Show, most recent first, up to ``limit``.","operationId":"list_episodes_for_show_api_v1_shows__show_id__episodes_get","parameters":[{"name":"show_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Show Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__Episode-Output"},"title":"Response List Episodes For Show Api V1 Shows  Show Id  Episodes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/shows/{show_id}/hosts":{"get":{"tags":["cbgreenroom","Show Graph"],"summary":"List Hosts for a Show","description":"List all Hosts associated with a Show.","operationId":"list_hosts_for_show_api_v1_shows__show_id__hosts_get","parameters":[{"name":"show_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Show Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__Host-Output"},"title":"Response List Hosts For Show Api V1 Shows  Show Id  Hosts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/tenants":{"post":{"tags":["cbgreenroom","Tenants"],"summary":"Create a Tenant","description":"Create a new Tenant. Requires ``platform.admin`` permission.\n\nNew tenants default to DEMO mode and outbound_locked=True. Promotion\nto LIVE requires a separate evidence-bearing call.","operationId":"create_tenant_api_v1_tenants_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Tenant"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Tenants"],"summary":"List Tenants","description":"List every Tenant. Platform-level read.","operationId":"list_tenants_api_v1_tenants_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__Tenant"},"title":"Response List Tenants Api V1 Tenants Get"}}}}}}},"/cbgreenroom/api/v1/tenants/{tenant_id}":{"get":{"tags":["cbgreenroom","Tenants"],"summary":"Get a Tenant by id","description":"Return a Tenant by id.","operationId":"get_tenant_api_v1_tenants__tenant_id__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/tenants/{tenant_id}/lock-outbound":{"post":{"tags":["cbgreenroom","Tenants"],"summary":"Lock outbound for a Tenant","description":"Set ``outbound_locked=True``. Even LIVE tenants produce no real outbound while locked.","operationId":"lock_outbound_api_v1_tenants__tenant_id__lock_outbound_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/tenants/{tenant_id}/promote-to-live":{"post":{"tags":["cbgreenroom","Tenants"],"summary":"Promote a Tenant from DEMO to LIVE","description":"Flip a tenant to LIVE mode. Requires tenant-admin authority.\n\nAccepts ``platform.admin`` or ``greenroom.compliance.write`` — the\nloom bundle's compliance-area admin grants the latter via cbauth.\n\nUse this endpoint when counsel has cleared the tenant for live outbound.\nRequires ``evidence_reference`` (counsel signoff id, license agreement\nreference, operator confirmation token); the value lands on the\n``ModeChangedEvent`` audit row. Idempotent.","operationId":"promote_to_live_api_v1_tenants__tenant_id__promote_to_live_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Tenant Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__PromoteToLiveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/tenants/{tenant_id}/revert-to-demo":{"post":{"tags":["cbgreenroom","Tenants"],"summary":"Revert a Tenant to DEMO mode","description":"Revert a Tenant to DEMO mode. Requires tenant-admin authority. Idempotent. Emits ModeChangedEvent.","operationId":"revert_to_demo_api_v1_tenants__tenant_id__revert_to_demo_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Tenant Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/tenants/{tenant_id}/unlock-outbound":{"post":{"tags":["cbgreenroom","Tenants"],"summary":"Unlock outbound for a Tenant","description":"Set ``outbound_locked=False``. Combined with LIVE mode, enables real outbound.","operationId":"unlock_outbound_api_v1_tenants__tenant_id__unlock_outbound_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Tenant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/touchpoints/digests":{"post":{"tags":["cbgreenroom","Touchpoints"],"summary":"Compose a TouchpointDigest","description":"Bundle Touchpoints into a digest for a target operator.","operationId":"compose_digest_api_v1_touchpoints_digests_post","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__ComposeDigestRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TouchpointDigest"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Touchpoints"],"summary":"List TouchpointDigests for a user","description":"List TouchpointDigests composed for an operator, newest-first up to ``limit``.","operationId":"list_digests_for_user_api_v1_touchpoints_digests_get","parameters":[{"name":"target_user_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","title":"Target User Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__TouchpointDigest"},"title":"Response List Digests For User Api V1 Touchpoints Digests Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/touchpoints/digests/{digest_id}":{"get":{"tags":["cbgreenroom","Touchpoints"],"summary":"Get a TouchpointDigest","description":"Return a single TouchpointDigest scoped to the request tenant.","operationId":"get_digest_api_v1_touchpoints_digests__digest_id__get","parameters":[{"name":"digest_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Digest Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TouchpointDigest"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/touchpoints/digests/{digest_id}/touchpoints":{"get":{"tags":["cbgreenroom","Touchpoints"],"summary":"List Touchpoints for a digest","description":"List the individual Touchpoints bundled into a digest.","operationId":"list_touchpoints_for_digest_api_v1_touchpoints_digests__digest_id__touchpoints_get","parameters":[{"name":"digest_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Digest Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__Touchpoint"},"title":"Response List Touchpoints For Digest Api V1 Touchpoints Digests  Digest Id  Touchpoints Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/touchpoints/signals":{"post":{"tags":["cbgreenroom","Touchpoints"],"summary":"Record a TouchpointSignal","description":"Record an upstream TouchpointSignal that may drive a recommendation.","operationId":"record_signal_api_v1_touchpoints_signals_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TouchpointSignal"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TouchpointSignal"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Touchpoints"],"summary":"List TouchpointSignals","description":"List TouchpointSignals in the request tenant, newest-first up to ``limit``.","operationId":"list_signals_api_v1_touchpoints_signals_get","parameters":[{"name":"detected_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Detected After"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__TouchpointSignal"},"title":"Response List Signals Api V1 Touchpoints Signals Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/touchpoints/{touchpoint_id}":{"get":{"tags":["cbgreenroom","Touchpoints"],"summary":"Get a Touchpoint","description":"Return a single Touchpoint scoped to the request tenant.","operationId":"get_touchpoint_api_v1_touchpoints__touchpoint_id__get","parameters":[{"name":"touchpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Touchpoint Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__Touchpoint"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/touchpoints/{touchpoint_id}/outcome":{"post":{"tags":["cbgreenroom","Touchpoints"],"summary":"Record a Touchpoint outcome","description":"Close out a Touchpoint with a captured outcome.\n\nUse this endpoint after the operator acted (called the producer, sent\nthe thank-you, escalated, skipped). The outcome feeds next cycle's\nsignal extraction.","operationId":"record_outcome_api_v1_touchpoints__touchpoint_id__outcome_post","parameters":[{"name":"touchpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Touchpoint Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__RecordOutcomeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__TouchpointOutcome"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/users":{"post":{"tags":["cbgreenroom","Users"],"summary":"Create a User","description":"Create a new User in a tenant.\n\nAccepts admin permissions (``platform.admin`` or\n``greenroom.compliance.write``) **or** an active in-tenant\n``CampaignOperator`` — operators can onboard additional principals\nto their own tenant without requiring the bundle compliance-area\nadmin perm. Approvers, assistants, and Greenroom Entity Staff are\nrejected.\n\nUse this endpoint when onboarding a new principal — campaign operator,\napprover, assistant, or Greenroom Entity Staff. Validates the role-mirror\ninvariant on ``is_greenroom_entity_staff``.\n\nNote: cbgreenroom auto-provisions a User row on first authenticated\ntouch (see :func:`src.deps.get_actor_user`); this endpoint is for\nexplicit onboarding of additional principals or admin-controlled role\nassignment.","operationId":"create_user_api_v1_users_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__User"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__User"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"get":{"tags":["cbgreenroom","Users"],"summary":"List Users","description":"List Users in the request tenant. Filter by active flag and role type.","operationId":"list_users_api_v1_users_get","parameters":[{"name":"only_active","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Only Active"}},{"name":"role_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbgreenroom__UserRoleType"},{"type":"null"}],"title":"Role Type"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbgreenroom__User"},"title":"Response List Users Api V1 Users Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/api/v1/users/{user_id}":{"get":{"tags":["cbgreenroom","Users"],"summary":"Get a User by id","description":"Return a User by id within the request tenant scope.","operationId":"get_user_api_v1_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__User"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"put":{"tags":["cbgreenroom","Users"],"summary":"Update a User","description":"Replace a User. Validates role-mirror invariant; rejects cross-tenant rewrite.","operationId":"update_user_api_v1_users__user_id__put","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__User"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__User"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}},"delete":{"tags":["cbgreenroom","Users"],"summary":"Deactivate a User","description":"Set the User to inactive. Idempotent; users are never hard-deleted.\n\nSame gate as ``POST /users``: admin perms or an active in-tenant\n``CampaignOperator``.","operationId":"deactivate_user_api_v1_users__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__User"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/health":{"get":{"tags":["cbgreenroom","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HealthResponse"}}}}}}},"/cbgreenroom/health/detailed":{"get":{"tags":["cbgreenroom","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HealthResponse"}}}}}}},"/cbgreenroom/m/decision":{"get":{"tags":["cbgreenroom","Mobile"],"summary":"Mobile tap-handler — record an approval decision from a signed link","description":"Tap-handler for the cblinks shortlink in approval-notify SMS.\n\nAll four query params are required and bound to a fresh, unconsumed\ncblinks token minted at notify-time. The handler does the verify\nitself (to recover the canonical actor identity from the token's\n``actor_user_id`` field, and to detect URL-param tampering) and\nthen calls :func:`approval_service.record_decision` with\n``pre_verified=True``.","operationId":"record_decision_via_signed_link_m_decision_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":256,"title":"Token"}},{"name":"approval_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","title":"Approval Id"}},{"name":"disposition","in":"query","required":true,"schema":{"$ref":"#/components/schemas/cbgreenroom__ApprovalDisposition"}},{"name":"approver_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","title":"Approver Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__HTTPValidationError"}}}}}}},"/cbgreenroom/prime":{"get":{"tags":["cbgreenroom","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__PrimeResponse"}}}}}}},"/cbgreenroom/status":{"get":{"tags":["cbgreenroom","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgreenroom__StatusResponse"}}}}}}},"/cbgroundgame/health":{"get":{"tags":["cbgroundgame","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__HealthResponse"}}}}}}},"/cbgroundgame/health/detailed":{"get":{"tags":["cbgroundgame","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__HealthResponse"}}}}}}},"/cbgroundgame/prime":{"get":{"tags":["cbgroundgame","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__PrimeResponse"}}}}}}},"/cbgroundgame/stacks":{"get":{"tags":["cbgroundgame","CBGROUNDGAME"],"summary":"List deployed stacks","description":"Return the names of every deployed stack — one entry per sub-directory of the stacks root that holds a compiled `stack.json` manifest. Read-only; the starting point for stack introspection. Returns `{\"stacks\": [name, ...]}` (empty list if none are deployed).","operationId":"list_stacks_stacks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Stacks Stacks Get"}}}}}}},"/cbgroundgame/stacks/{name}":{"get":{"tags":["cbgroundgame","CBGROUNDGAME"],"summary":"A stack's resolved manifest summary","description":"Load the stack's compiled manifest and return a flattened summary of what it declares: the template it derives from, plus the names/ids of its services, channels, devices, jobs, and the UI app. Read-only — describes the *desired* state, not live fleet state (use `/diff` or `/status` for that). Returns **404** if no stack with that name is deployed.","operationId":"get_stack_stacks__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Stack Stacks  Name  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__HTTPValidationError"}}}}}}},"/cbgroundgame/stacks/{name}/apply":{"post":{"tags":["cbgroundgame","CBGROUNDGAME"],"summary":"Reconcile the stack to the manifest (mutates the fleet)","description":"Execute the `/diff` plan against the live fleet: create/update every drifted object in dependency order (data → services → channels → devices → conflict cutover → ui → crons), snapshotting first so any failure rolls back what this run applied. Idempotent — `noop` ops are skipped, so re-applying an unchanged manifest does nothing.\n\n**Gates (two query flags):**\n- `confirm` — ownership-conflict cutovers (retiring a competing channel driver) are destructive and refused unless `confirm=true` → **409**.\n- `force` — even with `confirm`, retiring the driver of a channel a device is actively streaming would dark the screen; refused unless `force=true` → **423**.\n\nReturns the per-op outcome, the ownership ledger, and `status` (`applied`/`noop`/`needs-confirm`/`blocked`/`rolled-back`). **404** if the stack is not deployed; **503** if the live fleet is unavailable.","operationId":"apply_stack_stacks__name__apply_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"confirm","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Confirm"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Apply Stack Stacks  Name  Apply Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__HTTPValidationError"}}}}}}},"/cbgroundgame/stacks/{name}/destroy":{"post":{"tags":["cbgroundgame","CBGROUNDGAME"],"summary":"Tear the stack down (disable + revoke + pause)","description":"Group-lifecycle verb and the hard teardown: stop streams, pause the schedule, then durably disable and revoke-trust every service. With `purge=true` it also removes the VS homes and deletes the created cbcast/cbcron/data objects (catalog deletion is left operator-manual).\n\n**Destructive — requires `confirm=true`** or it returns **409** without touching anything. Best-effort across the group; response lists per-op results. **404** if the stack is not deployed; **503** if the live fleet is unavailable.","operationId":"destroy_stack_stacks__name__destroy_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"confirm","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Confirm"}},{"name":"purge","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Purge"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Destroy Stack Stacks  Name  Destroy Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__HTTPValidationError"}}}}}}},"/cbgroundgame/stacks/{name}/diff":{"get":{"tags":["cbgroundgame","CBGROUNDGAME"],"summary":"Plan the stack vs live fleet (read-only)","description":"Observe the live fleet and compute the reconcile plan to converge it to the stack's manifest **without changing anything**. For every managed object the response classifies the op as `create`, `update`, `noop`, or `conflict` (a competing driver on a channel a VS should own); `summary` is the count by kind. This is exactly the plan `apply` would execute, so use it as the dry-run before mutating.\n\nReturns **404** if the stack is not deployed, or **503** if live fleet observation is unavailable (e.g. the mesh observer is not yet wired).","operationId":"diff_stack_stacks__name__diff_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Diff Stack Stacks  Name  Diff Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__HTTPValidationError"}}}}}}},"/cbgroundgame/stacks/{name}/disable":{"post":{"tags":["cbgroundgame","CBGROUNDGAME"],"summary":"Durably disable every service (survives restart)","description":"Group-lifecycle verb: durably disable every service in the stack so it stays down across host restarts (unlike `down`). Best-effort across the group; response lists per-op results. **404** if the stack is not deployed; **503** if the live fleet is unavailable.","operationId":"disable_stack_stacks__name__disable_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Disable Stack Stacks  Name  Disable Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__HTTPValidationError"}}}}}}},"/cbgroundgame/stacks/{name}/down":{"post":{"tags":["cbgroundgame","CBGROUNDGAME"],"summary":"Stop every service (ephemeral)","description":"Group-lifecycle verb: stop every service in the stack. This is **ephemeral** — services come back on host restart (use `disable` for a durable stop). Best-effort across the group; response lists per-op results. **404** if the stack is not deployed; **503** if the live fleet is unavailable.","operationId":"down_stack_stacks__name__down_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Down Stack Stacks  Name  Down Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__HTTPValidationError"}}}}}}},"/cbgroundgame/stacks/{name}/status":{"get":{"tags":["cbgroundgame","CBGROUNDGAME"],"summary":"Observed health per layer vs the manifest","description":"Report the live health of the stack as a group: for each declared service, channel, device, and job, whether it is present and its observed state (VS lifecycle/fails/trust, channel presence, device streaming flag, job paused flag). The top-level `healthy` flag is true only when every service is `RUNNING` and every declared channel is present. Read-only.\n\nReturns **404** if the stack is not deployed, or **503** if live fleet observation is unavailable.","operationId":"status_stack_stacks__name__status_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Status Stack Stacks  Name  Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__HTTPValidationError"}}}}}}},"/cbgroundgame/stacks/{name}/up":{"post":{"tags":["cbgroundgame","CBGROUNDGAME"],"summary":"Start (or enable) every service","description":"Group-lifecycle verb: start every virtual service the stack declares. The executor resolves start-vs-enable per service (a durably-disabled VS is re-enabled; a merely-stopped one is started). Best-effort across the group — one service failing to start does not abort the rest; the response lists per-op `ok`/`failed` with notes. **404** if the stack is not deployed; **503** if the live fleet is unavailable.","operationId":"up_stack_stacks__name__up_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Up Stack Stacks  Name  Up Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__HTTPValidationError"}}}}}}},"/cbgroundgame/status":{"get":{"tags":["cbgroundgame","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbgroundgame__StatusResponse"}}}}}}},"/cbharvest/api/v1/packages":{"get":{"tags":["cbharvest","CBHARVEST"],"summary":"Every StatePackage this service serves","operationId":"list_packages_api_v1_packages_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Packages Api V1 Packages Get"}}}}}}},"/cbharvest/api/v1/packages/{slug}":{"get":{"tags":["cbharvest","CBHARVEST"],"summary":"One StatePackage, every source declared","operationId":"get_package_api_v1_packages__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Package Api V1 Packages  Slug  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbharvest__HTTPValidationError"}}}}}}},"/cbharvest/api/v1/packages/{slug}/preflight":{"get":{"tags":["cbharvest","CBHARVEST"],"summary":"Probe every source; honest per-source verdicts","description":"Reaches the network, so it is slow by nature and deliberately not on a\nhealth path — cboverseer polling this would probe every state SOS site\nevery thirty seconds.\n\nAlways HTTP 200 when the package exists. The verdict lives in the body\n(`verified`, `exit_code`, per-source states): a dark SOURCE is not a failed\nREQUEST, and returning 503 would make an honest report look like cbharvest\nitself being down.","operationId":"preflight_api_v1_packages__slug__preflight_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Preflight Api V1 Packages  Slug  Preflight Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbharvest__HTTPValidationError"}}}}}}},"/cbharvest/health":{"get":{"tags":["cbharvest","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbharvest__HealthResponse"}}}}}}},"/cbharvest/health/detailed":{"get":{"tags":["cbharvest","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbharvest__HealthResponse"}}}}}}},"/cbharvest/prime":{"get":{"tags":["cbharvest","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbharvest__PrimeResponse"}}}}}}},"/cbharvest/status":{"get":{"tags":["cbharvest","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbharvest__StatusResponse"}}}}}}},"/cbhive/api/v1/broker/leases":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Credential lease ledger (read-only)","description":"The audit trail: who leased what scope, why, when, and when it expired or\nwas revoked. ``raw_key`` is never carried here — it lives only on the\none-time grant return (``POST .../approve``).","operationId":"broker_leases_api_v1_broker_leases_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","pattern":"^(open|pending|all)$","description":"open (active/planned) | pending (awaiting the gate) | all (audit history)","default":"open","title":"Status"},"description":"open (active/planned) | pending (awaiting the gate) | all (audit history)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Cap for status=all","default":100,"title":"Limit"},"description":"Cap for status=all"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbhive__CredentialLease"},"title":"Response Broker Leases Api V1 Broker Leases Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}},"post":{"tags":["cbhive","CBHIVE"],"summary":"Request a credential lease (enters the operator gate)","description":"Record a lease request — mints NOTHING (even when armed live). The lease\nsits ``requested`` until an operator approves or denies it; the operator is\npinged via cbnotify (best-effort). Returns the pending lease (no key).","operationId":"broker_request_lease_api_v1_broker_leases_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__LeaseRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__CredentialLease"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/broker/leases/{lease_id}/approve":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Operator gate — approve + grant a requested lease","description":"The gate. Grant a requested lease: plan-mode records it (mints nothing);\nlive-mode mints the real cbauth key. ``raw_key`` is returned ONCE here and\nnever again. 422 if the lease isn't awaiting the gate.","operationId":"broker_approve_lease_api_v1_broker_leases__lease_id__approve_post","parameters":[{"name":"lease_id","in":"path","required":true,"schema":{"type":"string","title":"Lease Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__GateDecision"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__CredentialLease"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/broker/leases/{lease_id}/deny":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Operator gate — deny a requested lease","description":"Decline a requested lease at the gate — nothing was minted. 422 if the\nlease isn't awaiting the gate.","operationId":"broker_deny_lease_api_v1_broker_leases__lease_id__deny_post","parameters":[{"name":"lease_id","in":"path","required":true,"schema":{"type":"string","title":"Lease Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__GateDecision"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__CredentialLease"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/broker/status":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Credential broker mode","description":"Whether the broker is armed (``live``) or a dry-run (``plan``), plus the\ncount of currently-open leases. cbhive ships read-only — ``plan`` means no\ncredential is ever minted; ``live`` is the deliberately-armed first act.","operationId":"broker_status_api_v1_broker_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"object","title":"Response Broker Status Api V1 Broker Status Get"}}}}}}},"/cbhive/api/v1/broker/sweep":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Reap expired leases (cbcron tick)","description":"Auto-revoke any lease past its TTL so a crashed flow never leaves a\ncredential dangling, and expire any forgotten request that sat past its gate\nwindow. Janitorial, not a privilege grant: in ``plan`` mode it only flips\nlocal rows to ``expired`` (no cbauth call); in ``live`` mode it also revokes\nthe real key. Registered with cbcron, not a systemd timer. Returns\n``{swept: N}``.","operationId":"broker_sweep_api_v1_broker_sweep_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"integer"},"type":"object","title":"Response Broker Sweep Api V1 Broker Sweep Post"}}}}}}},"/cbhive/api/v1/build":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Which commit this process is running","description":"The commit this process STARTED on versus the commit on disk NOW. They diverge in exactly one situation: the code was updated and the service was not restarted — which is invisible from `/health`, because a healthy service serving stale code looks exactly like a healthy service (cbhive#23). `stale: true` means restart cbhive.service. `checkable: false` means there is no readable .git here, so the question cannot be answered rather than answered wrongly.","operationId":"build_api_v1_build_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Build Api V1 Build Get"}}}}}}},"/cbhive/api/v1/digest":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Collapsed, ranked digest (read-only)","description":"The horizon layer: collapse Signals by fingerprint, rank, and split into\n'needs attention' vs 'known noise'. `fmt=md` returns a human-readable view.","operationId":"digest_api_v1_digest_get","parameters":[{"name":"window_hours","in":"query","required":false,"schema":{"type":"integer","maximum":336,"minimum":1,"description":"Look-back window","default":48,"title":"Window Hours"},"description":"Look-back window"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated source filter","title":"Source"},"description":"Comma-separated source filter"},{"name":"fmt","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|md)$","description":"json | md","default":"json","title":"Fmt"},"description":"json | md"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__Digest"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/flows":{"get":{"tags":["cbhive","CBHIVE"],"summary":"List flow runs","description":"Flow runs, newest first. Filter by status (e.g. ``awaiting_gate``,\n``awaiting_agent``, ``done``, ``held``, ``failed``) to find what needs you.","operationId":"flow_list_api_v1_flows_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by run status.","title":"Status"},"description":"Filter by run status."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbhive__FlowRun"},"title":"Response Flow List Api V1 Flows Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}},"post":{"tags":["cbhive","CBHIVE"],"summary":"Start a flow run","description":"Create a run from an inline spec or a library flow name and walk it to its\nfirst suspend (gate/agent) or to done. Dry mode (default) previews the entire\nroute with no side effects; live mode executes and suspends for real. For a\nlibrary flow, pass the ctx keys it needs (see `GET /flows/library`).","operationId":"flow_start_api_v1_flows_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__FlowStartRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__FlowRun"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/flows/compile":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Compile a flow spec to a Workflow script","description":"Compile a FlowSpec to a self-contained Workflow orchestration script (JS) —\nthe autonomous compile target. `agent` steps become `agent()` calls with their\ntyped contract as `schema:`; gates require pre-authorization via\n`args.approved_gates`. Emits the script; running it is the operator's act.\n\n422s a spec whose verbs or argument names cannot bind. Compiling one would\nemit a script guaranteed to fail partway, which is worse than not compiling\nit: the failure would surface as a Workflow error rather than as the\nauthoring mistake it is.","operationId":"flow_compile_api_v1_flows_compile_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__FlowSpec"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/flows/contracts":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Available agent-stage contracts","description":"The typed I/O contracts for agent steps — name, description, the ctx keys\neach reads, and the JSON Schema its output must satisfy. An agent step whose\nname isn't here is free-form (uncontracted).","operationId":"flow_contracts_api_v1_flows_contracts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Response Flow Contracts Api V1 Flows Contracts Get"}}}}}}},"/cbhive/api/v1/flows/library":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Canonical flows you can start by name","description":"The reusable flow specs (resolve-cj-issue, deploy-fix, investigate) — each\nwith its stages, step count, and the ctx keys it needs. Start one via\n`POST /flows {\"flow\": \"<name>\", \"ctx\": {...}}`.","operationId":"flow_library_api_v1_flows_library_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbhive__FlowSummary"},"type":"array","title":"Response Flow Library Api V1 Flows Library Get"}}}}}}},"/cbhive/api/v1/flows/outcomes":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Flow outcomes — the Execute→Analyze learning ledger","description":"Every terminal flow run's outcome, newest first. A `done` run demoted its\nitem (acknowledged ruling + resolved); a `held`/`failed` run promoted it\n(watch). Rulings flow into every later digest — this is the loop closing.","operationId":"flow_outcomes_api_v1_flows_outcomes_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"done | held | failed","title":"Status"},"description":"done | held | failed"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbhive__FlowOutcome"},"title":"Response Flow Outcomes Api V1 Flows Outcomes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/flows/outcomes/scope-hint":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Cluster hint for an incoming item (Analyze got smarter)","description":"Given an incoming item's scope, surface prior outcomes that match so a flow\ncan be scoped across the cluster instead of run N times. Returns ``recurring``,\na ``suggested_cluster_id``, and the matching outcomes.","operationId":"flow_scope_hint_api_v1_flows_outcomes_scope_hint_get","parameters":[{"name":"repo","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Repo"}},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"}},{"name":"cluster_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cluster Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Flow Scope Hint Api V1 Flows Outcomes Scope Hint Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/flows/{run_id}":{"get":{"tags":["cbhive","CBHIVE"],"summary":"A flow run","description":"Fetch one flow run by id — its status, cursor, ctx, and step log (the\npersisted state machine). 404 if there's no such run.","operationId":"flow_get_api_v1_flows__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__FlowRun"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/flows/{run_id}/agent":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Supply a suspended agent step's output + continue","description":"Provide the typed output of a flow suspended at an agent judgment step\n(diagnose/patch/write-test) and resume the walk.","operationId":"flow_agent_api_v1_flows__run_id__agent_post","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__AgentOutput"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__FlowRun"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/flows/{run_id}/compile":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Compile this run's flow to a Workflow script","description":"The Workflow script for an existing run's spec — handy to take a flow that\nran with human-filled agent steps and re-run it autonomously.","operationId":"flow_compile_run_api_v1_flows__run_id__compile_get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/flows/{run_id}/diagram":{"get":{"tags":["cbhive","CBHIVE"],"summary":"The run's Waggle diagram (mermaid)","description":"Render the flow as a mermaid flowchart annotated with progress — done steps\n✓, the suspended/current step ▶ — so the operator reads the route and where\nit's parked before authorizing.","operationId":"flow_diagram_api_v1_flows__run_id__diagram_get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/flows/{run_id}/gate":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Operator gate — approve / deny / hold a suspended flow","description":"Resolve a flow suspended at an operator gate. ``by`` carries the operator;\npass the decision in ``note`` as ``approve``/``deny``/``hold`` (default\napprove). Approve steps past the gate and continues; deny/hold parks it.","operationId":"flow_gate_api_v1_flows__run_id__gate_post","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__GateDecision"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__FlowRun"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/hello":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Hello world","description":"Return a greeting.","operationId":"hello_api_v1_hello_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbhive/api/v1/mailbox":{"get":{"tags":["cbhive","CBHIVE"],"summary":"The mailbox","description":"Durable delivery surface: tracked items with state + history.","operationId":"mailbox_api_v1_mailbox_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"open (any non-resolved) | acknowledged | resolved | reopened | all","default":"open","title":"Status"},"description":"open (any non-resolved) | acknowledged | resolved | reopened | all"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbhive__MailboxItem"},"title":"Response Mailbox Api V1 Mailbox Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/mailbox/sync":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Fold the current digest into the mailbox","description":"Run intake → digest → upsert 'needs attention' items into the mailbox.\n\nRecurrences bump an existing item (no duplicates); a fault that recurs after\nbeing resolved reopens it; an open incident whose condition has cleared is\nauto-resolved (the mirror guardrail). Auto-resolve only runs on a full,\nall-sources-healthy poll — a source filter or a dead source skips it so a\ntransient outage can't mass-close its incidents. Returns {opened, reopened,\nupdated, resolved}.","operationId":"mailbox_sync_api_v1_mailbox_sync_post","parameters":[{"name":"window_hours","in":"query","required":false,"schema":{"type":"integer","maximum":336,"minimum":1,"default":48,"title":"Window Hours"}},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"integer"},"title":"Response Mailbox Sync Api V1 Mailbox Sync Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/mailbox/{item_id}/triage":{"get":{"tags":["cbhive","CBHIVE"],"summary":"The structured work-item for a doc-drop","description":"The full triage artifact: classified docs, distilled fork-tagged\nwork-items, and open questions.","operationId":"get_mailbox_triage_api_v1_mailbox__item_id__triage_get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"integer","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__TriageResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}},"post":{"tags":["cbhive","CBHIVE"],"summary":"Attach a structured triage to a doc-drop work-item","description":"Fold the agent's read of the pile onto the work-request item, then\nacknowledge it (triaged → awaiting the operator fork-gate). Auto, like CJ:\nno operator wait before triage; the gate is later, at execute.\n\nOnly ``work-request`` items are triaged (incidents are fleet-health, not\ndoc-drops). Re-POSTing replaces the artifact (re-triage on new docs).","operationId":"triage_mailbox_item_api_v1_mailbox__item_id__triage_post","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"integer","title":"Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__TriageRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__TriageResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/mailbox/{item_id}/{action}":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Transition a mailbox item","description":"Lifecycle: ack | resolve | reopen.","operationId":"mailbox_transition_api_v1_mailbox__item_id___action__post","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"integer","title":"Item Id"}},{"name":"action","in":"path","required":true,"schema":{"type":"string","title":"Action"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__MailboxItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/mint":{"post":{"tags":["cbhive","CBHIVE","CBHIVE"],"summary":"File an app-mint request","description":"The app-minting front door (cbhive#9): land an app-ask as an ``origin=mint``\nwork-request. The mint triage lane turns it into a staged LoomScript Spec on the\nnext dispatch tick; the ``mint-loom-app`` flow builds it behind the fork gate.\nRead-only intake — nothing is built here. Dedups on the target app slug.","operationId":"file_mint_api_v1_mint_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__MintRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__MailboxItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/roadmap":{"get":{"tags":["cbhive","CBHIVE"],"summary":"The LoomDSL roadmap (work-items across triages)","description":"Triaged deliverables as gated work-items, ordered by parent item then seq —\nthe input LoomDSL drives to its fork. Each carries its proposed `fork`, the\noperator's `fork_final` once gated, and its `status`/`delivery_ref`.","operationId":"roadmap_api_v1_roadmap_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"open (proposed|approved|dispatched) | proposed | approved | held | dispatched | done | all","default":"open","title":"Status"},"description":"open (proposed|approved|dispatched) | proposed | approved | held | dispatched | done | all"},{"name":"item_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter to one doc-drop's items","title":"Item Id"},"description":"Filter to one doc-drop's items"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbhive__WorkItem"},"title":"Response Roadmap Api V1 Roadmap Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/rulings":{"get":{"tags":["cbhive","CBHIVE"],"summary":"List operator rulings","description":"All persisted dispositions the operator has assigned to fingerprints.","operationId":"list_rulings_api_v1_rulings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbhive__Ruling"},"type":"array","title":"Response List Rulings Api V1 Rulings Get"}}}}}},"post":{"tags":["cbhive","CBHIVE"],"summary":"Rule on a fingerprint","description":"Record (or update) a disposition for a signal fingerprint.\n\n``known_noise``/``acknowledged``/``muted`` demote it out of 'needs\nattention'; ``watch`` forces it in. Honored by every subsequent digest.","operationId":"create_ruling_api_v1_rulings_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__RulingRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__Ruling"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/rulings/{fingerprint}":{"delete":{"tags":["cbhive","CBHIVE"],"summary":"Clear a ruling","description":"Remove a ruling so the fingerprint is judged on severity again.","operationId":"remove_ruling_api_v1_rulings__fingerprint__delete","parameters":[{"name":"fingerprint","in":"path","required":true,"schema":{"type":"string","title":"Fingerprint"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"boolean"},"title":"Response Remove Ruling Api V1 Rulings  Fingerprint  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/signals":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Live fleet signals (read-only)","description":"Intake layer (read-only).\n\nFans out across intake adapters and normalizes everything into Signals. No\npolicy, no dispatch — this just turns live fleet noise into something you\ncan curl. Sources: cbcron job failures, cboverseer open incidents.","operationId":"signals_api_v1_signals_get","parameters":[{"name":"window_hours","in":"query","required":false,"schema":{"type":"integer","maximum":336,"minimum":1,"description":"Look-back window","default":48,"title":"Window Hours"},"description":"Look-back window"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated source filter (e.g. 'cbcron,cboverseer'); default all","title":"Source"},"description":"Comma-separated source filter (e.g. 'cbcron,cboverseer'); default all"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbhive__Signal"},"title":"Response Signals Api V1 Signals Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/signals/sources":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Registered intake sources","description":"List the intake adapters currently wired into the control plane.","operationId":"signal_sources_api_v1_signals_sources_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Response Signal Sources Api V1 Signals Sources Get"}}}}}}},"/cbhive/api/v1/triage":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Triaged work-items (roadmap input)","description":"Every triaged doc-drop, newest first — the LoomDSL roadmap's input.\nLightweight rows (forks + counts); fetch one item's full artifact via\n``GET /mailbox/{item_id}/triage``.","operationId":"list_triage_api_v1_triage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbhive__TriageSummary"},"type":"array","title":"Response List Triage Api V1 Triage Get"}}}}}}},"/cbhive/api/v1/triage/autonomous":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Autonomously triage pending work-requests, then notify the rest","description":"The hands-free upgrade. Dispatches a headless triage agent for each pending\nwork-request (plan mode launches nothing), then runs detect+notify for whatever\ndidn't land — so autonomy degrades to the proven manual path, never to silence.\nThe fork gate is untouched: triage only produces ``proposed`` work-items.\nReturns {dispatch, notify}.","operationId":"triage_autonomous_api_v1_triage_autonomous_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Triage Autonomous Api V1 Triage Autonomous Post"}}}}}}},"/cbhive/api/v1/triage/mode":{"get":{"tags":["cbhive","CBHIVE"],"summary":"Triage autonomy mode (plan | live)","description":"Whether cbhive auto-triages itself (``live``, armed via\nCBHIVE_TRIAGE_AUTONOMOUS=1) or only detect+notifies (``plan``, the default).","operationId":"triage_mode_api_v1_triage_mode_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Triage Mode Api V1 Triage Mode Get"}}}}}}},"/cbhive/api/v1/triage/notify-pending":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Detect untriaged work-requests and ping the operator (cbcron tick)","description":"The auto-trigger (detect + notify mode). Finds open work-request items with\nno triage that the operator hasn't been pinged about, emails the list + the\n`triage-stash` command via cbnotify, and records them so the same drop isn't\nre-pinged. cbhive does NOT auto-triage — the operator dispatches (CJ-style).\nRegistered with cbcron (`*/30`). Returns {pending, notified, item_ids}.","operationId":"triage_notify_pending_api_v1_triage_notify_pending_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Triage Notify Pending Api V1 Triage Notify Pending Post"}}}}}}},"/cbhive/api/v1/work-items/{work_item_id}/approve":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Operator gate — approve a work-item's fork","description":"The umpire's call: lock the fork (override the proposal if given) and clear\nthe item to dispatch. 'unsure' is not approvable — the gate must resolve it.\n\nApproving a ``fork=loom`` mint work-item **auto-starts the ``mint-loom-app``\nflow** (build→verify), which then holds at its own ``gate-fork`` for the ship\ndecision — so the operator no longer hand-POSTs ``/api/v1/flows`` (cbhive#7).\nThe launch never fails the approval: on any error the item is returned approved\nfor a manual start. Disable via ``CBHIVE_MINT_AUTOSTART=0``.","operationId":"approve_api_v1_work_items__work_item_id__approve_post","parameters":[{"name":"work_item_id","in":"path","required":true,"schema":{"type":"integer","title":"Work Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__ApproveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__WorkItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/work-items/{work_item_id}/dispatch":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Record handoff to the fork delivery","description":"Hand the approved item to its fork (loom/stitch/issue) — an agent builds it.\nEnforces the gate: only an approved work-item dispatches.","operationId":"dispatch_api_v1_work_items__work_item_id__dispatch_post","parameters":[{"name":"work_item_id","in":"path","required":true,"schema":{"type":"integer","title":"Work Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__DispatchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__WorkItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/work-items/{work_item_id}/done":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Mark a work-item delivered","description":"Delivery complete.","operationId":"done_api_v1_work_items__work_item_id__done_post","parameters":[{"name":"work_item_id","in":"path","required":true,"schema":{"type":"integer","title":"Work Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HoldRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__WorkItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/api/v1/work-items/{work_item_id}/hold":{"post":{"tags":["cbhive","CBHIVE"],"summary":"Park a work-item (not now)","description":"Hold it out of the active roadmap; a later /approve revives it.","operationId":"hold_api_v1_work_items__work_item_id__hold_post","parameters":[{"name":"work_item_id","in":"path","required":true,"schema":{"type":"integer","title":"Work Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HoldRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__WorkItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HTTPValidationError"}}}}}}},"/cbhive/health":{"get":{"tags":["cbhive","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HealthResponse"}}}}}}},"/cbhive/health/detailed":{"get":{"tags":["cbhive","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__HealthResponse"}}}}}}},"/cbhive/prime":{"get":{"tags":["cbhive","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__PrimeResponse"}}}}}}},"/cbhive/status":{"get":{"tags":["cbhive","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbhive__StatusResponse"}}}}}}},"/cbinbox/health":{"get":{"tags":["cbinbox","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinbox__HealthResponse"}}}}}}},"/cbinbox/health/detailed":{"get":{"tags":["cbinbox","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinbox__HealthResponse"}}}}}}},"/cbinbox/messages/dm/{other_user_id}":{"get":{"tags":["cbinbox","CBINBOX"],"summary":"Fetch the DM thread between the caller and another user","description":"Returns DMs exchanged between the calling user (resolved from the cbauth-injected ``X-User-Id`` header) and ``other_user_id``, newest first. Pagination: pass ``before_id`` set to the smallest ``id`` from the previous page to walk older.","operationId":"get_dm_thread_messages_dm__other_user_id__get","parameters":[{"name":"other_user_id","in":"path","required":true,"schema":{"type":"string","title":"Other User Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"before_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"title":"Before Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Dm Thread Messages Dm  Other User Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinbox__HTTPValidationError"}}}}}}},"/cbinbox/messages/dm/{other_user_id}/read":{"post":{"tags":["cbinbox","CBINBOX"],"summary":"Mark the caller's DM thread with another user as read","description":"Records 'read up to now' for the calling user's thread with ``other_user_id``. Idempotent. Clears that thread's unread_count and decrements the caller's unread_total. Call when the thread is opened / viewed.","operationId":"mark_dm_read_messages_dm__other_user_id__read_post","parameters":[{"name":"other_user_id","in":"path","required":true,"schema":{"type":"string","title":"Other User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Mark Dm Read Messages Dm  Other User Id  Read Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinbox__HTTPValidationError"}}}}}}},"/cbinbox/messages/recent":{"get":{"tags":["cbinbox","CBINBOX"],"summary":"List the caller's distinct DM peers, newest-thread first","description":"Returns one row per peer the calling user has exchanged DMs with, newest first. Used by the Messages flash-app to render the conversation list.","operationId":"get_recent_peers_messages_recent_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Recent Peers Messages Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinbox__HTTPValidationError"}}}}}}},"/cbinbox/messages/topic/{topic}":{"get":{"tags":["cbinbox","CBINBOX"],"summary":"Fetch recent events published to a topic","description":"Returns ``topic`` events newest first. Caller must hold ``cbmesh.subscribe.<namespace>`` (matching the topic's leading segment). The internal namespace is service-kind-only — humans cannot read it via this endpoint.","operationId":"get_topic_events_messages_topic__topic__get","parameters":[{"name":"topic","in":"path","required":true,"schema":{"type":"string","title":"Topic"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"before_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"title":"Before Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Topic Events Messages Topic  Topic  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinbox__HTTPValidationError"}}}}}}},"/cbinbox/messages/unread_total":{"get":{"tags":["cbinbox","CBINBOX"],"summary":"Total unread DMs for the caller (inbox-bell badge)","description":"One integer: the number of DMs the caller has received but not yet read across all threads. Drives the global inbox-bell badge.","operationId":"get_unread_total_messages_unread_total_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Unread Total Messages Unread Total Get"}}}}}}},"/cbinbox/prime":{"get":{"tags":["cbinbox","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinbox__PrimeResponse"}}}}}}},"/cbinbox/push/subscribe":{"post":{"tags":["cbinbox","CBINBOX"],"summary":"Register a web-push subscription for the caller","description":"Idempotent on (caller_user_id, endpoint). The browser hands us the ``PushSubscription`` it got from ``PushManager.subscribe``; we store it so the push_sender can encrypt + POST notifications when DMs arrive while the tab is closed.","operationId":"subscribe_push_push_subscribe_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Subscribe Push Push Subscribe Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinbox__HTTPValidationError"}}}}}},"delete":{"tags":["cbinbox","CBINBOX"],"summary":"Remove a web-push subscription for the caller","description":"Used when the browser unsubscribes (settings toggle off, service worker uninstall). Idempotent — returns ``{deleted: 0}`` if the row was already gone.","operationId":"unsubscribe_push_push_subscribe_delete","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Unsubscribe Push Push Subscribe Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinbox__HTTPValidationError"}}}}}}},"/cbinbox/push/vapid_public":{"get":{"tags":["cbinbox","CBINBOX"],"summary":"Return the VAPID public key the browser needs to subscribe","description":"Browsers call this once on first push opt-in; the returned key is passed as ``applicationServerKey`` to ``PushManager.subscribe``. Public — no auth required, the key is non-secret by design.","operationId":"get_vapid_public_push_vapid_public_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Vapid Public Push Vapid Public Get"}}}}}}},"/cbinbox/status":{"get":{"tags":["cbinbox","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinbox__StatusResponse"}}}}}}},"/cbindex/health":{"get":{"tags":["cbindex","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HealthResponse"}}}},"401":{"description":"Unauthorized - Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"},"examples":{"missing_key":{"summary":"Missing API key","value":{"detail":"Missing API key. Provide X-API-Key header."}},"invalid_key":{"summary":"Invalid API key","value":{"detail":"Invalid API key"}}}}}}}}},"/cbindex/health/detailed":{"get":{"tags":["cbindex","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HealthResponse"}}}},"401":{"description":"Unauthorized - Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"},"examples":{"missing_key":{"summary":"Missing API key","value":{"detail":"Missing API key. Provide X-API-Key header."}},"invalid_key":{"summary":"Invalid API key","value":{"detail":"Invalid API key"}}}}}}}}},"/cbindex/index/markdown":{"post":{"tags":["cbindex","Document Indexing"],"summary":"Index a Markdown document","description":"Index a Markdown document and return its hierarchical tree structure.\n\n### Input Options\n\nProvide **one** of the following:\n- **File upload**: A `.md` file via multipart form\n- **Content string**: Raw markdown text in the `content` field\n\n### How It Works\n\n1. **Parse**: Markdown headers (`#`, `##`, etc.) define the structure\n2. **Analyze**: AI processes content at each level\n3. **Summarize**: Generates summaries for each section\n\n### Tree Thinning\n\nFor large documents, enable thinning to reduce tree depth:\n- `if_thinning: true` - Merge small sections\n- `thinning_threshold: 5000` - Minimum tokens to keep a section\n\n### Example Request\n\n```bash\ncurl -X POST \"https://api.example.com/index/markdown\" \\\n  -H \"X-API-Key: your-key\" \\\n  -F \"content=# My Document\\n\\n## Section 1\\n\\nContent here...\"\n```\n\n### Response Interpretation for LLMs\n\nMarkdown indexing produces cleaner structures than PDF since headers\nexplicitly define hierarchy. Use `line_num` for precise source references.","operationId":"index_markdown_endpoint_index_markdown_post","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbindex__Body_index_markdown_endpoint_index_markdown_post"}}}},"responses":{"200":{"description":"Successfully indexed Markdown","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__IndexResult"},"example":{"workspace_id":"550e8400-e29b-41d4-a716-446655440000","doc_name":"readme.md","doc_description":"Project documentation","structure":[{"title":"Introduction","node_id":"0001","line_num":1,"summary":"Overview of the project...","nodes":[]}]}}}},"401":{"description":"Unauthorized - Missing or invalid API key","content":{"application/json":{"examples":{"missing_key":{"summary":"Missing API key","value":{"detail":"Missing API key. Provide X-API-Key header."}},"invalid_key":{"summary":"Invalid API key","value":{"detail":"Invalid API key"}}},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"400":{"description":"Bad Request - Invalid input or validation error","content":{"application/json":{"examples":{"invalid_file":{"summary":"Invalid file type","value":{"detail":"File must be a PDF"}},"empty_file":{"summary":"Empty file","value":{"detail":"Empty file"}},"missing_input":{"summary":"Missing required input","value":{"detail":"Provide either file or content"}}},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"500":{"description":"Internal Server Error - Processing failed","content":{"application/json":{"examples":{"processing_error":{"summary":"Document processing failed","value":{"detail":"Processing failed: AI error"}}},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/index/pdf":{"post":{"tags":["cbindex","Document Indexing"],"summary":"Index a PDF document","description":"Index a PDF document and return its hierarchical tree structure.\n\n### How It Works\n\n1. **Upload**: Send a PDF file via multipart form\n2. **Analysis**: AI analyzes document structure and content\n3. **Tree Generation**: Returns a semantic tree with section summaries\n\n### Tree Structure\n\nThe response contains a hierarchical structure where each node represents a\ndocument section:\n\n```json\n{\n  \"title\": \"Chapter 1: Introduction\",\n  \"node_id\": \"0001\",\n  \"start_index\": 1,\n  \"end_index\": 5,\n  \"summary\": \"Overview of the main concepts...\",\n  \"nodes\": [\n    {\n      \"title\": \"1.1 Background\",\n      \"node_id\": \"0002\",\n      ...\n    }\n  ]\n}\n```\n\n### Workspace Integration\n\n- If `workspace_id` is provided, the file is stored in that workspace\n- If not provided, a new workspace is created automatically\n- The response includes the `workspace_id` for future reference\n\n### Configuration Options\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| `toc_check_page_num` | 20 | Pages to scan for table of contents |\n| `max_page_num_each_node` | 10 | Maximum pages per tree node |\n| `max_token_num_each_node` | 20000 | Maximum tokens per node |\n| `if_add_node_summary` | \"yes\" | Generate AI summaries for nodes |\n\n### Response Interpretation for LLMs\n\nUse `structure` for navigation. Each node's `summary` provides context without\nreading full content. Use `node_id` for precise references.","operationId":"index_pdf_endpoint_index_pdf_post","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbindex__Body_index_pdf_endpoint_index_pdf_post"}}}},"responses":{"200":{"description":"Successfully indexed PDF","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__IndexResult"},"example":{"workspace_id":"550e8400-e29b-41d4-a716-446655440000","doc_name":"research_paper.pdf","doc_description":"A research paper on machine learning","structure":[{"title":"Abstract","node_id":"0001","start_index":1,"end_index":1,"summary":"This paper presents...","nodes":[]}]}}}},"401":{"description":"Unauthorized - Missing or invalid API key","content":{"application/json":{"examples":{"missing_key":{"summary":"Missing API key","value":{"detail":"Missing API key. Provide X-API-Key header."}},"invalid_key":{"summary":"Invalid API key","value":{"detail":"Invalid API key"}}},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"400":{"description":"Bad Request - Invalid input or validation error","content":{"application/json":{"examples":{"invalid_file":{"summary":"Invalid file type","value":{"detail":"File must be a PDF"}},"empty_file":{"summary":"Empty file","value":{"detail":"Empty file"}},"missing_input":{"summary":"Missing required input","value":{"detail":"Provide either file or content"}}},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"500":{"description":"Internal Server Error - Processing failed","content":{"application/json":{"examples":{"processing_error":{"summary":"Document processing failed","value":{"detail":"Processing failed: AI error"}}},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/intel/index/youtube":{"post":{"tags":["cbindex","Intel"],"summary":"Extract and index YouTube transcript","description":"Extract transcript from a YouTube video and index it in one operation.\n\n## Overview\n\nCombines transcript extraction with document indexing:\n\n1. **Extract**: Submit URL to Intel service for transcript\n2. **Wait**: Poll until extraction completes\n3. **Convert**: Transform transcript to markdown format\n4. **Index**: Process through PageIndex for tree structure\n5. **Store**: Save markdown in workspace (optional)\n\n## Synchronous Behavior\n\nThis endpoint **blocks** until the transcript is fully extracted and\nindexed. This may take 10-60 seconds depending on video length.\n\n## Async Alternative\n\nFor long videos or better user experience, use the two-step workflow:\n\n1. `POST /intel/transcript` - Get job_id immediately\n2. `GET /intel/jobs/{job_id}` - Poll in background\n3. `POST /index/markdown` - Index when ready\n\n## Workspace Integration\n\nWhen `workspace_id` is provided, the generated markdown file\n(`{video_title}.md`) is stored in the workspace. If omitted,\na new workspace is auto-created.\n\n## Response Interpretation for LLMs\n\nThe response is a standard `IndexResult` with hierarchical tree\nstructure. The video title is used as the document name. Use\n`node_id` values to reference specific sections of the transcript.","operationId":"index_youtube_intel_index_youtube_post","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbindex__Body_index_youtube_intel_index_youtube_post"}}}},"responses":{"200":{"description":"Transcript extracted and indexed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__IndexResult"},"example":{"workspace_id":"550e8400-e29b-41d4-a716-446655440000","doc_name":"Introduction_to_Machine_Learning.md","structure":[{"title":"Introduction to Machine Learning","node_id":"0001","summary":"Overview of machine learning concepts","nodes":[{"title":"What is Machine Learning?","node_id":"0002","summary":"Definition and key concepts","nodes":[]}]}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"400":{"description":"Invalid URL or extraction failed","content":{"application/json":{"examples":{"invalid_url":{"summary":"Invalid YouTube URL","value":{"error":"Invalid URL","details":[{"field":"youtube_url","message":"Not a valid YouTube URL","code":"INVALID_URL"}]}},"no_captions":{"summary":"No captions available","value":{"error":"Extraction failed","details":[{"message":"Video has no captions","code":"NO_CAPTIONS"}]}}},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"408":{"description":"Extraction timeout","content":{"application/json":{"example":{"error":"Request timeout","details":[{"message":"Transcript extraction timed out after 120s","code":"TIMEOUT"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/intel/jobs/{job_id}":{"get":{"tags":["cbindex","Intel"],"summary":"Get job status","description":"Get the status of a transcript extraction job.\n\n## Overview\n\nPoll this endpoint to check the progress of a previously submitted\ntranscript extraction job. When the job completes, the response\nincludes the full transcript in the `result` field.\n\n## Status Values\n\n| Status | Description | Next Action |\n|--------|-------------|-------------|\n| `pending` | Job queued | Continue polling |\n| `processing` | Extraction in progress | Continue polling |\n| `completed` | Transcript ready | Use `result.transcript` |\n| `failed` | Extraction failed | Check `error_message` |\n\n## Polling Recommendations\n\n- **Interval**: 2-5 seconds between requests\n- **Timeout**: Consider giving up after 2-3 minutes\n- **Backoff**: Increase interval if receiving rate limits\n\n## Response Interpretation for LLMs\n\nCheck the `status` field first:\n- `completed`: Extract transcript from `result.transcript`\n- `failed`: Report `error_message` to user\n- Other: Continue polling","operationId":"get_job_status_intel_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","description":"Job ID returned from transcript submission","examples":["job_abc123xyz"],"title":"Job Id"},"description":"Job ID returned from transcript submission"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Job status retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__JobStatusResponse"},"examples":{"completed":{"summary":"Completed job","value":{"job_id":"job_abc123xyz","status":"completed","youtube_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","created_at":"2025-01-22T10:00:00Z","completed_at":"2025-01-22T10:01:30Z","result":{"job_id":"job_abc123xyz","youtube_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","video_title":"Introduction to ML","transcript":"Hello everyone, welcome...","duration_seconds":1845.0}}},"processing":{"summary":"Processing job","value":{"job_id":"job_abc123xyz","status":"processing","youtube_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","created_at":"2025-01-22T10:00:00Z"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"400":{"description":"Invalid job ID","content":{"application/json":{"example":{"error":"Job not found","details":[{"field":"job_id","message":"No job with specified ID","code":"NOT_FOUND"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/intel/transcript":{"post":{"tags":["cbindex","Intel"],"summary":"Submit transcript job","description":"Submit a YouTube URL for transcript extraction.\n\n## Overview\n\nInitiates an asynchronous job to extract the transcript from a YouTube\nvideo. Returns immediately with a job ID for status polling.\n\n## Supported URL Formats\n\n- `https://www.youtube.com/watch?v=VIDEO_ID`\n- `https://youtu.be/VIDEO_ID`\n- `https://www.youtube.com/embed/VIDEO_ID`\n\n## Async Workflow\n\n1. **Submit** - This endpoint returns a `job_id`\n2. **Poll** - Use `GET /intel/jobs/{job_id}` to check status\n3. **Retrieve** - When status is `completed`, the result contains the transcript\n\n## Caption Sources\n\nThe service tries these sources in order:\n1. Manual captions (highest quality)\n2. Auto-generated captions\n3. Third-party captions\n\nIf no captions are available, the job will fail with an appropriate error.\n\n## Response Interpretation for LLMs\n\nStore the `job_id` and poll every 2-5 seconds until `status` is\neither `completed` or `failed`. The transcript is not immediately\navailable - extraction typically takes 5-30 seconds.","operationId":"submit_transcript_job_intel_transcript_post","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbindex__Body_submit_transcript_job_intel_transcript_post"}}}},"responses":{"202":{"description":"Job submitted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__TranscriptJob"},"example":{"job_id":"job_abc123xyz","youtube_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","status":"pending","created_at":"2025-01-22T10:00:00Z"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"400":{"description":"Invalid YouTube URL","content":{"application/json":{"example":{"error":"Invalid URL","details":[{"field":"youtube_url","message":"Not a valid YouTube URL","code":"INVALID_URL"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/ocr":{"post":{"tags":["cbindex","OCR"],"summary":"Extract text from image","description":"Perform OCR on an image and return the extracted text.\n\n## Overview\n\nExtracts text from an image using the ai.campaignbrain.dev OCR service.\nSupports multiple input methods: file upload, URL, or base64 data.\n\n## Input Methods\n\nProvide **exactly one** of the following:\n\n| Parameter | Description |\n|-----------|-------------|\n| `file` | Multipart file upload |\n| `url` | Public URL to image |\n| `base64_data` | Base64-encoded image data |\n\n## Supported Formats\n\n- PNG, JPEG, GIF, WebP, TIFF\n- Maximum file size: 20MB\n\n## Response Interpretation for LLMs\n\nThe `text` field contains the extracted content with layout preserved.\nThe `confidence` score (0.0-1.0) indicates OCR quality when available.\nScores below 0.70 may have significant errors requiring review.","operationId":"perform_ocr_ocr_post","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbindex__Body_perform_ocr_ocr_post"}}}},"responses":{"200":{"description":"Text successfully extracted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__OCRResult"},"example":{"text":"INVOICE #12345\n\nBill To:\nJohn Doe\n123 Main St","source_type":"file","filename":"invoice.png","confidence":0.95}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"400":{"description":"Invalid input","content":{"application/json":{"examples":{"no_input":{"summary":"No image provided","value":{"error":"Invalid request","details":[{"message":"Provide file, url, or base64","code":"MISSING_INPUT"}]}},"multiple_inputs":{"summary":"Multiple images provided","value":{"error":"Invalid request","details":[{"message":"Provide only one source","code":"MULTIPLE_INPUTS"}]}}},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/ocr/index/image":{"post":{"tags":["cbindex","OCR"],"summary":"OCR and index image","description":"Extract text from an image via OCR and index it in a single operation.\n\n## Overview\n\nCombines OCR text extraction with document indexing:\n\n1. **OCR**: Extract text from the image\n2. **Convert**: Transform text to markdown format\n3. **Index**: Process through PageIndex to create tree structure\n4. **Store**: Save image and markdown in workspace (optional)\n\n## Input Methods\n\nProvide **exactly one** of:\n- `file`: Multipart file upload\n- `url`: Public URL to image\n- `base64_data`: Base64-encoded image\n\n## Workspace Integration\n\nWhen `workspace_id` is provided:\n- Original image is stored in workspace\n- Generated markdown (`{filename}_ocr.md`) is also stored\n- Both are tracked in the workspace manifest\n\nWhen `workspace_id` is omitted:\n- A new workspace is auto-created\n- The workspace_id is returned in the response\n\n## Response Interpretation for LLMs\n\nThe response is an `IndexResult` with the hierarchical tree structure\nof the extracted and indexed text. Use `node_id` values to reference\nspecific sections of the OCR'd content.","operationId":"index_image_ocr_index_image_post","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbindex__Body_index_image_ocr_index_image_post"}}}},"responses":{"200":{"description":"Image OCR'd and indexed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__IndexResult"},"example":{"workspace_id":"550e8400-e29b-41d4-a716-446655440000","doc_name":"receipt_ocr.md","structure":[{"title":"Receipt","node_id":"0001","summary":"Receipt from Example Shop","nodes":[]}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"400":{"description":"Invalid input or OCR failure","content":{"application/json":{"example":{"error":"OCR failed","details":[{"message":"Could not extract text from image","code":"OCR_FAILED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/ocr/index/text":{"post":{"tags":["cbindex","OCR"],"summary":"Index plain text","description":"Index plain text content by converting it to markdown.\n\n## Overview\n\nConverts plain text to markdown format and indexes it through\nthe standard markdown indexing pipeline. Useful for:\n\n- Pasted text from documents\n- Email content\n- Text extracted by external tools\n- Any unstructured text content\n\n## Text Processing\n\n1. Text is wrapped with a markdown title header (`# {title}`)\n2. Paragraph breaks (double newlines) are preserved\n3. Content is processed by PageIndex for tree extraction\n\n## Workspace Integration\n\nWhen `workspace_id` is provided, the generated markdown file\nis stored in the workspace. If omitted, a new workspace is created.","operationId":"index_text_ocr_index_text_post","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbindex__Body_index_text_ocr_index_text_post"}}}},"responses":{"200":{"description":"Text indexed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__IndexResult"},"example":{"workspace_id":"550e8400-e29b-41d4-a716-446655440000","doc_name":"Meeting Notes.md","structure":[{"title":"Meeting Notes","node_id":"0001","summary":"Notes from team meeting","nodes":[]}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/prime":{"get":{"tags":["cbindex","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__PrimeResponse"}}}},"401":{"description":"Unauthorized - Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"},"examples":{"missing_key":{"summary":"Missing API key","value":{"detail":"Missing API key. Provide X-API-Key header."}},"invalid_key":{"summary":"Invalid API key","value":{"detail":"Invalid API key"}}}}}}}}},"/cbindex/status":{"get":{"tags":["cbindex","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__StatusResponse"}}}},"401":{"description":"Unauthorized - Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"},"examples":{"missing_key":{"summary":"Missing API key","value":{"detail":"Missing API key. Provide X-API-Key header."}},"invalid_key":{"summary":"Invalid API key","value":{"detail":"Invalid API key"}}}}}}}}},"/cbindex/workspaces":{"post":{"tags":["cbindex","Workspaces"],"summary":"Create workspace","description":"Create a new empty workspace for document storage.\n\n## Overview\n\nCreates a new workspace with a unique UUID identifier. The workspace\nis immediately ready to accept file uploads.\n\n## Workspace Lifecycle\n\n1. **Create** - This endpoint creates the workspace\n2. **Upload** - Use `POST /workspaces/{id}/files` to add documents\n3. **Index** - Use `/index/pdf` or `/index/markdown` with `workspace_id`\n4. **Delete** - Use `DELETE /workspaces/{id}` to remove when done\n\n## Response Interpretation for LLMs\n\nThe returned `workspace_id` should be stored and used for all\nsubsequent operations on this workspace. The ID is a UUID v4 string.","operationId":"create_workspace_workspaces_post","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"201":{"description":"Workspace created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__WorkspaceCreateResponse"},"example":{"workspace_id":"550e8400-e29b-41d4-a716-446655440000","created_at":"2025-01-22T10:00:00Z"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}},"get":{"tags":["cbindex","Workspaces"],"summary":"List workspaces","description":"List all workspaces with summary statistics.\n\n## Overview\n\nReturns a list of all workspaces with basic metadata including\nfile counts and total sizes. Use this for dashboard views or\nworkspace selection interfaces.\n\n## Response Interpretation for LLMs\n\nThe `workspaces` array contains summary objects. To get full\ndetails including file lists, fetch individual workspaces using\n`GET /workspaces/{workspace_id}`.\n\n| Field | Description |\n|-------|-------------|\n| `file_count` | Number of uploaded files |\n| `total_size_bytes` | Combined size of all files |\n| `updated_at` | Last modification timestamp |","operationId":"list_workspaces_workspaces_get","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"List of workspace summaries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__WorkspaceListResponse"},"example":{"workspaces":[{"workspace_id":"550e8400-e29b-41d4-a716-446655440000","created_at":"2025-01-22T10:00:00Z","updated_at":"2025-01-22T14:30:00Z","file_count":3,"total_size_bytes":5242880}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/workspaces/{workspace_id}":{"get":{"tags":["cbindex","Workspaces"],"summary":"Get workspace details","description":"Get complete workspace details including file manifest.\n\n## Overview\n\nReturns the full workspace manifest with all file metadata.\nUse this to inspect workspace contents before indexing or\nto verify upload status.\n\n## File Status\n\nEach file in the `files` array includes an `index_processed`\nboolean indicating whether it has been indexed:\n\n| Value | Meaning |\n|-------|---------|\n| `true` | File has been indexed, ready for retrieval |\n| `false` | File uploaded but not yet indexed |\n\n## Response Interpretation for LLMs\n\nTo index unprocessed files, filter the `files` array for entries\nwhere `index_processed` is `false`, then call the appropriate\nindex endpoint (`/index/pdf` or `/index/markdown`) for each.","operationId":"get_workspace_workspaces__workspace_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace to retrieve","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace to retrieve"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Workspace manifest with file list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__WorkspaceManifest"},"example":{"workspace_id":"550e8400-e29b-41d4-a716-446655440000","created_at":"2025-01-22T10:00:00Z","updated_at":"2025-01-22T14:30:00Z","files":[{"file_id":"a1b2c3d4e5f67890","original_filename":"report.pdf","stored_filename":"a1b2c3d4e5f67890_report.pdf","file_hash":"a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890","file_type":"pdf","size_bytes":1048576,"uploaded_at":"2025-01-22T10:30:00Z","index_processed":true}],"metadata":{}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Workspace not found","content":{"application/json":{"example":{"error":"Workspace not found","details":[{"field":"workspace_id","message":"No workspace with specified ID","code":"NOT_FOUND"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}},"delete":{"tags":["cbindex","Workspaces"],"summary":"Delete workspace","description":"Delete a workspace and all its files.\n\n## Overview\n\nPermanently deletes the workspace directory and all contained files.\nThis operation cannot be undone.\n\n## Cascade Deletion\n\nDeleting a workspace removes:\n- The workspace manifest\n- All uploaded files\n- Any associated metadata\n\n## Warning\n\nEnsure you have exported or backed up any important data before\ndeletion. Index results stored elsewhere will still reference\nthis workspace ID but the files will no longer be accessible.","operationId":"delete_workspace_workspaces__workspace_id__delete","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace to delete","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace to delete"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Workspace deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__WorkspaceDeleteResponse"},"example":{"status":"deleted","workspace_id":"550e8400-e29b-41d4-a716-446655440000"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Workspace not found","content":{"application/json":{"example":{"error":"Workspace not found","details":[{"field":"workspace_id","message":"No workspace with specified ID","code":"NOT_FOUND"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/workspaces/{workspace_id}/conversations":{"get":{"tags":["cbindex","Chat"],"summary":"List conversations","description":"List all conversations in a workspace.\n\n## Overview\n\nReturns all conversations with basic metadata. Conversations are\nsorted by most recently updated first.\n\n## Response Interpretation for LLMs\n\nUse `conversation_id` to continue existing conversations.\nThe `document_scope` shows which documents a conversation is limited to.","operationId":"list_conversations_workspaces__workspace_id__conversations_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ConversationListResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}},"post":{"tags":["cbindex","Chat"],"summary":"Create conversation","description":"Create a new conversation in a workspace.\n\n## Overview\n\nCreates an empty conversation for multi-turn dialog with documents.\nOptionally scope to specific documents using `document_scope`.\n\n## Document Scope\n\nWhen `document_scope` is provided, queries will only search those\ndocuments. Leave empty to search all indexed documents.","operationId":"create_conversation_workspaces__workspace_id__conversations_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ConversationCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ConversationCreateResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/workspaces/{workspace_id}/conversations/{conversation_id}":{"get":{"tags":["cbindex","Chat"],"summary":"Get conversation","description":"Get a conversation with full message history.\n\n## Overview\n\nReturns the complete conversation including all messages\nand their source references.\n\n## Message History\n\nMessages are ordered chronologically. Each assistant message\nincludes `references` showing which document sections were used.","operationId":"get_conversation_workspaces__workspace_id__conversations__conversation_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace"},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","description":"Conversation identifier","examples":["conv_abc123"],"title":"Conversation Id"},"description":"Conversation identifier"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__Conversation"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}},"delete":{"tags":["cbindex","Chat"],"summary":"Delete conversation","description":"Delete a conversation and all its messages.\n\n## Overview\n\nPermanently removes a conversation and its history.\nThis operation cannot be undone.","operationId":"delete_conversation_workspaces__workspace_id__conversations__conversation_id__delete","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace"},{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","description":"Conversation identifier","examples":["conv_abc123"],"title":"Conversation Id"},"description":"Conversation identifier"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__DeleteResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/workspaces/{workspace_id}/files":{"post":{"tags":["cbindex","Workspaces"],"summary":"Upload file","description":"Upload a file to a workspace.\n\n## Overview\n\nUploads a file to the specified workspace. The file is stored with\ncontent-addressed naming using SHA-256 hashing for deduplication.\n\n## Supported File Types\n\n| Extension | Type | Notes |\n|-----------|------|-------|\n| `.pdf` | PDF | For document indexing |\n| `.md`, `.markdown` | Markdown | For structured text |\n| `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp` | Image | For OCR processing |\n| `.txt` | Text | Plain text documents |\n\n## Deduplication\n\nIf an identical file (by content hash) already exists in the\nworkspace, the existing file metadata is returned without creating\na duplicate. This makes the operation idempotent.\n\n## Size Limits\n\n- Maximum file size: 50MB (configurable)\n- Empty files are rejected\n\n## Response Interpretation for LLMs\n\nThe returned `file_id` is used for subsequent operations like\ndeletion. Store this value if you need to reference the file later.","operationId":"upload_file_workspaces__workspace_id__files_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace to upload to","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace to upload to"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbindex__Body_upload_file_workspaces__workspace_id__files_post"}}}},"responses":{"201":{"description":"File uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__FileUploadResponse"},"example":{"file_id":"a1b2c3d4e5f67890","original_filename":"research_paper.pdf","stored_filename":"a1b2c3d4e5f67890_research_paper.pdf","file_type":"pdf","size_bytes":2048576,"workspace_id":"550e8400-e29b-41d4-a716-446655440000"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"400":{"description":"Invalid file upload","content":{"application/json":{"examples":{"no_filename":{"summary":"Missing filename","value":{"error":"Invalid file","details":[{"field":"file","message":"Filename required","code":"MISSING_FILENAME"}]}},"empty_file":{"summary":"Empty file","value":{"error":"Invalid file","details":[{"field":"file","message":"Empty file","code":"EMPTY_FILE"}]}}},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Workspace not found","content":{"application/json":{"example":{"error":"Workspace not found","details":[{"field":"workspace_id","message":"No workspace with specified ID","code":"NOT_FOUND"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}},"get":{"tags":["cbindex","Workspaces"],"summary":"List files","description":"List all files in a workspace.\n\n## Overview\n\nReturns metadata for all files in the workspace. This is equivalent\nto the `files` array in the workspace manifest but as a standalone\nendpoint for convenience.\n\n## Response Interpretation for LLMs\n\nUse `file_id` values to reference specific files in delete operations.\nCheck `index_processed` to identify files needing indexing.","operationId":"list_files_workspaces__workspace_id__files_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"List of file metadata","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbindex__FileMetadata"},"title":"Response List Files Workspaces  Workspace Id  Files Get"},"example":[{"file_id":"a1b2c3d4e5f67890","original_filename":"report.pdf","stored_filename":"a1b2c3d4e5f67890_report.pdf","file_hash":"a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890","file_type":"pdf","size_bytes":1048576,"uploaded_at":"2025-01-22T10:30:00Z","index_processed":true}]}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Workspace not found","content":{"application/json":{"example":{"error":"Workspace not found","details":[{"field":"workspace_id","message":"No workspace with specified ID","code":"NOT_FOUND"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/workspaces/{workspace_id}/files/{file_id}":{"delete":{"tags":["cbindex","Workspaces"],"summary":"Delete file","description":"Delete a specific file from a workspace.\n\n## Overview\n\nRemoves a single file from the workspace. The file is deleted from\ndisk and removed from the workspace manifest.\n\n## Note\n\nDeleting a file does not affect any index results that were created\nfrom that file. The index tree structure remains valid but the\nsource file will no longer be accessible.","operationId":"delete_file_workspaces__workspace_id__files__file_id__delete","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace"},{"name":"file_id","in":"path","required":true,"schema":{"type":"string","description":"ID of the file to delete (16-char hash prefix)","examples":["a1b2c3d4e5f67890"],"title":"File Id"},"description":"ID of the file to delete (16-char hash prefix)"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"File deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__FileDeleteResponse"},"example":{"status":"deleted","file_id":"a1b2c3d4e5f67890","workspace_id":"550e8400-e29b-41d4-a716-446655440000"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Authentication required","details":[{"field":"X-API-Key","message":"Missing or invalid API key header","code":"UNAUTHORIZED"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"File or workspace not found","content":{"application/json":{"example":{"error":"File not found","details":[{"field":"file_id","message":"No file with specified ID","code":"NOT_FOUND"}]},"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/workspaces/{workspace_id}/indexes":{"get":{"tags":["cbindex","Chat"],"summary":"List indexed documents","description":"List all indexed documents in a workspace.\n\n## Overview\n\nReturns all document indexes stored in the workspace. Each index\ncontains the hierarchical tree structure generated during indexing.\n\n## Response Interpretation for LLMs\n\nUse `file_id` to reference specific documents in queries. The\n`total_nodes` and `max_depth` fields indicate document complexity.","operationId":"list_indexes_workspaces__workspace_id__indexes_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__IndexListResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/workspaces/{workspace_id}/indexes/{file_id}":{"get":{"tags":["cbindex","Chat"],"summary":"Get document index","description":"Get the complete index for a specific document.\n\n## Overview\n\nReturns the full document index including the hierarchical tree\nstructure with all node metadata (titles, summaries, page ranges).\n\n## Tree Navigation\n\nUse the `structure` field to navigate the document:\n- Top-level items are major sections\n- Nested `nodes` arrays contain subsections\n- Use `node_id` for precise references","operationId":"get_index_workspaces__workspace_id__indexes__file_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace"},{"name":"file_id","in":"path","required":true,"schema":{"type":"string","description":"File identifier (16-char hash prefix)","examples":["a1b2c3d4e5f67890"],"title":"File Id"},"description":"File identifier (16-char hash prefix)"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__DocumentIndex"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}},"delete":{"tags":["cbindex","Chat"],"summary":"Delete document index","description":"Delete the index for a specific document.\n\n## Overview\n\nRemoves the stored index for a document. The source file\nremains in the workspace and can be re-indexed.\n\n## Note\n\nDeleting an index does not affect existing conversations\nthat may reference this document.","operationId":"delete_index_workspaces__workspace_id__indexes__file_id__delete","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace"},{"name":"file_id","in":"path","required":true,"schema":{"type":"string","description":"File identifier (16-char hash prefix)","examples":["a1b2c3d4e5f67890"],"title":"File Id"},"description":"File identifier (16-char hash prefix)"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__DeleteResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbindex/workspaces/{workspace_id}/query":{"post":{"tags":["cbindex","Chat"],"summary":"Query documents","description":"Query indexed documents with natural language.\n\n## Overview\n\nThe main endpoint for asking questions about indexed documents.\nUses LLM-based reasoning to find relevant sections and generate\nanswers with source citations.\n\n## How It Works\n\n1. **Document Selection**: If multiple docs, LLM picks relevant ones\n2. **Tree Search**: LLM reasons over titles/summaries to find nodes\n3. **Content Extraction**: Reads content from selected nodes\n4. **Answer Generation**: LLM synthesizes answer with citations\n\n## Conversation Context\n\nProvide `conversation_id` to maintain context across questions.\nIf not provided, creates a new conversation automatically.\n\n## Document Scope\n\nUse `document_scope` to limit search to specific documents.\nUseful when you know which documents contain the answer.\n\n## Example Usage\n\n```bash\n# New query (creates conversation)\ncurl -X POST \".../workspaces/{id}/query\" \\\n  -d '{\"query\": \"What are the main findings?\"}'\n\n# Follow-up (uses context)\ncurl -X POST \".../workspaces/{id}/query\" \\\n  -d '{\"query\": \"Elaborate on point 2\", \"conversation_id\": \"...\"}'\n```","operationId":"query_documents_workspaces__workspace_id__query_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","description":"UUID of the workspace","examples":["550e8400-e29b-41d4-a716-446655440000"],"title":"Workspace Id"},"description":"UUID of the workspace"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__QueryRequest"}}}},"responses":{"200":{"description":"Query answered successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__QueryResponse"},"example":{"conversation_id":"conv_abc123","answer":"The main findings include...","references":[{"file_id":"a1b2c3d4e5f67890","doc_name":"paper.pdf","node_id":"0005","title":"4. Results","start_index":12,"end_index":18}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbindex__HTTPValidationError"}}}}}}},"/cbinfra/api/auth/{slug}/session":{"post":{"tags":["cbinfra","Auth"],"summary":"Mint a vault-backed login session for a site","description":"Mint (or serve a cached) login session as the stored identity. The caller\nreceives cookies/headers, never the credential.","operationId":"authenticate_api_auth__slug__session_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"Site slug","title":"Slug"},"description":"Site slug"},{"name":"masquerade_user","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Which stored identity to log in as (multi-identity slugs).","title":"Masquerade User"},"description":"Which stored identity to log in as (multi-identity slugs)."},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Skip the cached session and re-login.","default":false,"title":"Force"},"description":"Skip the cached session and re-login."},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__Session"}}}},"404":{"description":"No login adapter registered for the slug"},"422":{"description":"No credentials in the vault for the slug"},"502":{"description":"The site login failed"}}},"delete":{"tags":["cbinfra","Auth"],"summary":"Drop (and invalidate) a login session","operationId":"logout_api_auth__slug__session_delete","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","description":"Site slug","title":"Slug"},"description":"Site slug"},{"name":"masquerade_user","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Masquerade User"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__LogoutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/bootstrap":{"post":{"tags":["cbinfra","bootstrap"],"summary":"Bootstrap Project","description":"Bootstrap a new project.\n\nThis endpoint will:\n1. Check if the GitHub repository exists\n2. Create the repository if it doesn't exist\n3. Clone the repository locally\n4. Generate project files based on the flavor (python, node, both)\n5. Set up the development environment\n\nExample request:\n```json\n{\n    \"org\": \"Nominate-AI\",\n    \"project\": \"cbtest\",\n    \"flavor\": \"python\",\n    \"parent_dir\": \"/home/bisenbek/projects/nominate\",\n    \"pyenv\": \"nominates\",\n    \"private\": true,\n    \"description\": \"Test project\"\n}\n```","operationId":"bootstrap_project_api_bootstrap_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__BootstrapRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__BootstrapResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/bootstrap/check/{org}/{project}":{"get":{"tags":["cbinfra","bootstrap"],"summary":"Check Repo Exists","description":"Check if a GitHub repository exists.\n\nReturns whether the repo exists and its URL if it does.","operationId":"check_repo_exists_api_bootstrap_check__org___project__get","parameters":[{"name":"org","in":"path","required":true,"schema":{"type":"string","title":"Org"}},{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/bootstrap/host":{"post":{"tags":["cbinfra","bootstrap"],"summary":"Bootstrap Host","description":"Provision a new subdomain on any BIND-managed domain.\n\nThis endpoint will:\n1. Validate the domain has a BIND zone file\n2. Check for wildcard SSL cert (warn if missing)\n3. Allocate port(s) from the CB port range\n4. Generate a normalized NGINX config\n5. Install to sites-nominate/ and sites-enabled/\n6. Test and reload NGINX\n\nExample request:\n```json\n{\n    \"domain\": \"campaignbrain.dev\",\n    \"subdomain\": \"analytics\",\n    \"service_type\": \"api\",\n    \"description\": \"Analytics dashboard API\",\n    \"pin_gate\": true\n}\n```","operationId":"bootstrap_host_api_bootstrap_host_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HostRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HostResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/bind/records":{"get":{"tags":["cbinfra","config"],"summary":"List Dns Records","description":"List all DNS records.","operationId":"list_dns_records_api_config_bind_records_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbinfra__DNSRecord"},"type":"array","title":"Response List Dns Records Api Config Bind Records Get"}}}}}},"post":{"tags":["cbinfra","config"],"summary":"Add Dns Record","description":"Add a DNS record.\n\nNote: For most subdomains, the wildcard *.campaignbrain.dev already provides coverage.","operationId":"add_dns_record_api_config_bind_records_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__DNSRecord"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/bind/records/{name}":{"delete":{"tags":["cbinfra","config"],"summary":"Delete Dns Record","description":"Delete a DNS record.","operationId":"delete_dns_record_api_config_bind_records__name__delete","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"confirm","in":"query","required":false,"schema":{"type":"boolean","description":"Confirm deletion","default":false,"title":"Confirm"},"description":"Confirm deletion"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/bind/reload":{"post":{"tags":["cbinfra","config"],"summary":"Reload Bind","description":"Reload BIND DNS server.","operationId":"reload_bind_api_config_bind_reload_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbinfra/api/config/certs":{"get":{"tags":["cbinfra","config"],"summary":"List Certs","description":"List all SSL certificates.","operationId":"list_certs_api_config_certs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbinfra__CertInfo"},"type":"array","title":"Response List Certs Api Config Certs Get"}}}}}}},"/cbinfra/api/config/certs/expiring":{"get":{"tags":["cbinfra","config"],"summary":"Check Expiring","description":"Check for certificates expiring within N days.","operationId":"check_expiring_api_config_certs_expiring_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","description":"Days until expiry","default":30,"title":"Days"},"description":"Days until expiry"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/certs/renew":{"post":{"tags":["cbinfra","config"],"summary":"Renew Certs","description":"Renew all certificates.","operationId":"renew_certs_api_config_certs_renew_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbinfra/api/config/certs/{domain}":{"post":{"tags":["cbinfra","config"],"summary":"Request Cert","description":"Request a new SSL certificate.\n\nUses HTTP-01 challenge via nginx plugin.\n\nNote: For *.campaignbrain.dev subdomains, the wildcard cert already provides coverage.","operationId":"request_cert_api_config_certs__domain__post","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string","title":"Domain"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/certs/{name}":{"get":{"tags":["cbinfra","config"],"summary":"Get Cert","description":"Get certificate details.","operationId":"get_cert_api_config_certs__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__CertInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/deploy":{"post":{"tags":["cbinfra","config"],"summary":"Deploy Service Endpoint","description":"Deploy a new service.\n\nCreates systemd unit, NGINX config, starts service, and reloads NGINX.\n\nExample:\n```json\n{\n    \"name\": \"cbtest\",\n    \"description\": \"Test service\"\n}\n```\n\nAll other fields have sensible defaults:\n- subdomain: defaults to name\n- port: auto-allocated\n- project_path: /home/bisenbek/projects/nominate/{name}\n- service_type: auto-detected\n- use_pin_gate: true\n- ssl_mode: wildcard","operationId":"deploy_service_endpoint_api_config_deploy_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__DeployRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__DeployResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/nginx":{"get":{"tags":["cbinfra","config"],"summary":"List Nginx Configs","description":"List every config nginx is serving, by filename.\n\nNames are the real filenames, so `cb-wildcard` and `cb-split-apps` (which\ncarry no `.conf` extension and between them route most of the fleet) appear\nalongside the `*.conf` vhosts.","operationId":"list_nginx_configs_api_config_nginx_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Response List Nginx Configs Api Config Nginx Get"}}}}}}},"/cbinfra/api/config/nginx/reload":{"post":{"tags":["cbinfra","config"],"summary":"Reload Nginx","description":"Reload NGINX.","operationId":"reload_nginx_api_config_nginx_reload_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbinfra/api/config/nginx/test":{"post":{"tags":["cbinfra","config"],"summary":"Test Nginx","description":"Test NGINX configuration.","operationId":"test_nginx_api_config_nginx_test_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbinfra/api/config/nginx/{name}":{"get":{"tags":["cbinfra","config"],"summary":"Get Nginx Config","description":"Get NGINX configuration details.","operationId":"get_nginx_config_api_config_nginx__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__NginxConfig"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"delete":{"tags":["cbinfra","config"],"summary":"Delete Nginx Config","description":"Delete NGINX configuration.","operationId":"delete_nginx_config_api_config_nginx__name__delete","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"confirm","in":"query","required":false,"schema":{"type":"boolean","description":"Confirm deletion","default":false,"title":"Confirm"},"description":"Confirm deletion"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/ports":{"get":{"tags":["cbinfra","config"],"summary":"List Ports","description":"List all port allocations by range.","operationId":"list_ports_api_config_ports_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"$ref":"#/components/schemas/cbinfra__PortRange"},"type":"object","title":"Response List Ports Api Config Ports Get"}}}}}}},"/cbinfra/api/config/ports/next":{"get":{"tags":["cbinfra","config"],"summary":"Get Next Port","description":"Get next available port in range.","operationId":"get_next_port_api_config_ports_next_get","parameters":[{"name":"start","in":"query","required":false,"schema":{"type":"integer","description":"Start of range","default":32207,"title":"Start"},"description":"Start of range"},{"name":"end","in":"query","required":false,"schema":{"type":"integer","description":"End of range","default":32299,"title":"End"},"description":"End of range"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/ports/{port}":{"get":{"tags":["cbinfra","config"],"summary":"Check Port","description":"Check if a specific port is available.","operationId":"check_port_api_config_ports__port__get","parameters":[{"name":"port","in":"path","required":true,"schema":{"type":"integer","title":"Port"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/services":{"get":{"tags":["cbinfra","config"],"summary":"List Services","description":"List all deployed services with their status.","operationId":"list_services_api_config_services_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbinfra__ServiceStatus"},"type":"array","title":"Response List Services Api Config Services Get"}}}}}}},"/cbinfra/api/config/services/{name}":{"get":{"tags":["cbinfra","config"],"summary":"Get Service","description":"Get service details and status.","operationId":"get_service_api_config_services__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__ServiceStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"delete":{"tags":["cbinfra","config"],"summary":"Undeploy Service","description":"Undeploy a service.\n\nRemoves systemd unit, NGINX config, and registry entry.\n\nRequires ?confirm=true to proceed.","operationId":"undeploy_service_api_config_services__name__delete","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"confirm","in":"query","required":false,"schema":{"type":"boolean","description":"Confirm deletion","default":false,"title":"Confirm"},"description":"Confirm deletion"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/services/{name}/restart":{"post":{"tags":["cbinfra","config"],"summary":"Restart Service","description":"Restart a service.","operationId":"restart_service_api_config_services__name__restart_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/services/{name}/start":{"post":{"tags":["cbinfra","config"],"summary":"Start Service","description":"Start a service.","operationId":"start_service_api_config_services__name__start_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/services/{name}/stop":{"post":{"tags":["cbinfra","config"],"summary":"Stop Service","description":"Stop a service.","operationId":"stop_service_api_config_services__name__stop_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/sync":{"post":{"tags":["cbinfra","config"],"summary":"Sync Registry Endpoint","description":"Reconcile the registry with live host state.\n\nScans systemd (cb* units), NGINX sites-enabled, and letsencrypt live dirs,\nthen upserts the registry so the `/services`, `/nginx`, and `/certs`\nendpoints reflect reality. Safe to run repeatedly — only changes existing\nentries when they've drifted, and adds new entries for services that\nweren't previously tracked.\n\nReturns a summary of added/updated/skipped services.","operationId":"sync_registry_endpoint_api_config_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbinfra/api/config/systemd":{"get":{"tags":["cbinfra","config"],"summary":"List Systemd Units","description":"List all CB systemd units.","operationId":"list_systemd_units_api_config_systemd_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Response List Systemd Units Api Config Systemd Get"}}}}}}},"/cbinfra/api/config/systemd/reload":{"post":{"tags":["cbinfra","config"],"summary":"Reload Systemd","description":"Reload systemd daemon.","operationId":"reload_systemd_api_config_systemd_reload_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbinfra/api/config/systemd/{name}":{"get":{"tags":["cbinfra","config"],"summary":"Get Systemd Unit","description":"Get systemd unit details.","operationId":"get_systemd_unit_api_config_systemd__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SystemdUnit"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"delete":{"tags":["cbinfra","config"],"summary":"Delete Systemd Unit","description":"Delete systemd unit.","operationId":"delete_systemd_unit_api_config_systemd__name__delete","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"confirm","in":"query","required":false,"schema":{"type":"boolean","description":"Confirm deletion","default":false,"title":"Confirm"},"description":"Confirm deletion"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/config/systemd/{name}/status":{"post":{"tags":["cbinfra","config"],"summary":"Get Systemd Status","description":"Get detailed systemd service status.","operationId":"get_systemd_status_api_config_systemd__name__status_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/files/{service}":{"get":{"tags":["cbinfra","Files"],"summary":"List encrypted files for a service","description":"Returns plaintext metadata (path, content_type, size, sops_mode, sha256, updated_at) for every file in the service's `files/` dir. **No content is decrypted** — listing reads the plaintext manifest.","operationId":"list_files_api_files__service__get","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$","description":"Service slug","title":"Service"},"description":"Service slug"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__FileListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/files/{service}/{path}":{"get":{"tags":["cbinfra","Files"],"summary":"Decrypt and return file content","description":"Returns the decrypted plaintext bytes. Content-Type comes from the manifest entry recorded at upload time. **Audit-logged**.","operationId":"get_file_api_files__service___path__get","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$","description":"Service slug","title":"Service"},"description":"Service slug"},{"name":"path","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$","description":"Flat file name","title":"Path"},"description":"Flat file name"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}},"application/octet-stream":{}}},"404":{"description":"File not in manifest"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"post":{"tags":["cbinfra","Files"],"summary":"Encrypt and store a file","description":"Body is the plaintext file content. Content-Type drives the SOPS mode: `application/json`/`yaml` → structured mode (per-key encryption); everything else → binary mode (opaque whole-file). `.har` files force binary mode regardless of Content-Type. Hard cap: 50 MB plaintext. Audit-logged.","operationId":"put_file_api_files__service___path__post","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$","description":"Service slug","title":"Service"},"description":"Service slug"},{"name":"path","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$","description":"Flat file name","title":"Path"},"description":"Flat file name"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__FileUploadResponse"}}}},"413":{"description":"Body exceeds the 50 MB cap"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"delete":{"tags":["cbinfra","Files"],"summary":"Delete an encrypted file","description":"Removes the encrypted blob and its manifest entry. Audit-logged.","operationId":"delete_file_api_files__service___path__delete","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$","description":"Service slug","title":"Service"},"description":"Service slug"},{"name":"path","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$","description":"Flat file name","title":"Path"},"description":"Flat file name"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__FileDeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/github":{"get":{"tags":["cbinfra","issues"],"summary":"List Issues","description":"List issues for a project.\n\nExample: `/api/issues?project=cbinfra&state=open&limit=10`","operationId":"list_issues_api_github_get","parameters":[{"name":"project","in":"query","required":true,"schema":{"type":"string","description":"Project slug (e.g., 'cbinfra')","title":"Project"},"description":"Project slug (e.g., 'cbinfra')"},{"name":"state","in":"query","required":false,"schema":{"type":"string","description":"Issue state: open, closed, all","default":"open","title":"State"},"description":"Issue state: open, closed, all"},{"name":"labels","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated labels to filter by","title":"Labels"},"description":"Comma-separated labels to filter by"},{"name":"assignee","in":"query","required":false,"schema":{"type":"string","description":"Filter by assignee username","title":"Assignee"},"description":"Filter by assignee username"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max issues to return","default":30,"title":"Limit"},"description":"Max issues to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__IssueListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/github/projects":{"get":{"tags":["cbinfra","issues"],"summary":"List Projects","description":"List all CB projects available for issue management.","operationId":"list_projects_api_github_projects_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__ProjectListResponse"}}}}}}},"/cbinfra/api/github/{project}":{"post":{"tags":["cbinfra","issues"],"summary":"Create Issue","description":"Create a new issue.\n\nExample:\n```json\n{\n    \"title\": \"Bug: Something is broken\",\n    \"body\": \"Description of the issue\",\n    \"labels\": [\"bug\", \"priority:high\"]\n}\n```","operationId":"create_issue_api_github__project__post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__IssueCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/github/{project}/{number}":{"get":{"tags":["cbinfra","issues"],"summary":"Get Issue","description":"Get a single issue by number.","operationId":"get_issue_api_github__project___number__get","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"patch":{"tags":["cbinfra","issues"],"summary":"Update Issue","description":"Update an existing issue.\n\nExample:\n```json\n{\n    \"title\": \"Updated title\",\n    \"add_labels\": [\"in-progress\"],\n    \"remove_labels\": [\"needs-triage\"]\n}\n```","operationId":"update_issue_api_github__project___number__patch","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__IssueUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/github/{project}/{number}/close":{"post":{"tags":["cbinfra","issues"],"summary":"Close Issue","description":"Close an issue.\n\nExample:\n```json\n{\n    \"comment\": \"Fixed in PR #42\",\n    \"reason\": \"completed\"\n}\n```","operationId":"close_issue_api_github__project___number__close_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__IssueClose"},{"type":"null"}],"title":"Data"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/github/{project}/{number}/comments":{"get":{"tags":["cbinfra","issues"],"summary":"List Comments","description":"List comments on an issue.","operationId":"list_comments_api_github__project___number__comments_get","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__CommentListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"post":{"tags":["cbinfra","issues"],"summary":"Add Comment","description":"Add a comment to an issue.\n\nExample:\n```json\n{\n    \"body\": \"This is my comment\"\n}\n```","operationId":"add_comment_api_github__project___number__comments_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__CommentCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/github/{project}/{number}/reopen":{"post":{"tags":["cbinfra","issues"],"summary":"Reopen Issue","description":"Reopen a closed issue.","operationId":"reopen_issue_api_github__project___number__reopen_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__IssueReopen"},{"type":"null"}],"title":"Data"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/inventory":{"get":{"tags":["cbinfra","inventory"],"summary":"Full Inventory","description":"Get full inventory including system, devtools, and projects.\n\nExamples:\n- `/api/inventory` - Scan current directory only\n- `/api/inventory?path=/home/user/projects` - Scan specific directory\n- `/api/inventory?path=/home/user/projects&depth=1` - Scan all children\n- `/api/inventory?path=/home/user/projects&pattern=cb*&depth=1` - Scan children matching pattern","operationId":"full_inventory_api_inventory_get","parameters":[{"name":"path","in":"query","required":false,"schema":{"type":"string","description":"Base directory to scan (default: current working directory)","title":"Path"},"description":"Base directory to scan (default: current working directory)"},{"name":"pattern","in":"query","required":false,"schema":{"type":"string","description":"Glob pattern to filter child directories (e.g., 'cb*')","title":"Pattern"},"description":"Glob pattern to filter child directories (e.g., 'cb*')"},{"name":"depth","in":"query","required":false,"schema":{"type":"integer","maximum":1,"minimum":0,"description":"Scan depth: 0 = single directory, 1 = immediate children","default":0,"title":"Depth"},"description":"Scan depth: 0 = single directory, 1 = immediate children"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__InventoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/inventory/devtools":{"get":{"tags":["cbinfra","inventory"],"summary":"Devtools Info","description":"Get detected development tools.","operationId":"devtools_info_api_inventory_devtools_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__DevToolsInfo"}}}}}}},"/cbinfra/api/inventory/project":{"get":{"tags":["cbinfra","inventory"],"summary":"Project Info","description":"Get inventory for a single project directory.","operationId":"project_info_api_inventory_project_get","parameters":[{"name":"path","in":"query","required":true,"schema":{"type":"string","description":"Path to the project directory","title":"Path"},"description":"Path to the project directory"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__ProjectInventory"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/inventory/setup":{"get":{"tags":["cbinfra","inventory","setup"],"summary":"Setup Full","description":"Generate a shell script to set up both Python and Node.js.\n\nUsage:\n```bash\ncurl -s https://infra.campaignbrain.dev/api/inventory/setup | bash\n```","operationId":"setup_full_api_inventory_setup_get","parameters":[{"name":"python_env","in":"query","required":false,"schema":{"type":"string","description":"Name of the pyenv virtualenv","default":"nominates","title":"Python Env"},"description":"Name of the pyenv virtualenv"},{"name":"python","in":"query","required":false,"schema":{"type":"string","description":"Python version","default":"3.12.9","title":"Python"},"description":"Python version"},{"name":"node","in":"query","required":false,"schema":{"type":"string","description":"Node.js version","default":"24","title":"Node"},"description":"Node.js version"}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/inventory/setup/node":{"get":{"tags":["cbinfra","inventory","setup"],"summary":"Setup Node","description":"Generate a shell script to set up a Node.js environment.\n\nUsage:\n```bash\ncurl -s https://infra.campaignbrain.dev/api/inventory/setup/node | bash\ncurl -s \"https://infra.campaignbrain.dev/api/inventory/setup/node?version=22\" | bash\n```","operationId":"setup_node_api_inventory_setup_node_get","parameters":[{"name":"version","in":"query","required":false,"schema":{"type":"string","description":"Node.js version to install (e.g., '20', '22', 'lts')","default":"24","title":"Version"},"description":"Node.js version to install (e.g., '20', '22', 'lts')"},{"name":"path","in":"query","required":false,"schema":{"type":"string","description":"Project path to install dependencies from","title":"Path"},"description":"Project path to install dependencies from"},{"name":"deps","in":"query","required":false,"schema":{"type":"boolean","description":"Install dependencies from package.json","default":true,"title":"Deps"},"description":"Install dependencies from package.json"}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/inventory/setup/python":{"get":{"tags":["cbinfra","inventory","setup"],"summary":"Setup Python","description":"Generate a shell script to set up a Python environment.\n\nUsage:\n```bash\ncurl -s https://infra.campaignbrain.dev/api/inventory/setup/python | bash\ncurl -s \"https://infra.campaignbrain.dev/api/inventory/setup/python?env=myenv&python=3.12.9\" | bash\n```","operationId":"setup_python_api_inventory_setup_python_get","parameters":[{"name":"env","in":"query","required":false,"schema":{"type":"string","description":"Name of the pyenv virtualenv to create/use","default":"nominates","title":"Env"},"description":"Name of the pyenv virtualenv to create/use"},{"name":"python","in":"query","required":false,"schema":{"type":"string","description":"Python version to install","default":"3.12.9","title":"Python"},"description":"Python version to install"},{"name":"path","in":"query","required":false,"schema":{"type":"string","description":"Project path to install dependencies from","title":"Path"},"description":"Project path to install dependencies from"},{"name":"deps","in":"query","required":false,"schema":{"type":"boolean","description":"Install dependencies from pyproject.toml or requirements.txt","default":true,"title":"Deps"},"description":"Install dependencies from pyproject.toml or requirements.txt"}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/inventory/system":{"get":{"tags":["cbinfra","inventory"],"summary":"System Info","description":"Get system and OS information.","operationId":"system_info_api_inventory_system_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SystemInfo"}}}}}}},"/cbinfra/api/orchestrate/cbos/status":{"get":{"tags":["cbinfra","orchestrate"],"summary":"Cbos Status","description":"Is cbos reachable, and can THIS client talk to it?\n\nTwo different questions. Reporting one answer for both is how a live hub\ngets called dead: a WebSocket-only cbos is reachable and NOT usable by\nthese HTTP methods, so say exactly that.","operationId":"cbos_status_api_orchestrate_cbos_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbinfra/api/orchestrate/generate-claude-md":{"post":{"tags":["cbinfra","orchestrate"],"summary":"Generate Claude Md","description":"Generate CLAUDE.md content from seed prompt.\n\nUses ai.campaignbrain.dev to generate a comprehensive CLAUDE.md file\nbased on the project name, description, and optional structure.","operationId":"generate_claude_md_api_orchestrate_generate_claude_md_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__GenerateClaudeMdRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__GenerateClaudeMdResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/orchestrate/launch":{"post":{"tags":["cbinfra","orchestrate"],"summary":"Launch Orchestration","description":"Launch full project orchestration.\n\n1. Bootstrap project (GitHub repo + local clone + scaffolding)\n2. Generate CLAUDE.md from seed prompt\n3. Launch Claude Code session via cbos\n4. Send initial prompt (interactive or auto mode)\n\nReturns session info for monitoring progress.","operationId":"launch_orchestration_api_orchestrate_launch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__OrchestrateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__OrchestrateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/orchestrate/sessions/{session_id}":{"get":{"tags":["cbinfra","orchestrate"],"summary":"Get Session Status","description":"Get status of a cbos session.","operationId":"get_session_status_api_orchestrate_sessions__session_id__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SessionStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"delete":{"tags":["cbinfra","orchestrate"],"summary":"Kill Session","description":"Kill a cbos session.","operationId":"kill_session_api_orchestrate_sessions__session_id__delete","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/orchestrate/sessions/{session_id}/abort":{"post":{"tags":["cbinfra","orchestrate"],"summary":"Abort Session","description":"Abort/interrupt a session.","operationId":"abort_session_api_orchestrate_sessions__session_id__abort_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/orchestrate/sessions/{session_id}/approve":{"post":{"tags":["cbinfra","orchestrate"],"summary":"Approve Session","description":"Approve pending action in session (send 'y').","operationId":"approve_session_api_orchestrate_sessions__session_id__approve_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/orchestrate/sessions/{session_id}/send":{"post":{"tags":["cbinfra","orchestrate"],"summary":"Send To Session","description":"Send input to a cbos session.","operationId":"send_to_session_api_orchestrate_sessions__session_id__send_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"text","in":"query","required":true,"schema":{"type":"string","title":"Text"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/releases/{product}/channel":{"get":{"tags":["cbinfra","releases"],"summary":"What a channel points at, resolved to its release — the updater's one read","operationId":"channel_api_releases__product__channel_get","parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,31}$","description":"e.g. cbcli","title":"Product"},"description":"e.g. cbcli"},{"name":"name","in":"query","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,15}$","description":"e.g. stable","title":"Name"},"description":"e.g. stable"},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__ChannelRelease"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/releases/{product}/download":{"get":{"tags":["cbinfra","releases"],"summary":"Download an artifact the manifest names (wheel or boot script)","operationId":"download_api_releases__product__download_get","parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,31}$","description":"e.g. cbcli","title":"Product"},"description":"e.g. cbcli"},{"name":"version","in":"query","required":true,"schema":{"type":"string","pattern":"^\\d+\\.\\d+\\.\\d+$","description":"e.g. 0.23.1","title":"Version"},"description":"e.g. 0.23.1"},{"name":"file","in":"query","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$","description":"exactly as the manifest names it","title":"File"},"description":"exactly as the manifest names it"},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/releases/{product}/manifest":{"get":{"tags":["cbinfra","releases"],"summary":"One version's manifest: git sha, artifacts, checksums","operationId":"manifest_api_releases__product__manifest_get","parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,31}$","description":"e.g. cbcli","title":"Product"},"description":"e.g. cbcli"},{"name":"version","in":"query","required":true,"schema":{"type":"string","pattern":"^\\d+\\.\\d+\\.\\d+$","description":"e.g. 0.23.1","title":"Version"},"description":"e.g. 0.23.1"},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__ReleaseManifest"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/releases/{product}/promote":{"post":{"tags":["cbinfra","releases"],"summary":"Promote: point a channel at a built version (the rollout decision)","operationId":"promote_api_releases__product__promote_post","parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,31}$","description":"e.g. cbcli","title":"Product"},"description":"e.g. cbcli"},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__PromoteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__ChannelInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/releases/{product}/versions":{"get":{"tags":["cbinfra","releases"],"summary":"Every built version and every channel for a product","operationId":"versions_api_releases__product__versions_get","parameters":[{"name":"product","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,31}$","description":"e.g. cbcli","title":"Product"},"description":"e.g. cbcli"},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__ProductSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/secrets":{"get":{"tags":["cbinfra","Secrets"],"summary":"List encrypted secret files","description":"Returns file paths and sizes — no secret values are exposed.","operationId":"list_secrets_api_secrets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbinfra__SecretFile"},"type":"array","title":"Response List Secrets Api Secrets Get"}}}}}}},"/cbinfra/api/secrets/verify":{"post":{"tags":["cbinfra","Secrets"],"summary":"Verify all secret files decrypt","description":"Attempts to decrypt every encrypted file and reports pass/fail per file. Does NOT return any secret values — only checks that decryption succeeds.","operationId":"verify_all_secrets_api_secrets_verify_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SecretVerifyResponse"}}}}}}},"/cbinfra/api/secrets/{service}":{"get":{"tags":["cbinfra","Secrets"],"summary":"List key names for a service","description":"Decrypts the file to extract key names only — values are NOT returned. Use GET /secrets/{service}/decrypt for values.","operationId":"list_service_keys_api_secrets__service__get","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$","description":"Service slug","title":"Service"},"description":"Service slug"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SecretKeys"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"post":{"tags":["cbinfra","Secrets"],"summary":"Add or update a secret","description":"Encrypts and stores a KEY=VALUE pair in the service's SOPS file. Creates the file if it doesn't exist. Audit-logged.","operationId":"add_secret_api_secrets__service__post","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$","description":"Service slug","title":"Service"},"description":"Service slug"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SecretAddRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SecretAddResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/secrets/{service}/decrypt":{"get":{"tags":["cbinfra","Secrets"],"summary":"Decrypt secret values","description":"**Returns plaintext secret values.** Every call is audit-logged with the caller's identity. Use sparingly — prefer /secrets/{service} (key names only) when you just need to check what's configured.","operationId":"decrypt_service_secrets_api_secrets__service__decrypt_get","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$","description":"Service slug","title":"Service"},"description":"Service slug"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SecretValues"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/secrets/{service}/{key}":{"delete":{"tags":["cbinfra","Secrets"],"summary":"Remove a secret key","description":"Removes a single KEY from the service's SOPS file (decrypt, drop, re-encrypt in place). 404 if the service has no secret file or the key is not present. Audit-logged. The sysforge VaultClient's delete_secret() calls this route.","operationId":"remove_secret_api_secrets__service___key__delete","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z][a-z0-9_-]{0,63}$","description":"Service slug","title":"Service"},"description":"Service slug"},{"name":"key","in":"path","required":true,"schema":{"type":"string","maxLength":255,"pattern":"^[A-Z][A-Z0-9_]*$","description":"Env var name","title":"Key"},"description":"Env var name"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SecretRemoveResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/shared-memory/":{"get":{"tags":["cbinfra","shared-memory"],"summary":"List Files","description":"List canonical .md files — every scope by default, one scope with `?scope=`.","operationId":"list_files_api_shared_memory__get","parameters":[{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`fleet`, a family id, or omit for all","title":"Scope"},"description":"`fleet`, a family id, or omit for all"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbinfra__FileSummary"},"title":"Response List Files Api Shared Memory  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"post":{"tags":["cbinfra","shared-memory"],"summary":"Create File","description":"Create a new canonical file in `scope` (default fleet). 409 if the name exists in ANY scope.","operationId":"create_file_api_shared_memory__post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__FileCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__WriteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/shared-memory/effective":{"get":{"tags":["cbinfra","shared-memory"],"summary":"Effective","description":"Everything that applies to ONE project: fleet files + the files of every family it belongs to.\n\nThis is the question an agent (or the Apex Hyper Params port) actually asks:\n\"what are my marching orders here?\"","operationId":"effective_api_shared_memory_effective_get","parameters":[{"name":"project","in":"query","required":true,"schema":{"type":"string","description":"A project path (`~/projects/nominate/cbapex/cbsvi`) or its Claude Code slug","title":"Project"},"description":"A project path (`~/projects/nominate/cbapex/cbsvi`) or its Claude Code slug"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__EffectiveResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/shared-memory/families":{"get":{"tags":["cbinfra","shared-memory"],"summary":"List Families","description":"The scope tree: fleet file count + every family with its resolved members.","operationId":"list_families_api_shared_memory_families_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__ScopeTree"}}}}}}},"/cbinfra/api/shared-memory/families/{family_id}":{"get":{"tags":["cbinfra","shared-memory"],"summary":"Get Family","operationId":"get_family_api_shared_memory_families__family_id__get","parameters":[{"name":"family_id","in":"path","required":true,"schema":{"type":"string","title":"Family Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__Family"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"put":{"tags":["cbinfra","shared-memory"],"summary":"Put Family","description":"Create or replace a family definition. Membership = `paths` globs, resolved on disk.\n\nCreates `family/<id>/` so files can be added. Commits + syncs, so a project\nthat just became a member gets its pointer line on this call.","operationId":"put_family_api_shared_memory_families__family_id__put","parameters":[{"name":"family_id","in":"path","required":true,"schema":{"type":"string","title":"Family Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__FamilyDef"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__Family"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"delete":{"tags":["cbinfra","shared-memory"],"summary":"Delete Family","description":"Remove a family definition. 409 while it still holds files.","operationId":"delete_family_api_shared_memory_families__family_id__delete","parameters":[{"name":"family_id","in":"path","required":true,"schema":{"type":"string","title":"Family Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Family Api Shared Memory Families  Family Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/shared-memory/sync":{"post":{"tags":["cbinfra","shared-memory"],"summary":"Trigger Sync","description":"Run sync-shared-memory.py now (rather than waiting for cbcron's 15-min tick).","operationId":"trigger_sync_api_shared_memory_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SyncResponse"}}}}}}},"/cbinfra/api/shared-memory/{name}":{"get":{"tags":["cbinfra","shared-memory"],"summary":"Get File","description":"Read a canonical file's content + parsed frontmatter + last commit, from whichever scope holds it.","operationId":"get_file_api_shared_memory__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__FileDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"put":{"tags":["cbinfra","shared-memory"],"summary":"Update File","description":"Replace a canonical's content; with `scope` set, also move it between tiers.\n\nA move is a delete-here + create-there in one commit; the sync then prunes\nthe old symlinks from projects that lost it and links it where it now applies.","operationId":"update_file_api_shared_memory__name__put","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__FileUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__WriteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}},"delete":{"tags":["cbinfra","shared-memory"],"summary":"Delete File","description":"Delete a canonical file. Sync prunes its symlinks from project dirs.","operationId":"delete_file_api_shared_memory__name__delete","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete File Api Shared Memory  Name  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/traffic/rollup":{"post":{"tags":["cbinfra","traffic"],"summary":"Rollup","description":"Parse the day's access log, compute basic stats, and record them. Idempotent.","operationId":"rollup_api_traffic_rollup_post","parameters":[{"name":"site","in":"query","required":false,"schema":{"type":"string","description":"vhost whose /var/log/nginx/<site>.access.log to parse","default":"campaignbrain.dev","title":"Site"},"description":"vhost whose /var/log/nginx/<site>.access.log to parse"},{"name":"day","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD; default = yesterday (UTC)","title":"Day"},"description":"YYYY-MM-DD; default = yesterday (UTC)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__RollupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/api/traffic/summary":{"get":{"tags":["cbinfra","traffic"],"summary":"Summary","description":"Recent daily rows plus cross-day totals for a site.","operationId":"summary_api_traffic_summary_get","parameters":[{"name":"site","in":"query","required":false,"schema":{"type":"string","default":"campaignbrain.dev","title":"Site"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"how many recent daily rows to roll up","default":30,"title":"Days"},"description":"how many recent daily rows to roll up"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__SummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/health":{"get":{"tags":["cbinfra","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HealthResponse"}}}}}}},"/cbinfra/health/detailed":{"get":{"tags":["cbinfra","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HealthResponse"}}}}}}},"/cbinfra/prime":{"get":{"tags":["cbinfra","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__PrimeResponse"}}}}}}},"/cbinfra/status":{"get":{"tags":["cbinfra","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__StatusResponse"}}}}}}},"/cbinfra/toolbench/":{"get":{"tags":["cbinfra","Toolbench"],"summary":"Toolbench Ui","description":"Mobile-first toolbench UI.","operationId":"toolbench_ui_toolbench__get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/cbinfra/toolbench/api/services":{"get":{"tags":["cbinfra","Toolbench"],"summary":"List Services","description":"List all services with status.","operationId":"list_services_toolbench_api_services_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbinfra/toolbench/api/services/{name}":{"get":{"tags":["cbinfra","Toolbench"],"summary":"Get Service Detail","description":"Get service details.","operationId":"get_service_detail_toolbench_api_services__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/toolbench/api/services/{name}/logs":{"get":{"tags":["cbinfra","Toolbench"],"summary":"Get Service Logs","description":"Get recent logs for a service.","operationId":"get_service_logs_toolbench_api_services__name__logs_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"lines","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Lines"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbinfra/toolbench/api/services/{name}/restart":{"post":{"tags":["cbinfra","Toolbench"],"summary":"Restart Service","description":"Restart a service.","operationId":"restart_service_toolbench_api_services__name__restart_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbinfra__HTTPValidationError"}}}}}}},"/cbingest/api/v1/classify":{"post":{"tags":["cbingest","CBINGEST"],"summary":"Classify a document and generate extraction prompt","description":"Send OCR text to Opus for classification.\n\nReturns document type, vendor name, and a custom extraction prompt\noptimized for this specific document format. Use the extraction_prompt\nas the `context` parameter when calling /api/v1/extract.","operationId":"classify_api_v1_classify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__ClassifyRequest"}}},"required":true},"responses":{"200":{"description":"Document type, vendor, and a custom extraction prompt for this format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__ClassifyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__HTTPValidationError"}}}}}}},"/cbingest/api/v1/extract":{"post":{"tags":["cbingest","CBINGEST"],"summary":"Extract text and structured data from a document","description":"Process a document through the full extraction pipeline.\n\n**Pipeline steps:**\n\n1. **OCR** — auto-routes to the correct provider based on file type\n   (PDF → Mistral, image → Pixtral, Office → direct/Unstructured, text → direct decode)\n2. **Schema extraction** — if `extraction_schema` is provided, sends OCR text\n   to Claude via cbai to extract structured JSON matching the schema\n3. **Entity resolution** — if `entity_keys` is provided, normalizes fuzzy\n   extracted names to canonical values (case-insensitive matching)\n\nSupports `content_base64` (inline) or `content_url` (fetched server-side,\nwith optional `fetch_headers` for authenticated sources).","operationId":"extract_api_v1_extract_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__ExtractRequest"}}},"required":true},"responses":{"200":{"description":"Extracted text, optional structured data, provider info, and timing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__ExtractResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__HTTPValidationError"}}}}}}},"/cbingest/api/v1/extract/batch":{"post":{"tags":["cbingest","CBINGEST"],"summary":"Extract multiple documents concurrently","description":"Process up to 20 documents concurrently with shared schema and entity keys.\n\nDocuments are processed in parallel (max 5 concurrent). Each document gets\nits own result entry — failures are independent.","operationId":"extract_batch_api_v1_extract_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__BatchRequest"}}},"required":true},"responses":{"200":{"description":"Individual results per document.","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__HTTPValidationError"}}}}}}},"/cbingest/api/v1/extract/health":{"get":{"tags":["cbingest","CBINGEST"],"summary":"Deep health check for the extraction pipeline","description":"Verify the upstream (cbai) is actually reachable.\n\nUnlike `/health` which only reports process liveness, this endpoint\npings the upstream so callers can distinguish 'extract path broken'\nfrom 'document was bad'.\n\ncbai is the only component probed: every OCR and extraction call leaves\ncbingest through it (infra#250), including the fallback. The retired\ndirect-ollama probe outlived the provider it monitored and reported this\npipeline \"degraded\" whenever the GPU host was unreachable — a 503 for a\ndependency the extract path no longer has.","operationId":"extract_health_api_v1_extract_health_get","responses":{"200":{"description":"Status of each upstream the extract endpoint depends on.","content":{"application/json":{"schema":{}}}}}}},"/cbingest/api/v1/hello":{"get":{"tags":["cbingest","CBINGEST"],"summary":"Hello world","description":"Return a greeting to confirm the service is running.","operationId":"hello_api_v1_hello_get","responses":{"200":{"description":"A greeting message confirming the service is reachable.","content":{"application/json":{"schema":{}}}}}}},"/cbingest/api/v1/jobs":{"post":{"tags":["cbingest","CBINGEST"],"summary":"Submit an async extraction job","description":"Submit a document for background extraction processing.\n\nReturns a `job_id` immediately with status `pending`. The extraction\nruns asynchronously — poll `GET /api/v1/jobs/{job_id}` for results.\nUse this for large documents or batch processing where synchronous\nextraction would time out.","operationId":"submit_job_api_v1_jobs_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__ExtractRequest"}}}},"responses":{"202":{"description":"Job ID and initial pending status.","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__HTTPValidationError"}}}}}},"get":{"tags":["cbingest","CBINGEST"],"summary":"List all extraction jobs","description":"List all extraction jobs, newest first.\n\nFilter by status with `?status=completed`. Paginate with `?limit=25&offset=0`.","operationId":"list_jobs_api_v1_jobs_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Paginated list of jobs with total count.","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__HTTPValidationError"}}}}}}},"/cbingest/api/v1/jobs/{job_id}":{"get":{"tags":["cbingest","CBINGEST"],"summary":"Poll async job status and result","description":"Poll for the status and result of an async extraction job.\n\nReturns the full job record including status (`pending`, `processing`,\n`completed`, `failed`), timestamps, and the extraction result when complete.\nReturns 404 if the job ID is not found.","operationId":"get_job_api_v1_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Job status, timestamps, and result (when completed).","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__HTTPValidationError"}}}}}}},"/cbingest/api/v1/providers":{"get":{"tags":["cbingest","CBINGEST"],"summary":"List available OCR providers","description":"List all configured document processing providers.","operationId":"list_providers_api_v1_providers_get","responses":{"200":{"description":"Provider names, descriptions, and supported file types.","content":{"application/json":{"schema":{}}}}}}},"/cbingest/api/v1/stats":{"get":{"tags":["cbingest","CBINGEST"],"summary":"Dashboard metrics","description":"Return aggregate job statistics for the admin dashboard.","operationId":"get_stats_api_v1_stats_get","responses":{"200":{"description":"Job counts by status and recent job summaries.","content":{"application/json":{"schema":{}}}}}}},"/cbingest/health":{"get":{"tags":["cbingest","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__HealthResponse"}}}}}}},"/cbingest/health/detailed":{"get":{"tags":["cbingest","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__HealthResponse"}}}}}}},"/cbingest/prime":{"get":{"tags":["cbingest","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__PrimeResponse"}}}}}}},"/cbingest/status":{"get":{"tags":["cbingest","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbingest__StatusResponse"}}}}}}},"/cbintel/":{"get":{"tags":["cbintel","Health"],"summary":"Root","description":"API root with service discovery information.","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbintel/api/v1/assets":{"post":{"tags":["cbintel","Releases"],"summary":"Register Asset","description":"Register a supplied asset (bound or unbound). Idempotent on sha256 —\na re-registration returns the existing row (200 semantics in body via\n``created``). cbscan's post-land hook calls this with origin=cbscan.","operationId":"register_asset_api_v1_assets_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__RegisterAssetRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Register Asset Api V1 Assets Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"get":{"tags":["cbintel","Releases"],"summary":"List All Assets","description":"Every supplied asset across all publications — the Sources screen.\n\nOptional exact-match filters: slug, kind, origin, sensitivity.","operationId":"list_all_assets_api_v1_assets_get","parameters":[{"name":"slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"}},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"}},{"name":"origin","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Origin"}},{"name":"sensitivity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sensitivity"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List All Assets Api V1 Assets Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/assets/intake":{"get":{"tags":["cbintel","Releases"],"summary":"Intake","description":"Unbound arrivals awaiting triage (bind to a publication).","operationId":"intake_api_v1_assets_intake_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Intake Api V1 Assets Intake Get"}}}}}}},"/cbintel/api/v1/assets/{asset_id}":{"delete":{"tags":["cbintel","Releases"],"summary":"Delete Asset","operationId":"delete_asset_api_v1_assets__asset_id__delete","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","title":"Asset Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/assets/{asset_id}/bind":{"post":{"tags":["cbintel","Releases"],"summary":"Bind Asset","description":"Attach an intake arrival to a publication. 409 if bound elsewhere.","operationId":"bind_asset_api_v1_assets__asset_id__bind_post","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","title":"Asset Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__BindAssetRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Bind Asset Api V1 Assets  Asset Id  Bind Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/auctions/states":{"get":{"tags":["cbintel","Auctions"],"summary":"List States","description":"States covered by the auctions dataset + a top-line rollup for each.","operationId":"list_states_api_v1_auctions_states_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List States Api V1 Auctions States Get"}}}}}}},"/cbintel/api/v1/auctions/{state}/counties":{"get":{"tags":["cbintel","Auctions"],"summary":"Counties","description":"Counties with source coverage + sale-day counts. Default sort = coverage\ncompleteness desc (the all-5-gov counties float to the top).","operationId":"counties_api_v1_auctions__state__counties_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"substring match on county name/slug","title":"Q"},"description":"substring match on county name/slug"},{"name":"platform","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"RealForeclose | RealTaxDeed | clerk_native","title":"Platform"},"description":"RealForeclose | RealTaxDeed | clerk_native"},{"name":"has_portal","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"only counties with (or without) a foreclosure/tax-deed portal","title":"Has Portal"},"description":"only counties with (or without) a foreclosure/tax-deed portal"},{"name":"min_confidence","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"floor on EVERY resolved source: high | low | best_effort","title":"Min Confidence"},"description":"floor on EVERY resolved source: high | low | best_effort"},{"name":"missing","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"only counties missing this source type","title":"Missing"},"description":"only counties missing this source type"},{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"coverage | name | sale_days","default":"coverage","title":"Sort"},"description":"coverage | name | sale_days"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Counties Api V1 Auctions  State  Counties Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/auctions/{state}/counties/{slug}":{"get":{"tags":["cbintel","Auctions"],"summary":"County Detail","description":"One county — full sources (with review flags on low-confidence picks),\ncoverage summary, and upcoming sale dates with derived preview links.","operationId":"county_detail_api_v1_auctions__state__counties__slug__get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response County Detail Api V1 Auctions  State  Counties  Slug  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/auctions/{state}/domain":{"get":{"tags":["cbintel","Auctions"],"summary":"Domain Model","description":"The domain model — auction types, responsible offices, platforms,\nstatewide resources, and the model's own open questions.","operationId":"domain_model_api_v1_auctions__state__domain_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Domain Model Api V1 Auctions  State  Domain Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/auctions/{state}/domain/brief":{"get":{"tags":["cbintel","Auctions"],"summary":"Domain Brief","description":"The rendered-elsewhere markdown primer (raw text + provenance).","operationId":"domain_brief_api_v1_auctions__state__domain_brief_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Domain Brief Api V1 Auctions  State  Domain Brief Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/auctions/{state}/listings":{"get":{"tags":["cbintel","Auctions"],"summary":"Listings","description":"🟡 Phase C.2 — the harvested auction-records browser. Returns an honest\nempty payload (with the disposition vocabulary) until the\n``fl_property_auctions`` catalog is populated.","operationId":"listings_api_v1_auctions__state__listings_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"county","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"}},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"}},{"name":"disposition","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disposition"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Listings Api V1 Auctions  State  Listings Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/auctions/{state}/listings/{key}":{"get":{"tags":["cbintel","Auctions"],"summary":"Listing Detail","description":"🟡 Phase C.2 — one auction full record (key = case# or AID).","operationId":"listing_detail_api_v1_auctions__state__listings__key__get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Listing Detail Api V1 Auctions  State  Listings  Key  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/auctions/{state}/overview":{"get":{"tags":["cbintel","Auctions"],"summary":"Overview","description":"Dashboard payload for the Overview view — headline tiles, the 67×6\ncoverage matrix, platform breakdown, schedule density, and provenance.","operationId":"overview_api_v1_auctions__state__overview_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Overview Api V1 Auctions  State  Overview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/auctions/{state}/schedule":{"get":{"tags":["cbintel","Auctions"],"summary":"Sale Schedule","description":"Flattened sale-day calendar: one entry per date → the counties holding a\nsale that day, each with a live PREVIEW deep-link.","operationId":"sale_schedule_api_v1_auctions__state__schedule_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"county","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"filter to one county slug","title":"County"},"description":"filter to one county slug"},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"inclusive MM/DD/YYYY lower bound","title":"From"},"description":"inclusive MM/DD/YYYY lower bound"},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"inclusive MM/DD/YYYY upper bound","title":"To"},"description":"inclusive MM/DD/YYYY upper bound"},{"name":"platform","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"RealForeclose | RealTaxDeed","title":"Platform"},"description":"RealForeclose | RealTaxDeed"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Sale Schedule Api V1 Auctions  State  Schedule Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/auctions/{state}/stats":{"get":{"tags":["cbintel","Auctions"],"summary":"Stats","description":"Just the rollup numbers — cheap enough to poll for the tiles.","operationId":"stats_api_v1_auctions__state__stats_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Stats Api V1 Auctions  State  Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/colors":{"post":{"tags":["cbintel","images"],"summary":"Extract dominant colors from an image (deterministic)","description":"Pillow MEDIANCUT quantization. Same input → same output, every run.\n\nResizes to 256px on the long edge, quantizes to ``_QUANTIZE_BUCKETS``\npalette entries, counts pixels per bucket, returns the top N colors\nby coverage percentage.","operationId":"extract_colors_api_v1_colors_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ColorsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ColorsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/corpus/documents/{doc_id}":{"get":{"tags":["cbintel","Corpus Index"],"summary":"One document + its version history","operationId":"get_document_api_v1_corpus_documents__doc_id__get","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}},{"name":"catalog","in":"query","required":false,"schema":{"type":"string","default":"mi_elections_index","title":"Catalog"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Document Api V1 Corpus Documents  Doc Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/corpus/jurisdiction/{state}/{county}":{"get":{"tags":["cbintel","Corpus Index"],"summary":"A jurisdiction's evolving temporal profile (cbscout)","description":"Reads the cbscout 'place' entity that every indexed document for this jurisdiction accretes into — its evolving Category summaries + context. This is the temporal plane: what the rules mean and how they've changed. Pass ?municipality= for a township/city; omit for the county; pass county=_ for the statewide entity. Returns 404 if nothing memorized yet.","operationId":"jurisdiction_api_v1_corpus_jurisdiction__state___county__get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"county","in":"path","required":true,"schema":{"type":"string","title":"County"}},{"name":"municipality","in":"query","required":false,"schema":{"type":"string","description":"Township/city (optional).","default":"","title":"Municipality"},"description":"Township/city (optional)."},{"name":"catalog","in":"query","required":false,"schema":{"type":"string","description":"Catalog name.","default":"mi_elections_index","title":"Catalog"},"description":"Catalog name."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Jurisdiction Api V1 Corpus Jurisdiction  State   County  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/corpus/search":{"get":{"tags":["cbintel","Corpus Index"],"summary":"Semantic search across the corpus","description":"Embeds the query and searches the vectl store (shared across flavors by default, or a single flavor's store with `flavor=`), then resolves each hit to its current document row in the cbdatabase catalog (citation + Wayback archive_url + is_current).","operationId":"search_api_v1_corpus_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Natural-language query.","title":"Q"},"description":"Natural-language query."},{"name":"flavor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict to one flavor; omit for cross-flavor.","title":"Flavor"},"description":"Restrict to one flavor; omit for cross-flavor."},{"name":"catalog","in":"query","required":false,"schema":{"type":"string","description":"Catalog name.","default":"mi_elections_index","title":"Catalog"},"description":"Catalog name."},{"name":"corpus","in":"query","required":false,"schema":{"type":"string","description":"Corpus tag.","default":"mi","title":"Corpus"},"description":"Corpus tag."},{"name":"k","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Max hits.","default":10,"title":"K"},"description":"Max hits."},{"name":"min_score","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"default":0.3,"title":"Min Score"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__cbintel__corpus__api__SearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/corpus/stats":{"get":{"tags":["cbintel","Corpus Index"],"summary":"Catalog rollup","description":"Document/version/chunk/run counts and a by-flavor breakdown.","operationId":"stats_api_v1_corpus_stats_get","parameters":[{"name":"catalog","in":"query","required":false,"schema":{"type":"string","default":"mi_elections_index","title":"Catalog"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Stats Api V1 Corpus Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/crop":{"post":{"tags":["cbintel","images"],"summary":"Crop a bbox from an image","description":"Crop ``bbox`` from ``image_b64``. Returns base64 PNG bytes.\n\nrembg (background removal) is reserved for V2.1 and currently 501s.","operationId":"crop_image_api_v1_crop_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CropRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CropResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/documents":{"post":{"tags":["cbintel","documents"],"summary":"Document Pipeline","description":"Run full document processing pipeline.\n\nExecutes the complete pipeline: detect → process → analyze\n\nThis is the recommended endpoint for most use cases. It automatically:\n1. Detects document type and selects optimal provider\n2. Extracts text and images using appropriate OCR\n3. Analyzes content for entities, summary, and topics\n\n**Accepts:**\n- `url`: URL to fetch document from (recommended for files.campaignbrain.dev)\n- `file_path`: Local file path\n\n**Returns:**\nCombined results from detection, processing, and analysis.\n\n**Example:**\n```json\n{\n  \"url\": \"https://files.campaignbrain.dev/docs/investigation-report.pdf\",\n  \"extract_entities\": true,\n  \"extract_summary\": true,\n  \"max_summary_length\": 250\n}\n```\n\n**CLI Usage (via cbcli):**\n```bash\ncbcli intel documents create --params '{\"url\": \"https://files.campaignbrain.dev/docs/report.pdf\"}'\n```","operationId":"document_pipeline_api_v1_documents_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__cbintel__jobs__routers__documents__PipelineRequest"}}},"required":true},"responses":{"200":{"description":"Full pipeline completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__PipelineResponse"},"example":{"detected_type":"pdf","mime_type":"application/pdf","provider":"mistral","confidence":0.99,"text":"Quarterly Report Q4 2025\n\nRevenue increased 15%...","images":[],"pages":12,"word_count":3420,"processing_strategy":"mistral_ocr","title":"Quarterly Financial Report Q4 2025","summary":"Revenue increased 15% year-over-year.","entities":[{"text":"Nominate AI","type":"organization","confidence":0.95}],"topics":["financial performance","market expansion"],"document_type":"report","key_sections":[],"file_path":"/tmp/cbintel_doc_abc123.pdf"}}}},"400":{"description":"Invalid URL or file path"},"415":{"description":"Unsupported document format"},"422":{"description":"Validation error"},"502":{"description":"Failed to download or process document"},"504":{"description":"Pipeline timeout"}}}},"/cbintel/api/v1/documents/analyze":{"post":{"tags":["cbintel","documents"],"summary":"Analyze Document","description":"Analyze document to extract entities, summary, and topics.\n\nPerforms intelligent analysis including:\n- Named entity extraction (people, organizations, locations)\n- Document summarization\n- Topic extraction\n- Document type classification\n\n**Accepts:**\n- `url`: URL to fetch document from\n- `file_path`: Local file path\n- `text`: Pre-extracted text (skips detection/processing)\n\n**Returns:**\n- `title`: Inferred document title\n- `summary`: Generated summary\n- `entities`: Extracted named entities with confidence scores\n- `topics`: Main topics/themes\n- `document_type`: Classification (report, article, memo, etc.)\n\n**Example:**\n```json\n{\n  \"url\": \"https://files.campaignbrain.dev/docs/annual-report.pdf\",\n  \"extract_entities\": true,\n  \"entity_types\": [\"person\", \"organization\"],\n  \"max_summary_length\": 300\n}\n```","operationId":"analyze_document_api_v1_documents_analyze_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__AnalyzeRequest"}}},"required":true},"responses":{"200":{"description":"Document analyzed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__AnalyzeResponse"},"example":{"title":"Quarterly Financial Report Q4 2025","summary":"Revenue increased 15% year-over-year driven by expansion into new markets.","entities":[{"text":"Nominate AI","type":"organization","confidence":0.95,"context":"Nominate AI reported..."},{"text":"John Smith","type":"person","confidence":0.88,"context":"CEO John Smith stated..."}],"topics":["financial performance","market expansion","AI technology"],"document_type":"report","key_sections":[{"title":"Executive Summary","page":1}],"file_path":"/tmp/cbintel_doc_abc123.pdf","word_count":3420}}}},"400":{"description":"Invalid input — no URL, file path, or text provided"},"422":{"description":"Validation error"},"502":{"description":"Failed to download or analyze document"},"504":{"description":"Analysis timeout"}}}},"/cbintel/api/v1/documents/detect":{"post":{"tags":["cbintel","documents"],"summary":"Detect Document Type","description":"Detect document type and select optimal processing strategy.\n\nAnalyzes the document using magic bytes, file extension, or AI\nto determine the file type and appropriate OCR provider.\n\n**Accepts:**\n- `url`: URL to fetch document from (e.g., https://files.campaignbrain.dev/docs/report.pdf)\n- `file_path`: Local file path\n\n**Returns:**\n- `detected_type`: pdf, image, office, text, html, or unknown\n- `provider`: mistral (PDFs/images), unstructured (Office), or direct (text)\n- `confidence`: 0.0-1.0 detection confidence\n\n**Example:**\n```json\n{\"url\": \"https://files.campaignbrain.dev/docs/quarterly-report.pdf\"}\n```","operationId":"detect_document_type_api_v1_documents_detect_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__DetectRequest"}}},"required":true},"responses":{"200":{"description":"Document type detected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__DetectResponse"},"example":{"detected_type":"pdf","mime_type":"application/pdf","provider":"mistral","confidence":0.99,"detection_method":"magic_bytes","file_info":{"size_bytes":245760,"pages":12},"file_path":"/tmp/cbintel_doc_abc123.pdf"}}}},"400":{"description":"Invalid URL or file path","content":{"application/json":{"example":{"detail":"Invalid URL: not-a-url"}}}},"415":{"description":"Unsupported document format","content":{"application/json":{"example":{"detail":"Unsupported file format: .xyz"}}}},"422":{"description":"Validation error"},"502":{"description":"Failed to download document","content":{"application/json":{"example":{"detail":"Failed to download https://...: HTTP 503"}}}}}}},"/cbintel/api/v1/documents/process":{"post":{"tags":["cbintel","documents"],"summary":"Process Document","description":"Extract content from document using optimal provider.\n\nProcesses the document to extract text, images, and tables\nusing OCR or direct extraction based on detected type.\n\n**Accepts:**\n- `url`: URL to fetch document from\n- `file_path`: Local file path\n\n**Processing strategies:**\n- PDFs/Images: OCR via Mistral or pdfplumber fallback\n- Office docs: Unstructured API\n- Text/HTML: Direct extraction\n\n**Returns:**\n- `text`: Extracted text content\n- `images`: Base64-encoded images (if extract_images=true)\n- `pages`: Number of pages\n- `word_count`: Extracted word count\n\n**Example:**\n```json\n{\n  \"url\": \"https://files.campaignbrain.dev/docs/scan.pdf\",\n  \"extract_images\": true,\n  \"ocr_provider\": \"mistral\"\n}\n```","operationId":"process_document_api_v1_documents_process_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ProcessRequest"}}},"required":true},"responses":{"200":{"description":"Document content extracted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ProcessResponse"},"example":{"text":"Quarterly Report Q4 2025\n\nRevenue increased 15% year-over-year...","images":[],"tables":[],"pages":12,"word_count":3420,"processing_strategy":"mistral_ocr","detected_type":"pdf","file_path":"/tmp/cbintel_doc_abc123.pdf"}}}},"400":{"description":"Invalid URL or file path"},"422":{"description":"Validation error"},"502":{"description":"Failed to download or process document"},"504":{"description":"Processing timeout"}}}},"/cbintel/api/v1/explorer/artifacts/{address}":{"get":{"tags":["cbintel","explorer"],"summary":"Get Artifact","description":"Resolve an address to artifact data.","operationId":"get_artifact_api_v1_explorer_artifacts__address__get","parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string","title":"Address"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/explorer/runs/{run_id}":{"get":{"tags":["cbintel","explorer"],"summary":"Get Run","description":"Get detailed run information.","operationId":"get_run_api_v1_explorer_runs__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}},{"name":"workspace_id","in":"query","required":true,"schema":{"type":"string","description":"Workspace containing the run","title":"Workspace Id"},"description":"Workspace containing the run"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__RunDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/explorer/search":{"get":{"tags":["cbintel","explorer"],"summary":"Search","description":"Search across workspaces and artifacts.","operationId":"search_api_v1_explorer_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Search query","title":"Q"},"description":"Search query"},{"name":"ws","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Workspace scope","title":"Ws"},"description":"Workspace scope"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__SearchResults"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/explorer/tenants":{"get":{"tags":["cbintel","explorer"],"summary":"List Tenants","description":"List tenants with summary statistics.","operationId":"list_tenants_api_v1_explorer_tenants_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__TenantListResponse"}}}}}}},"/cbintel/api/v1/explorer/tenants/{tenant_id}":{"get":{"tags":["cbintel","explorer"],"summary":"List Workspaces","description":"List workspaces for a given tenant.","operationId":"list_workspaces_api_v1_explorer_tenants__tenant_id__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__cbintel__jobs__routers__explorer__WorkspaceListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/explorer/workspaces/{workspace_id}":{"get":{"tags":["cbintel","explorer"],"summary":"Get Workspace","description":"Get detailed workspace information.","operationId":"get_workspace_api_v1_explorer_workspaces__workspace_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__WorkspaceDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/explorer/workspaces/{workspace_id}/runs":{"get":{"tags":["cbintel","explorer"],"summary":"List Runs","description":"List runs for a workspace with pagination.","operationId":"list_runs_api_v1_explorer_workspaces__workspace_id__runs_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__RunListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/feeds/counties":{"get":{"tags":["cbintel","Elections"],"summary":"Per-county feed state, reason, artifact and runtime","description":"`last_fetched_at`/`contests`/`votes` are NULL for a county that has not reported — never 0. A county reporting zero votes and a county that has not reported are different facts.","operationId":"counties_api_v1_feeds_counties_get","parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","description":"Channel slug","default":"mi-2026-primary","title":"Channel"},"description":"Channel slug"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one state value","title":"State"},"description":"Filter to one state value"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbintel__CountyRow"},"title":"Response Counties Api V1 Feeds Counties Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/feeds/coverage":{"get":{"tags":["cbintel","Elections"],"summary":"Feed coverage by cohort — the 'N of 83, and why' arithmetic","description":"Armed / no-identifier / dark are taken from the same `load_targets()` the ingest tick uses, so this cannot disagree with what is actually polled. `reconciles: false` means the classification drifted and the cohort table should not be trusted until someone looks.","operationId":"coverage_api_v1_feeds_coverage_get","parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","description":"Channel slug","default":"mi-2026-primary","title":"Channel"},"description":"Channel slug"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CoverageOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/feeds/sweep/latest":{"get":{"tags":["cbintel","Elections"],"summary":"The most recent dark-county sweep's per-county verdicts","description":"Reads the newest JSON in the channel's `sweeps/` directory, written by `scripts/elections/mi_dark_county_sweep.py`. 404 when no sweep has been recorded — that is honestly 'nobody has looked', which is different from 'nothing is ingestible'.","operationId":"sweep_latest_api_v1_feeds_sweep_latest_get","parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","description":"Channel slug","default":"mi-2026-primary","title":"Channel"},"description":"Channel slug"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__SweepOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/fetch":{"post":{"tags":["cbintel","fetch"],"summary":"Fetch","description":"Swiss-army knife URL fetch endpoint.\n\n## ⚠️ The two fields that decide whether a page comes back\n\n**`wait_until` (browser engines) — default `domcontentloaded`.**\nThis is worth more than `timeout`. The old default, `networkidle`, never\narrives on a page carrying analytics or a tracking pixel — which is most\n`.gov` domains — so it failed closed on exactly the sites that are hardest\nto reach, and **raising `timeout` cannot rescue it**: the condition is\nnever satisfied, so a longer wait just fails later. `azsos.gov` was carried\nas \"guzl times out on this domain\" for weeks; on `domcontentloaded` it\nreturns 248,959 bytes (cbintel#258).\n\n| value | when |\n|---|---|\n| `domcontentloaded` | **default.** Right for nearly everything. |\n| `load` | sub-resources must be in (images sized, etc.). |\n| `networkidle` | opt in ONLY for a page whose content genuinely lands after\n  the network settles. It will hang forever on an analytics-carrying site. |\n\n**`retries` — default 2 (i.e. 3 attempts per engine).**\nThese failures are measurably nondeterministic: the same URL, engine and\nminute has been seen to succeed on attempt 4 of 15. One failed attempt is\nnot evidence a host is unreachable. Retries are spent only on fast,\nnon-definitive failures — a 404 or a timeout is not hammered.\n\n## Reading the result\n\nJudge the outcome from these fields, **never from `len(html)`** — a\nfully-styled 404 is 61 KB and a bot-wall can be 200 OK:\n\n| field | meaning |\n|---|---|\n| `success` | Real content came back. False for a challenge page too. |\n| `status_code` | What the server actually sent — **present on failure**. |\n| `error` | **Never null when `success` is false.** |\n| `blocked` | The body is a bot-wall/challenge, not the page. |\n| `failure_origin` | `remote` = the host; `local` = cbintel's own plumbing. |\n| `attempts[]` | Every engine tried, in order, with its own verdict. |\n| `egress_used` | `direct` / `vpn` / `proxy`. |\n\n`failure_origin: \"local\"` means the URL is **not** known to be unreachable\n— an engine of ours broke. Never grade a source on one.\n\n## Egress: `geo` rotates over a pool\n\nWith `geo` set, attempts are spread round-robin over `exits` distinct cbvpn\nexits (default 2), and **each attempt reports the exit it left from** in\n`attempts[].exit_ip`. Raise it to ask a question one exit cannot answer:\n\n```json\n{\"url\": \"https://sos.ga.gov/\", \"geo\": \"us\", \"exits\": 3}\n```\n\n→ \"blocked on 3 of 3 exits\" (the host screens datacenter ranges) versus\n\"blocked on 1 of 3\" (that exit is burned). Until this rotated, every\nVPN-based reachability verdict silently meant the latter.\n\nAn explicit `geo` that cannot be allocated **fails** — it never falls back\nto direct, because that would leak the origin IP the request existed to\nhide. Omit `geo` to fetch direct deliberately.\n\nUnknown fields are rejected with a 422 naming them. `timeout` is in\n**seconds**; there is no `timeout_ms`, and `method`/`data`/`form` are not\nsupported (this endpoint issues GETs).\n\n## Engines\n\nEvery field below is also documented on the request schema itself, which is\nthe authoritative list — `cbcli describe cbintel fetch -c create`, or the\n`FetchRequest` model in `/openapi.json`.\n\n- auto: climbs `aiohttp → curl → guzl → lynx`, retrying each rung, and\n  stops at the first that returns real content (default). Set\n  `escalate: false` to pin the first choice and see its raw verdict.\n- aiohttp: Fast async HTTP client\n- curl: CLI curl with interface binding support\n- lynx: Text-mode browser for JavaScript-light pages\n- guzl: Full Playwright browser for JavaScript rendering\n\n`auto` demotes guzl to last for document URLs (.pdf/.xlsx/.csv/...), which\nPlaywright cannot navigate to — it aborts with \"Download is starting\".\n\n**Content Options:**\n- include_html: Raw HTML (default: True)\n- include_markdown: Markdown conversion (default: True)\n- include_text: Plain text extraction (default: True)\n- include_screenshot: PNG screenshot (default: False, requires guzl)\n- include_pdf: PDF rendering (default: False, requires guzl)\n- include_dom: DOM elements with positions (default: False, requires guzl)\n- include_links: Extracted links (default: True)\n- include_metadata: Page metadata (default: True)\n\n**Delivery Options:**\n- inline: JSON response with base64-encoded binaries (default)\n- files: Upload to files.campaignbrain.dev, return URLs\n\n**Client Credentials (for file uploads):**\n- cbfiles_api_key: Client's API key (body or X-CBFILES-API-KEY header)\n- cbfiles_bucket: Target bucket (body or X-CBFILES-BUCKET header)\n\nHeaders take precedence over body parameters for credentials.\n\n**Geographic Routing:**\n- geo: Geographic filter (e.g., \"us\", \"us:ca\", \"de\")\n- proxy: Direct proxy URL override\n\n**Browser Options (guzl only):**\n- browser: chromium, firefox, webkit\n- viewport_width/height: Custom viewport\n- wait_until: see the warning at the top — this one decides outcomes\n  (default: domcontentloaded)\n- wait_delay: Extra settle time after the load event, in ms (default: 0)\n\n**User Agent Rotation:**\n- user_agent: Single custom UA string (used when rotation is \"none\")\n- user_agent_rotation: Rotation strategy — \"none\" (default), \"random\", or \"rotate\"\n- user_agent_pool: Custom list of UAs for rotation (uses built-in pool of 5 Chrome/Firefox UAs if omitted)\n\nStrategies:\n- **none**: Uses `user_agent` if set, otherwise the engine picks a default\n- **random**: Picks a random UA from the pool on each request\n- **rotate**: Deterministic per-URL pick — same URL always gets the same UA (hash-based)\n\nWhen `user_agent_rotation` is \"random\" or \"rotate\", the `user_agent` field is ignored\nin favor of the pool.\n\n**Proxy Rotation:**\n- proxy: Single proxy URL (used when rotation is \"none\")\n- proxy_rotation: Rotation strategy — \"none\" (default), \"random\", or \"rotate\"\n- proxy_pool: List of proxy URLs for rotation (required for rotation — no built-in pool)\n\nStrategies:\n- **none**: Uses `proxy` if set, otherwise no proxy\n- **random**: Picks a random proxy from the pool on each request\n- **rotate**: Deterministic per-URL pick — same URL always gets the same proxy (hash-based)\n\nWhen `proxy_rotation` is \"random\" or \"rotate\", the `proxy` field is ignored\nin favor of the pool.\n\n**Example Requests:**\n\nSimple HTML fetch:\n```json\n{\"url\": \"https://example.com\"}\n```\n\nRandom UA rotation with built-in pool:\n```json\n{\"url\": \"https://example.com\", \"user_agent_rotation\": \"random\"}\n```\n\nDeterministic rotation with custom pool:\n```json\n{\n  \"url\": \"https://example.com\",\n  \"user_agent_rotation\": \"rotate\",\n  \"user_agent_pool\": [\n    \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120\",\n    \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/537.36\",\n    \"Mozilla/5.0 (X11; Linux x86_64) Firefox/121.0\"\n  ]\n}\n```\n\nRandom proxy rotation:\n```json\n{\n  \"url\": \"https://example.com\",\n  \"proxy_rotation\": \"random\",\n  \"proxy_pool\": [\n    \"http://proxy1:8080\",\n    \"http://proxy2:8080\",\n    \"http://proxy3:8080\"\n  ]\n}\n```\n\nFull browser capture with client credentials:\n```json\n{\n  \"url\": \"https://example.com\",\n  \"engine\": \"guzl\",\n  \"include_screenshot\": true,\n  \"delivery\": \"files\",\n  \"cbfiles_api_key\": \"cbfiles_xxx...\",\n  \"cbfiles_bucket\": \"docs\"\n}\n```\n\nOr use headers:\n```\nX-CBFILES-API-KEY: cbfiles_xxx...\nX-CBFILES-BUCKET: docs\n```","operationId":"fetch_api_v1_fetch_post","parameters":[{"name":"x-cbfiles-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cbfiles-Api-Key"}},{"name":"x-cbfiles-bucket","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cbfiles-Bucket"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__FetchRequest"}}}},"responses":{"200":{"description":"Successful fetch with extracted content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__FetchResponse"},"example":{"success":true,"url":"https://example.com","final_url":"https://example.com/","status_code":200,"blocked":false,"warnings":[],"attempts":[{"engine":"aiohttp","ok":true,"status_code":200,"bytes":1256,"blocked":false,"duration_ms":342}],"engine_used":"aiohttp","egress_used":"direct","duration_ms":342,"html":"<html>...</html>","markdown":"# Example Domain\n\nThis domain is for use in illustrative examples.","text":"Example Domain\nThis domain is for use in illustrative examples.","links":["https://www.iana.org/domains/example"],"metadata":{"title":"Example Domain","description":""}}}}},"400":{"description":"Invalid URL","content":{"application/json":{"example":{"detail":"Invalid URL: 'not-a-url'"}}}},"422":{"description":"Validation error — including an unrecognised field. A knob this endpoint does not have is rejected by name rather than silently ignored (cbintel#258).","content":{"application/json":{"example":{"detail":[{"type":"extra_forbidden","loc":["body","timeout_ms"],"msg":"Extra inputs are not permitted","input":90000}]}}}},"502":{"description":"Upstream fetch failed","content":{"application/json":{"example":{"detail":"Fetch failed: connection refused"}}}},"504":{"description":"Fetch timeout","content":{"application/json":{"example":{"detail":"Fetch timed out after 30s"}}}}}}},"/cbintel/api/v1/har":{"get":{"tags":["cbintel","har"],"summary":"List vault HARs","operationId":"har_list_api_v1_har_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbintel__HarEntry"},"type":"array","title":"Response Har List Api V1 Har Get"}}}}}}},"/cbintel/api/v1/har/extract":{"post":{"tags":["cbintel","har"],"summary":"Mine response-body records","operationId":"har_extract_api_v1_har_extract_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ExtractRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HarRecords"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/har/inspect":{"post":{"tags":["cbintel","har"],"summary":"Pre-flight a HAR","operationId":"har_inspect_api_v1_har_inspect_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__InspectRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HarInspect"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/har/sessions":{"get":{"tags":["cbintel","har"],"summary":"Auth sessions from local HARs","operationId":"har_sessions_api_v1_har_sessions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbintel__HarSession"},"type":"array","title":"Response Har Sessions Api V1 Har Sessions Get"}}}}}}},"/cbintel/api/v1/har/status":{"get":{"tags":["cbintel","har"],"summary":"HAR pipeline status","operationId":"har_status_api_v1_har_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HarStatus"}}}}}}},"/cbintel/api/v1/har/sync":{"post":{"tags":["cbintel","har"],"summary":"Sync vault → local","operationId":"har_sync_api_v1_har_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HarSyncResult"}}}}}}},"/cbintel/api/v1/hyperlocal/counties":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"List Counties","description":"List county resources with optional filters. ``status=missing`` is the\ngap list (counties whose source URL hasn't been found yet).","operationId":"list_counties_api_v1_hyperlocal_counties_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state code, e.g. MI","title":"State"},"description":"Filter by state code, e.g. MI"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"live | pdf | missing | unverified","title":"Status"},"description":"live | pdf | missing | unverified"},{"name":"vendor_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"enhanced_voting | clarity | ...","title":"Vendor Type"},"description":"enhanced_voting | clarity | ..."},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"resource_kind filter","title":"Kind"},"description":"resource_kind filter"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"substring match on county/url/notes","title":"Q"},"description":"substring match on county/url/notes"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CountyResourceList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"post":{"tags":["cbintel","Hyperlocal"],"summary":"Create County","description":"Create (or upsert) a county resource — for a new county or a new\n``resource_kind`` beyond the seeded election-results sources.","operationId":"create_county_api_v1_hyperlocal_counties_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ResourceCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CountyResource"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/hyperlocal/counties/stats":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"Counties Stats","description":"Coverage rollup — totals, gap count, and breakdowns by status + vendor.","operationId":"counties_stats_api_v1_hyperlocal_counties_stats_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Counties Stats Api V1 Hyperlocal Counties Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/hyperlocal/counties/{state}/{county}":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"Get County","operationId":"get_county_api_v1_hyperlocal_counties__state___county__get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"county","in":"path","required":true,"schema":{"type":"string","title":"County"}},{"name":"kind","in":"query","required":false,"schema":{"type":"string","default":"election_results_source","title":"Kind"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CountyResource"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"patch":{"tags":["cbintel","Hyperlocal"],"summary":"Patch County","description":"Fill or update a county resource (the gap-filling write path). The row\nmust already exist (seeded); use POST to create a new county/kind.","operationId":"patch_county_api_v1_hyperlocal_counties__state___county__patch","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"county","in":"path","required":true,"schema":{"type":"string","title":"County"}},{"name":"kind","in":"query","required":false,"schema":{"type":"string","default":"election_results_source","title":"Kind"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ResourcePatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CountyResource"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/hyperlocal/jurisdictions":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"List Jurisdictions","description":"The sub-county clerk roster — Michigan's ~1,520 city/township jurisdictions that\nadminister elections, with clerk contact + website where known. The crawl surface for\nthe municipality document tier.","operationId":"list_jurisdictions_api_v1_hyperlocal_jurisdictions_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state code, e.g. MI","title":"State"},"description":"Filter by state code, e.g. MI"},{"name":"county","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by county","title":"County"},"description":"Filter by county"},{"name":"muni_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"township | city | village","title":"Muni Type"},"description":"township | city | village"},{"name":"have_url","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only municipalities with (or without) a clerk URL","title":"Have Url"},"description":"Only municipalities with (or without) a clerk URL"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Jurisdictions Api V1 Hyperlocal Jurisdictions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/hyperlocal/jurisdictions/stats":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"Jurisdictions Stats","description":"Roster coverage — total municipalities, splits by type, how many carry a clerk URL\n(the doc-crawl surface), and how many counties are represented.","operationId":"jurisdictions_stats_api_v1_hyperlocal_jurisdictions_stats_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Jurisdictions Stats Api V1 Hyperlocal Jurisdictions Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/hyperlocal/law-docs":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"List Law Docs","description":"The election-law document corpus — authoritative state-baseline docs (MI SoS /\nBureau of Elections) plus per-county and per-municipality local procedural docs.\nEach row carries the extracted summary, key provisions, effective cycle, and\nrecency. Populated by scripts/hyperlocal/ingest_election_law.py.","operationId":"list_law_docs_api_v1_hyperlocal_law_docs_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state code, e.g. MI","title":"State"},"description":"Filter by state code, e.g. MI"},{"name":"county","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"County name (use '' for state-level docs)","title":"County"},"description":"County name (use '' for state-level docs)"},{"name":"municipality","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"City/township name","title":"Municipality"},"description":"City/township name"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"jurisdiction_level: state | county | municipality","title":"Level"},"description":"jurisdiction_level: state | county | municipality"},{"name":"doc_kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"calendar|voter_id|absentee|early_voting|canvass|...","title":"Doc Kind"},"description":"calendar|voter_id|absentee|early_voting|canvass|..."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ingested | stale | unreachable | superseded","title":"Status"},"description":"ingested | stale | unreachable | superseded"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Law Docs Api V1 Hyperlocal Law Docs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/hyperlocal/law-docs/stats":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"Law Docs Stats","description":"Corpus coverage — totals, splits by jurisdiction_level / doc_kind / status, and\nhow many counties + municipalities have at least one ingested document.","operationId":"law_docs_stats_api_v1_hyperlocal_law_docs_stats_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Law Docs Stats Api V1 Hyperlocal Law Docs Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/hyperlocal/law-docs/{state}":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"Law Docs For","description":"The election-law docs that APPLY to a jurisdiction, with inheritance — a township\ngets its own docs plus its county's plus the state baseline. This is the 'what is the\ncurrent election law + status here' read for the cbloom app + cbmodels chat.","operationId":"law_docs_for_api_v1_hyperlocal_law_docs__state__get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"county","in":"query","required":false,"schema":{"type":"string","description":"County name ('' = state baseline only)","default":"","title":"County"},"description":"County name ('' = state baseline only)"},{"name":"municipality","in":"query","required":false,"schema":{"type":"string","description":"City/township ('' = county + state)","default":"","title":"Municipality"},"description":"City/township ('' = county + state)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Law Docs For Api V1 Hyperlocal Law Docs  State  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/hyperlocal/profiles":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"List Profiles","description":"The standard data set extracted from each county source by the profiling\ncrawl — officials, contacts, event dates, rules, ballot initiatives, result-doc\nlinks, NER entities. Populated by scripts/hyperlocal/profile_sources.py.","operationId":"list_profiles_api_v1_hyperlocal_profiles_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state code, e.g. MI","title":"State"},"description":"Filter by state code, e.g. MI"},{"name":"reachable","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only sources that fetched to usable content","title":"Reachable"},"description":"Only sources that fetched to usable content"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Profiles Api V1 Hyperlocal Profiles Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/hyperlocal/profiles/stats":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"Profiles Stats","description":"Profiling-crawl coverage: how many sources profiled + reachable, and how\nmany carry each structured field.","operationId":"profiles_stats_api_v1_hyperlocal_profiles_stats_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Profiles Stats Api V1 Hyperlocal Profiles Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/hyperlocal/profiles/{state}/{county}":{"get":{"tags":["cbintel","Hyperlocal"],"summary":"Get Profile","operationId":"get_profile_api_v1_hyperlocal_profiles__state___county__get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}},{"name":"county","in":"path","required":true,"schema":{"type":"string","title":"County"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Profile Api V1 Hyperlocal Profiles  State   County  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/jobs/":{"get":{"tags":["cbintel","jobs"],"summary":"List recent jobs","description":"List recent jobs with optional filtering.\n\n## Query Parameters\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `status` | string | Filter by status: queued, processing, completed, failed, cancelled |\n| `job_type` | string | Filter by type: crawl, lazarus, vectl, screenshot |\n| `limit` | integer | Max jobs to return (default 50, max 100) |\n\n## Use Cases\n\n- Monitor active jobs: `?status=processing`\n- Review failed jobs: `?status=failed`\n- List all crawl jobs: `?job_type=crawl`\n\n## Response\n\nReturns array of job summaries with id, type, status, and timestamps.","operationId":"list_jobs_api_v1_jobs__get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbintel__JobStatus"},{"type":"null"}],"title":"Status"}},{"name":"job_type","in":"query","required":false,"schema":{"anyOf":[{"enum":["browser","crawl","graph","lazarus","report","vectl","screenshot","transcript"],"type":"string"},{"type":"null"}],"title":"Job Type"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"List of jobs matching filters","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Jobs Api V1 Jobs  Get"},"example":{"jobs":[{"job_id":"job_abc123","job_type":"crawl","status":"completed","created_at":"2024-01-15T10:30:00Z","completed_at":"2024-01-15T10:35:00Z"},{"job_id":"job_def456","job_type":"lazarus","status":"processing","created_at":"2024-01-15T10:32:00Z"}],"total":2}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/jobs/browser":{"post":{"tags":["cbintel","jobs"],"summary":"Submit browser automation job","description":"Submit a browser automation job using ferret.\n\n## What This Job Does\n\n1. **Script Resolution** - Resolves script from JSON, natural language, or template\n2. **Session Setup** - Configures browser viewport, proxy, and user agent\n3. **VPN Routing** - Routes traffic through specified region (if geo provided)\n4. **Script Execution** - Runs the automation script via SWRM\n5. **Result Capture** - Uploads screenshots, network logs, and results\n\n## Script Sources (exactly one required)\n\n| Parameter | Description |\n|-----------|-------------|\n| `script` | Raw JSON AST script definition |\n| `natural_language` | Natural language instruction to compile |\n| `template` | Built-in template name with parameter substitution |\n\n## Available Templates\n\n| Template | Description |\n|----------|-------------|\n| `fec_filing_check` | Monitor FEC filings for a committee |\n| `social_profile_archive` | Archive social media profile |\n\n## Use Cases\n\n- **Monitoring**: Check for changes on specific pages\n- **Data Extraction**: Scrape structured data from websites\n- **Form Automation**: Automate form submissions\n- **Visual Archiving**: Capture page states with screenshots\n\n## Geographic Routing\n\nUse the `geo` parameter to route browser traffic through specific regions:\n```json\n{\"template\": \"...\", \"geo\": \"us:ca\"}\n```\n\n## Response\n\nReturns a `job_id` for polling. When complete, results include:\n- `result.json` - Execution result with context\n- `final_screenshot.png` - Screenshot after execution (if enabled)\n- Network logs (if capture_network=true)","operationId":"submit_browser_job_api_v1_jobs_browser_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__BrowserJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__JobSubmitResponse"}}}},"201":{"description":"Job queued successfully"},"400":{"description":"Invalid request parameters","content":{"application/json":{"examples":{"template_not_found":{"value":{"detail":"Template 'unknown' not found. Available: fec_filing_check, social_profile_archive"}},"unresolved_params":{"value":{"detail":"Unresolved template parameters: committee_id, date"}}}}}},"422":{"description":"Validation error","content":{"application/json":{"examples":{"no_source":{"value":{"detail":[{"loc":["body"],"msg":"Exactly one of script/natural_language/template required"}]}},"multiple_sources":{"value":{"detail":[{"loc":["body"],"msg":"Only one of script/natural_language/template allowed, got multiple"}]}}}}}},"503":{"description":"Service unavailable","content":{"application/json":{"example":{"detail":"SWRM server unavailable"}}}}}}},"/cbintel/api/v1/jobs/crawl":{"post":{"tags":["cbintel","jobs"],"summary":"Submit AI-powered crawl job","description":"Submit an AI-powered web crawling job.\n\n## What This Job Does\n\n1. **Search Phase** - Queries search engines for relevant URLs\n2. **Fetch Phase** - Retrieves content from discovered URLs\n3. **AI Evaluation** - Claude evaluates page quality and relevance\n4. **Child Discovery** - Extracts links for iterative crawling\n5. **Synthesis** - Combines findings into structured knowledge\n\n## Use Cases\n\n- **Research**: \"Find information about AI regulation in California\"\n- **Competitive Analysis**: \"Analyze competitor product pages\"\n- **News Monitoring**: \"Track coverage of a specific topic\"\n- **Data Collection**: \"Gather product prices from e-commerce sites\"\n\n## Geographic Routing\n\nUse the `geo` parameter to route through specific regions:\n```json\n{\"query\": \"...\", \"geo\": \"us:ca\"}\n```\n\n## Response\n\nReturns a `job_id` for polling. When complete, results include:\n- Synthesized findings (JSON)\n- Raw page content (uploaded to files.campaignbrain.dev)\n- URL quality scores","operationId":"submit_crawl_job_api_v1_jobs_crawl_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CrawlJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__JobSubmitResponse"}}}},"201":{"description":"Job queued successfully"},"400":{"description":"Invalid request parameters","content":{"application/json":{"examples":{"empty_query":{"value":{"detail":"query: String should have at least 1 character"}},"invalid_geo":{"value":{"detail":"Invalid geo filter: 'xyz' - use format 'us' or 'us:ca'"}}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","max_urls"],"msg":"Input should be less than or equal to 1000"}]}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"detail":"Rate limit: max 10 concurrent crawl jobs"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"example":{"detail":"Job queue unavailable - Redis connection failed"}}}}}}},"/cbintel/api/v1/jobs/documents":{"post":{"tags":["cbintel","jobs"],"summary":"Submit document intelligence job","description":"Submit a document intelligence processing job.\n\n## What This Job Does\n\n1. **Document Acquisition** - Downloads URLs or reads local files\n2. **Type Detection** - AI-powered detection of document type and format\n3. **Content Extraction** - OCR for images/PDFs, parsing for Office docs\n4. **Analysis** - Extract entities, topics, summaries, relationships\n5. **Indexing** - Store vectors for semantic search (local or remote)\n\n## Supported Formats\n\n| Format | Processing |\n|--------|------------|\n| PDF | OCR (Mistral) or text extraction (pdfplumber) |\n| Images (PNG, JPG, TIFF) | OCR via Mistral |\n| Office (DOCX, XLSX, PPTX) | Unstructured API |\n| Text (TXT, MD, JSON, CSV) | Direct extraction |\n| HTML | Tag stripping and text extraction |\n\n## Input Sources\n\nProvide either `urls` or `files` (or both):\n```json\n{\"urls\": [\"https://files.campaignbrain.dev/docs/report.pdf\"]}\n```\nor\n```json\n{\"files\": [\"/data/documents/report.pdf\"]}\n```\n\n## Processing Options\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `extract_entities` | true | Extract people, orgs, locations |\n| `extract_summary` | true | Generate document summaries |\n| `extract_topics` | true | Identify main topics |\n| `extract_relationships` | false | Find entity connections (slower) |\n| `extract_facts` | false | Extract claims with evidence |\n| `translate_to` | null | Translate to language code |\n\n## Indexing Options\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `index_locally` | true | Store in local vectl index |\n| `index_remotely` | false | Index via Index API (needs workspace_id) |\n\n## Response\n\nReturns a `job_id` for polling. When complete, results include:\n- Extracted text and metadata\n- Entities with confidence scores\n- Document summaries and topics\n- Relationships and facts (if enabled)\n- Vector storage references\n\n## CLI Usage\n\n```bash\ncbcli intel jobs documents create --params '{\n  \"urls\": [\"https://files.campaignbrain.dev/docs/report.pdf\"],\n  \"extract_entities\": true,\n  \"extract_relationships\": true\n}'\n```","operationId":"submit_document_job_api_v1_jobs_documents_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__DocumentJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__JobSubmitResponse"}}}},"201":{"description":"Job queued successfully"},"400":{"description":"Invalid request parameters","content":{"application/json":{"examples":{"no_input":{"value":{"detail":"At least one of 'urls' or 'files' required"}},"invalid_url":{"value":{"detail":"Invalid URL: not-a-url"}}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","chunk_size"],"msg":"Input should be >= 100"}]}}}}}}},"/cbintel/api/v1/jobs/graph":{"post":{"tags":["cbintel","jobs"],"summary":"Submit Research Graph job","description":"Submit a Research Graph execution job.\n\n## What This Job Does\n\nExecute complex multi-stage research pipelines defined in YAML:\n\n1. **Parse Graph** - Load from built-in template or inline YAML definition\n2. **Execute Stages** - Process stages sequentially with parallel operations\n3. **Data Flow** - Pass outputs between operations via context\n4. **Synthesize** - Combine findings into structured results\n\n## Graph Source (one required)\n\n| Parameter | Description |\n|-----------|-------------|\n| `template` | Built-in template: `opponent_deep_dive`, `simple_search`, `geo_news_comparison`, `position_tracker` |\n| `graph_yaml` | Inline YAML graph definition |\n\n## Execution Modes\n\nGraphs support three execution patterns per stage:\n\n- **sequential** - Operations run in order\n- **parallel** - Operations run concurrently\n- **parallel_foreach** - Single operation applied to each item in a list\n\n## Use Cases\n\n- **Opponent Research**: Deep dive into political candidates with multi-source synthesis\n- **News Analysis**: Compare coverage across geographic regions\n- **Position Tracking**: Track stance changes over time via archive analysis\n- **Custom Pipelines**: Define your own multi-stage research workflows\n\n## Geographic Routing\n\nUse the `geo` parameter to route all fetch operations through specific regions:\n```json\n{\"template\": \"opponent_deep_dive\", \"params\": {\"opponent_name\": \"...\"}, \"geo\": \"us:ky\"}\n```\n\n## Response\n\nReturns a `job_id` for polling. When complete, results include:\n- `graph_result.json` - Full execution results with stage outputs\n- Individual output files based on graph definition","operationId":"submit_graph_job_api_v1_jobs_graph_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__GraphJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__JobSubmitResponse"}}}},"201":{"description":"Job queued successfully"},"400":{"description":"Invalid request parameters","content":{"application/json":{"examples":{"no_source":{"value":{"detail":"Either 'template' or 'graph_yaml' must be provided"}},"invalid_template":{"value":{"detail":"Unknown template: 'fake_template'. Available: opponent_deep_dive, simple_search, geo_news_comparison, position_tracker"}},"invalid_yaml":{"value":{"detail":"Invalid YAML syntax at line 5: expected mapping"}},"missing_params":{"value":{"detail":"Missing required parameter: 'opponent_name'"}}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","timeout"],"msg":"Input should be a valid integer"}]}}}},"503":{"description":"Service unavailable","content":{"application/json":{"example":{"detail":"Graph executor unavailable"}}}}}}},"/cbintel/api/v1/jobs/lazarus":{"post":{"tags":["cbintel","jobs"],"summary":"Submit archive retrieval job","description":"Submit a historical archive retrieval job.\n\n## What This Job Does\n\n1. **URL Discovery** - Finds archived URLs via CDX API and gau\n2. **Snapshot Retrieval** - Fetches historical versions from Wayback Machine\n3. **Deduplication** - Removes duplicate content across snapshots\n4. **Timeline Construction** - Organizes snapshots chronologically\n\n## Use Cases\n\n- **Content Recovery**: \"Find deleted pages from example.com\"\n- **Change Tracking**: \"How did the pricing page change over time?\"\n- **Historical Research**: \"What did this site look like in 2020?\"\n- **Evidence Collection**: \"Archive versions of a page before it was modified\"\n\n## Data Sources\n\n- **Internet Archive (Wayback Machine)** - Primary archive source\n- **Common Crawl** - Secondary archive for broader coverage\n- **gau** - URL discovery tool for finding archived URLs\n\n## Response\n\nReturns a `job_id` for polling. When complete, results include:\n- discovery.json - List of discovered URLs\n- snapshots.json - Retrieved snapshot metadata\n- Content files (HTML) uploaded to files.campaignbrain.dev","operationId":"submit_lazarus_job_api_v1_jobs_lazarus_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__LazarusJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__JobSubmitResponse"}}}},"201":{"description":"Job queued successfully"},"400":{"description":"Invalid request parameters","content":{"application/json":{"examples":{"empty_domain":{"value":{"detail":"domain: String should have at least 1 character"}},"invalid_date":{"value":{"detail":"from_date must be in YYYYMMDD format"}}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","sample_size"],"msg":"Input should be less than or equal to 10000"}]}}}},"503":{"description":"Service unavailable","content":{"application/json":{"example":{"detail":"Archive services unreachable"}}}}}}},"/cbintel/api/v1/jobs/pipeline":{"post":{"tags":["cbintel","jobs"],"summary":"Submit a multi-step job pipeline","description":"Submit a multi-step job pipeline.\n\n## What This Endpoint Does\n\nCreates an ordered pipeline of jobs. When step N completes, step N+1 is\nautomatically enqueued with variable substitution from the previous result.\n\n## Variable Substitution\n\nUse `$previous.result_url` or `$previous.summary.KEY` in step requests to\nreference values from the prior step's result.\n\n## Example\n\n```json\n{\n  \"steps\": [\n    {\"type\": \"crawl\", \"request\": {\"query\": \"Michigan 2026\"}},\n    {\"type\": \"report\", \"request\": {\"source_url\": \"$previous.result_url\", \"template\": \"briefing\"}}\n  ]\n}\n```","operationId":"submit_pipeline_api_v1_jobs_pipeline_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__cbintel__jobs__models__PipelineRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__PipelineSubmitResponse"}}}},"201":{"description":"Pipeline created successfully"},"400":{"description":"Invalid step type","content":{"application/json":{"example":{"detail":"Unknown job type in step 1: 'foo'"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"get":{"tags":["cbintel","jobs"],"summary":"List recent pipelines","description":"List recent pipelines with optional status filtering.\n\n## Query Parameters\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `status` | string | Filter: running, completed, failed, cancelled |\n| `limit` | integer | Max pipelines to return (default 50) |","operationId":"list_pipelines_api_v1_jobs_pipeline_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["running","completed","failed","cancelled"],"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"List of pipelines matching filters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__PipelineListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/jobs/pipeline/{pipeline_id}":{"get":{"tags":["cbintel","jobs"],"summary":"Get pipeline status","description":"Poll pipeline status.\n\nReturns the current state of the pipeline including per-step status and\njob IDs for each step.","operationId":"get_pipeline_status_api_v1_jobs_pipeline__pipeline_id__get","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}}],"responses":{"200":{"description":"Pipeline status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__PipelineStatusResponse"}}}},"404":{"description":"Pipeline not found","content":{"application/json":{"example":{"detail":"Pipeline not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"delete":{"tags":["cbintel","jobs"],"summary":"Delete pipeline and all its jobs, artifacts, and vectors","description":"Delete a pipeline and cascade-delete all its jobs.\n\nCancels running jobs first, then removes: vectl vectors, MinIO files,\nRedis state. Use `?dry_run=true` to preview without deleting.\n\nIf any worker cannot be confirmed stopped, **nothing is deleted** — see\n`jobs_still_running` on the receipt, or `?force=true` (cbintel#244).","operationId":"delete_pipeline_api_v1_jobs_pipeline__pipeline_id__delete","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","description":"Preview what would be deleted","default":false,"title":"Dry Run"},"description":"Preview what would be deleted"},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Delete even if a worker could not be confirmed stopped","default":false,"title":"Force"},"description":"Delete even if a worker could not be confirmed stopped"}],"responses":{"200":{"description":"Pipeline deleted","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Pipeline Api V1 Jobs Pipeline  Pipeline Id  Delete"}}}},"404":{"description":"Pipeline not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/jobs/pipeline/{pipeline_id}/retry":{"post":{"tags":["cbintel","jobs"],"summary":"Retry a failed pipeline from its failed step","description":"Retry a failed pipeline from its failed step.\n\nRe-enqueues the failed step with a new job ID. Subsequent steps will\nchain as normal once the retried step completes.\n\nOptionally pass `override_request` to fix the failed step's params\n(e.g. correct a bad URL).","operationId":"retry_pipeline_api_v1_jobs_pipeline__pipeline_id__retry_post","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbintel__PipelineRetryRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Pipeline retried, now running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__PipelineStatusResponse"}}}},"400":{"description":"Pipeline is not in failed state","content":{"application/json":{"example":{"detail":"Pipeline is not failed"}}}},"404":{"description":"Pipeline not found","content":{"application/json":{"example":{"detail":"Pipeline not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/jobs/report":{"post":{"tags":["cbintel","jobs"],"summary":"Generate report from crawl results","description":"Submit a report generation job.\n\n## What This Job Does\n\n1. **Fetch Source** — Downloads crawl result JSON from source_url\n2. **Extract Content** — Pulls synthesis, page scores, child results\n3. **Synthesize** — Sends assembled context to AI with template-specific prompt\n4. **Format** — Wraps output with metadata header, sources, knowledge gaps\n5. **Upload** — Saves report.md to files.campaignbrain.dev\n\n## Templates\n\n| Template | Description |\n|----------|-------------|\n| `briefing` | Structured intelligence briefing with executive summary and key findings |\n| `timeline` | Chronological report with dated events and narrative analysis |\n| `comparison` | Comparative analysis with table, pros/cons, and synthesis |\n\n## Use Cases\n\n- **Post-Crawl Reports**: Turn raw crawl results into readable briefings\n- **Research Summaries**: Generate polished documents from gathered data\n- **Timeline Construction**: Build chronological narratives from events\n\n## Response\n\nReturns a `job_id` for polling. When complete, results include:\n- `report.md` — Polished Markdown report uploaded to files.campaignbrain.dev","operationId":"submit_report_job_api_v1_jobs_report_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ReportJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__JobSubmitResponse"}}}},"201":{"description":"Job queued successfully"},"400":{"description":"Invalid request parameters","content":{"application/json":{"examples":{"empty_url":{"value":{"detail":"source_url: String should have at least 1 character"}},"invalid_template":{"value":{"detail":"template must be one of: briefing, timeline, comparison"}}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","max_tokens"],"msg":"Input should be less than or equal to 16384"}]}}}}}}},"/cbintel/api/v1/jobs/screenshots":{"post":{"tags":["cbintel","jobs"],"summary":"Submit browser screenshot job","description":"Submit a browser automation job for screenshots or PDFs.\n\n## What This Job Does\n\n1. **Browser Launch** - Starts Playwright browser (Chromium by default)\n2. **Page Navigation** - Loads each URL with configurable wait conditions\n3. **Capture** - Takes full-page screenshots or generates PDFs\n4. **DOM Extraction** - Optionally extracts DOM structure with positions\n5. **Upload** - Uploads results to files.campaignbrain.dev\n\n## Output Formats\n\n| Format | Description |\n|--------|-------------|\n| `png` | Full-page screenshot (default) |\n| `pdf` | PDF rendering of the page |\n\n## Use Cases\n\n- **Visual Archiving**: \"Capture how this page looks today\"\n- **PDF Generation**: \"Create PDF versions of web articles\"\n- **Visual Regression**: \"Compare page appearance over time\"\n- **Evidence Collection**: \"Screenshot pages for legal/compliance\"\n\n## Geographic Routing\n\nUse `geo` parameter to capture from specific regions (for geo-restricted content):\n```json\n{\"urls\": [...], \"geo\": \"de\"}\n```\n\n## Response\n\nReturns a `job_id` for polling. When complete, results include:\n- manifest.json - Capture metadata for all URLs\n- screenshot_XXX.png or document_XXX.pdf - Captured files\n- dom_XXX.html - DOM structure (if capture_dom=true)","operationId":"submit_screenshot_job_api_v1_jobs_screenshots_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ScreenshotJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__JobSubmitResponse"}}}},"201":{"description":"Job queued successfully"},"400":{"description":"Invalid request parameters","content":{"application/json":{"examples":{"empty_urls":{"value":{"detail":"urls: List should have at least 1 item"}},"invalid_url":{"value":{"detail":"Invalid URL format: 'not-a-url'"}}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","viewport_width"],"msg":"Input should be less than or equal to 3840"}]}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"detail":"Rate limit: max 5 concurrent screenshot jobs"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"example":{"detail":"Playwright browser not available"}}}}}}},"/cbintel/api/v1/jobs/stats":{"get":{"tags":["cbintel","jobs","jobs"],"summary":"Queue depth, in-flight work, and worker reservation","description":"What the queue is doing, as a number rather than a forensic exercise.\n\nA bulk walk stalled for 15 minutes behind three slow jobs and the only way\nto see it was `journalctl` — which produced the wrong diagnosis, because\nthe browser jobs it named had all completed fine (cbintel#261).\n\nRead it like this:\n\n* `queued` / `queued_total` — depth per lane. Depth alone is ambiguous: a\n  queue that is draining and refilling looks the same as one that is stuck.\n* **`oldest_queued_age_s`** — the number that disambiguates it. If this\n  keeps climbing, nothing is being served from that lane.\n* `in_flight` — what is actually running, by type. Slow types filling this\n  while a fast lane's `oldest_queued_age_s` climbs IS the starvation\n  signature.\n* `reserved_fast_workers` — how many workers refuse `slow_job_types`\n  outright, so a cheap class always has somewhere to run.\n* `in_flight_here` vs `in_flight` — a gap means records claim work no\n  worker on this process is doing; the reaper fails those within\n  `HEARTBEAT_STALE_AFTER`.\n\n⚠️ **Do not alert on a single-sample `in_flight` > `in_flight_here`.**\nA job's record is written `processing` a moment before its task is\nregistered locally, so a healthy queue shows a transient gap of 1 — about\n1 sample in 8 at ~36 jobs/min. Require the gap to persist across several\nsamples before treating it as a real orphan. (The reaper is immune by\nconstruction: `heartbeat_at` is stamped at that same write, so a job in\nthe window never looks stale.)","operationId":"queue_stats_api_v1_jobs_stats_get","responses":{"200":{"description":"One readable picture of the queue","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Queue Stats Api V1 Jobs Stats Get"},"example":{"queued":{"browser":77},"queued_total":77,"oldest_queued_age_s":{"browser":912.4},"in_flight":{"transcript":3,"browser":3},"in_flight_here":3,"workers":3,"reserved_fast_workers":1,"slow_job_types":["crawl","graph","lazarus","report","transcript"],"job_timeouts_s":{"browser":300,"transcript":3600},"generation":"6f1c…"}}}}}}},"/cbintel/api/v1/jobs/templates":{"get":{"tags":["cbintel","jobs","jobs"],"summary":"List built-in graph templates","description":"Catalog the built-in graph templates a `/jobs/graph` job can run.\n\nEach entry is parsed straight from the `templates/*.yaml` source so the\nlist stays live — a new template file shows up here (and in the Intel app's\nResearch catalog) with no code change. The `inputs` array mirrors each\ntemplate's declared parameters so a client can render a submit form from it.\n\nReturns `{templates: [{name, description, version, category, inputs:\n[{name, type, required, default, description}]}], total}`.","operationId":"list_graph_templates_api_v1_jobs_templates_get","responses":{"200":{"description":"Catalog of available graph templates","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Graph Templates Api V1 Jobs Templates Get"}}}}}}},"/cbintel/api/v1/jobs/transcript":{"post":{"tags":["cbintel","jobs"],"summary":"Submit YouTube transcript job","description":"Submit a YouTube transcript processing job.\n\n## Slop in, clean out — channel vs video\n\nIf the input is a **channel** reference (`@handle`, `youtube.com/@handle`,\n`/channel/UC…`, `/c/Name`, `/user/Name`) it is auto-routed to channel\n**enrollment** (the slow-drip flow that enumerates + transcribes the\nchannel's videos) and an `EnrollResponse` is returned — instead of failing\nthe single-video resolver with a cryptic \"could not extract video ID\".\nAnything else (video id/URL, short link, embed page) takes the video path.\n\n## What This Job Does\n\n1. **URL Resolution** - Resolves video IDs from various URL formats\n   - Direct video IDs: `dQw4w9WgXcQ`\n   - YouTube URLs: `youtube.com/watch?v=...`, `youtu.be/...`\n   - Shortened URLs: `bit.ly/...`, `t.co/...` (follows redirects)\n   - Pages with embeds: Extracts video ID from embedded players\n2. **Transcript Fetch** - Retrieves transcript with timestamps\n3. **Content Classification** - AI-powered detection of content type\n4. **Structured Summary** - Generates type-specific structured output\n\n## Content Types\n\n| Type | Output Features |\n|------|-----------------|\n| `debate` | Speaker attribution, key arguments, points of agreement/disagreement |\n| `lecture` | Chapters with timestamps, key concepts, prerequisites |\n| `tutorial` | Numbered steps, code examples, common issues |\n| `interview` | Q&A pairs, key insights, quotable moments |\n| `conversation` | Topics discussed, participant statements, highlights |\n| `general` | Summary, key points, notable timestamps |\n\n## Use Cases\n\n- **Research**: Summarize educational content with chapters\n- **Meeting Notes**: Extract speaker statements from recorded discussions\n- **Tutorial Guides**: Convert video tutorials to step-by-step docs\n- **Interview Summaries**: Extract Q&A pairs from podcasts\n\n## Response\n\nReturns a `job_id` for polling. When complete, results include:\n- `result.json` - Full structured response with metadata\n- `summary.md` - Markdown document with formatted summary\n- `raw_transcript.txt` - Plain text transcript with timestamps\n- `subtitles.srt` - SRT subtitle file for video players","operationId":"submit_transcript_job_api_v1_jobs_transcript_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__TranscriptJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbintel__JobSubmitResponse"},{"$ref":"#/components/schemas/cbintel__EnrollResponse"}],"title":"Response Submit Transcript Job Api V1 Jobs Transcript Post"}}}},"201":{"description":"Job queued successfully"},"400":{"description":"Invalid request parameters","content":{"application/json":{"examples":{"empty_url":{"value":{"detail":"url: String should have at least 1 character"}},"invalid_url":{"value":{"detail":"Could not extract video ID from URL"}},"no_transcript":{"value":{"detail":"No transcript available for this video"}}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","content_type"],"msg":"Input should be 'debate', 'lecture', 'tutorial', 'interview', 'conversation' or 'general'"}]}}}},"503":{"description":"Service unavailable","content":{"application/json":{"example":{"detail":"YouTube API unavailable"}}}}}}},"/cbintel/api/v1/jobs/vectl":{"post":{"tags":["cbintel","jobs"],"summary":"Submit vector embedding job","description":"Submit a vector embedding or search job.\n\n## Operations\n\n| Operation | Description |\n|-----------|-------------|\n| `embed` | Generate embedding for a single text |\n| `batch_embed` | Generate embeddings for multiple texts |\n| `search` | Find similar documents by semantic meaning |\n| `index` | Index documents from a directory |\n\n## What This Job Does\n\n1. **Embedding Generation** - Converts text to 768-dimensional vectors\n2. **Vector Storage** - Stores vectors in high-performance C++ backend\n3. **Similarity Search** - Finds nearest neighbors by cosine similarity\n4. **Result Ranking** - Returns top-k most similar documents\n\n## Use Cases\n\n- **Semantic Search**: \"Find documents similar to this query\"\n- **Document Clustering**: \"Group similar documents together\"\n- **Recommendation**: \"Find related content\"\n- **Deduplication**: \"Identify near-duplicate documents\"\n\n## Model\n\nUses Ollama with `nomic-embed-text` model (768 dimensions).\n\n## Response\n\nReturns a `job_id` for polling. When complete, results include:\n- embedding.json or embeddings.json - Generated vectors\n- search_results.json - Similarity search results (for search operation)","operationId":"submit_vectl_job_api_v1_jobs_vectl_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__VectlJobRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__JobSubmitResponse"}}}},"201":{"description":"Job queued successfully"},"400":{"description":"Invalid request parameters","content":{"application/json":{"examples":{"missing_texts":{"value":{"detail":"texts required for embed/batch_embed operations"}},"missing_query":{"value":{"detail":"query required for search operation"}},"invalid_operation":{"value":{"detail":"operation must be one of: embed, search, batch_embed, index"}}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","k"],"msg":"Input should be less than or equal to 100"}]}}}},"503":{"description":"Service unavailable","content":{"application/json":{"example":{"detail":"Ollama embedding service unavailable"}}}}}}},"/cbintel/api/v1/jobs/{job_id}":{"get":{"tags":["cbintel","jobs"],"summary":"Get job status","description":"Poll job status and retrieve results.\n\n## Status Values\n\n| Status | Description |\n|--------|-------------|\n| `queued` | Job submitted, waiting for worker |\n| `processing` | Worker is executing the job |\n| `completed` | Job finished successfully |\n| `failed` | Job failed with error |\n| `cancelled` | Job was cancelled |\n\n## Polling Strategy\n\nRecommended polling interval: **5 seconds**\n\n```python\nwhile True:\n    status = GET / api / v1 / jobs / {job_id}\n    if status.status in [\"completed\", \"failed\", \"cancelled\"]:\n        break\n    time.sleep(5)\n```\n\n## Response Fields\n\n- **progress** - Available during `processing` (stage, current, total, message)\n- **result** - Available when `completed` (result_url, summary, output_paths)\n- **error** - Available when `failed` (error message)","operationId":"get_job_status_api_v1_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Job status and results (if completed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__JobStatusResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"example":{"detail":"Job not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"delete":{"tags":["cbintel","jobs"],"summary":"Delete job and its artifacts, files, and vectors","description":"Delete a job and all its data.\n\nCancels the job if running, then removes: vectl vectors, MinIO files,\nRedis state. Use `?dry_run=true` to preview without deleting.\n\nIf the worker cannot be confirmed stopped, **nothing is deleted** and the\nreceipt lists the job under `jobs_still_running` with an entry in `errors`.\nThe record is deliberately kept — it is the only handle for observing or\nre-cancelling the job (cbintel#244). Retry once it ends, or `?force=true`.\n`jobs_cancelled` counts only workers confirmed stopped.","operationId":"delete_job_api_v1_jobs__job_id__delete","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","description":"Preview what would be deleted","default":false,"title":"Dry Run"},"description":"Preview what would be deleted"},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Delete even if the worker could not be confirmed stopped","default":false,"title":"Force"},"description":"Delete even if the worker could not be confirmed stopped"}],"responses":{"200":{"description":"Job deleted","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Job Api V1 Jobs  Job Id  Delete"}}}},"404":{"description":"Job not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/models":{"get":{"tags":["cbintel","models"],"summary":"List available models","description":"Returns all known models, optionally filtered by tags, provider, task, or availability.","operationId":"list_models_api_v1_models_get","parameters":[{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/cbintel__ModelTag"}},{"type":"null"}],"description":"Filter by tag(s) — all must match","title":"Tag"},"description":"Filter by tag(s) — all must match"},{"name":"provider","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by provider (ollama, claude, mistral)","title":"Provider"},"description":"Filter by provider (ollama, claude, mistral)"},{"name":"task","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbintel__TaskType"},{"type":"null"}],"description":"Filter/rank by task type","title":"Task"},"description":"Filter/rank by task type"},{"name":"available","in":"query","required":false,"schema":{"type":"boolean","description":"Only show models currently available","default":false,"title":"Available"},"description":"Only show models currently available"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ModelListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/models/recommend":{"get":{"tags":["cbintel","models"],"summary":"Recommend models for a task","description":"Returns the best available models for a given task, ranked by suitability.","operationId":"recommend_api_v1_models_recommend_get","parameters":[{"name":"task","in":"query","required":true,"schema":{"$ref":"#/components/schemas/cbintel__TaskType","description":"Task type to optimize for"},"description":"Task type to optimize for"},{"name":"provider","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict to a specific provider","title":"Provider"},"description":"Restrict to a specific provider"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10,"minimum":1,"description":"Max recommendations","default":3,"title":"Limit"},"description":"Max recommendations"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ModelRecommendation"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/models/tags":{"get":{"tags":["cbintel","models"],"summary":"List all tags","description":"Returns the tag taxonomy with descriptions.","operationId":"list_tags_api_v1_models_tags_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Tags Api V1 Models Tags Get"}}}}}}},"/cbintel/api/v1/models/{model_id}":{"get":{"tags":["cbintel","models"],"summary":"Get a specific model","operationId":"get_model_api_v1_models__model_id__get","parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","title":"Model Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ModelCard"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/paths/health":{"get":{"tags":["cbintel","paths"],"summary":"Per-path fetch health (worst-status first)","description":"Roll fetch attempts up into ``origin → handler → egress → host`` health.\n\nEach path reports attempt count, ok-rate, avg latency, last-seen, a\n``status`` of ``OK | DEGRADED | FAILING | UNAVAIL``, and a ``reason``\n(bot-walls / errors / skips + the most recent error).","operationId":"health_api_v1_paths_health_get","parameters":[{"name":"since_hours","in":"query","required":false,"schema":{"type":"number","maximum":720,"exclusiveMinimum":0,"description":"look-back window","default":24.0,"title":"Since Hours"},"description":"look-back window"},{"name":"origin","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"crawl | websearch | solve | fetch","title":"Origin"},"description":"crawl | websearch | solve | fetch"},{"name":"min_n","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"drop paths below N attempts","default":1,"title":"Min N"},"description":"drop paths below N attempts"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Health Api V1 Paths Health Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/paths/recent":{"get":{"tags":["cbintel","paths"],"summary":"Raw tail of fetch attempts","description":"Newest-first raw attempts (one row per rung tried).","operationId":"recent_api_v1_paths_recent_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"origin","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Origin"}},{"name":"host","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Host"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Recent Api V1 Paths Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/probe/drift":{"get":{"tags":["cbintel","probe"],"summary":"Get Drift","description":"Per-target ok-rate delta between the latest run and one N runs ago.\n\n``versus=1`` (default) compares latest vs. previous run; ``versus=7``\ncompares against ~a week ago when scheduled nightly. Designed for the\nLoom drift card.","operationId":"get_drift_api_v1_probe_drift_get","parameters":[{"name":"versus","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Versus"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Drift Api V1 Probe Drift Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/probe/progress":{"get":{"tags":["cbintel","probe"],"summary":"Get Progress","description":"Return the live progress JSON for the most recent run.\n\nUpdated after every cell by the runner. 404 if no run has produced a\nprogress file yet.","operationId":"get_progress_api_v1_probe_progress_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Progress Api V1 Probe Progress Get"}}}}}}},"/cbintel/api/v1/probe/runs":{"get":{"tags":["cbintel","probe"],"summary":"List Runs","description":"Return the most recent runs (newest first).","operationId":"list_runs_api_v1_probe_runs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Runs Api V1 Probe Runs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/probe/runs/latest":{"get":{"tags":["cbintel","probe"],"summary":"Latest Run","description":"Return summary stats for the most recent completed run.","operationId":"latest_run_api_v1_probe_runs_latest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Latest Run Api V1 Probe Runs Latest Get"}}}}}}},"/cbintel/api/v1/probe/runs/{run_id}/matrix":{"get":{"tags":["cbintel","probe"],"summary":"Get Matrix","description":"One row per (target, browser, egress) — same pivot as the CLI report.","operationId":"get_matrix_api_v1_probe_runs__run_id__matrix_get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get Matrix Api V1 Probe Runs  Run Id  Matrix Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/probe/runs/{run_id}/results":{"get":{"tags":["cbintel","probe"],"summary":"Get Run Results","description":"Raw rows, with optional axis filters.","operationId":"get_run_results_api_v1_probe_runs__run_id__results_get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}},{"name":"target","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target"}},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Browser"}},{"name":"egress","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Egress"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":500,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get Run Results Api V1 Probe Runs  Run Id  Results Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/probe/runs/{run_id}/summary":{"get":{"tags":["cbintel","probe"],"summary":"Get Run Summary","description":"Aggregate counts + averages for a run.","operationId":"get_run_summary_api_v1_probe_runs__run_id__summary_get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Run Summary Api V1 Probe Runs  Run Id  Summary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/publications":{"get":{"tags":["cbintel","Publications"],"summary":"List Publications","description":"Every doc published through the editorial chain, newest activity first.","operationId":"list_publications_api_v1_publications_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Publications Api V1 Publications Get"}}}}}}},"/cbintel/api/v1/publications/{slug}":{"get":{"tags":["cbintel","Publications"],"summary":"Get Publication","description":"One slug's full version history.","operationId":"get_publication_api_v1_publications__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Publication Api V1 Publications  Slug  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/publications/{slug}/assets":{"get":{"tags":["cbintel","Releases"],"summary":"List Assets For Slug","operationId":"list_assets_for_slug_api_v1_publications__slug__assets_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Assets For Slug Api V1 Publications  Slug  Assets Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"post":{"tags":["cbintel","Releases"],"summary":"Register Asset For Slug","description":"Register an asset already bound to this publication.","operationId":"register_asset_for_slug_api_v1_publications__slug__assets_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__RegisterAssetRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Register Asset For Slug Api V1 Publications  Slug  Assets Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/publications/{slug}/releases":{"post":{"tags":["cbintel","Releases"],"summary":"Open Release","description":"Open a release cycle for a publication (one open cycle per slug).","operationId":"open_release_api_v1_publications__slug__releases_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CreateReleaseRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Open Release Api V1 Publications  Slug  Releases Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"get":{"tags":["cbintel","Releases"],"summary":"List Releases For Slug","operationId":"list_releases_for_slug_api_v1_publications__slug__releases_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Releases For Slug Api V1 Publications  Slug  Releases Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/releases/inbox":{"get":{"tags":["cbintel","Releases"],"summary":"Inbox","description":"The needs-attention queue: what a human should look at next.","operationId":"inbox_api_v1_releases_inbox_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Inbox Api V1 Releases Inbox Get"}}}}}}},"/cbintel/api/v1/releases/{rel_id}":{"get":{"tags":["cbintel","Releases"],"summary":"Get Release","operationId":"get_release_api_v1_releases__rel_id__get","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Release Api V1 Releases  Rel Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"delete":{"tags":["cbintel","Releases"],"summary":"Delete Release","operationId":"delete_release_api_v1_releases__rel_id__delete","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/releases/{rel_id}/artifacts":{"post":{"tags":["cbintel","Releases"],"summary":"Add Artifacts","description":"Record emitted-manifest rows (publish.sh calls this as it goes).\n\nA ``memo`` artifact landing while the release is building auto-advances\nit to review — the memo IS the review transition.","operationId":"add_artifacts_api_v1_releases__rel_id__artifacts_post","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__AddArtifactsRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Artifacts Api V1 Releases  Rel Id  Artifacts Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"get":{"tags":["cbintel","Releases"],"summary":"Get Artifacts","operationId":"get_artifacts_api_v1_releases__rel_id__artifacts_get","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Artifacts Api V1 Releases  Rel Id  Artifacts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/releases/{rel_id}/findings/{finding_id}/disposition":{"post":{"tags":["cbintel","Releases"],"summary":"Disposition Finding","description":"Accept/reject a finding — writes the memo worksheet checkbox.","operationId":"disposition_finding_api_v1_releases__rel_id__findings__finding_id__disposition_post","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}},{"name":"finding_id","in":"path","required":true,"schema":{"type":"string","title":"Finding Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__DispositionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Disposition Finding Api V1 Releases  Rel Id  Findings  Finding Id  Disposition Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/releases/{rel_id}/gates/{gate}":{"post":{"tags":["cbintel","Releases"],"summary":"Set Gate","operationId":"set_gate_api_v1_releases__rel_id__gates__gate__post","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}},{"name":"gate","in":"path","required":true,"schema":{"type":"string","title":"Gate"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__GateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Gate Api V1 Releases  Rel Id  Gates  Gate  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/releases/{rel_id}/readiness":{"get":{"tags":["cbintel","Releases"],"summary":"Get Readiness","description":"Pre-build checklist — gates + ledger/markdown/version checks.","operationId":"get_readiness_api_v1_releases__rel_id__readiness_get","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Readiness Api V1 Releases  Rel Id  Readiness Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/releases/{rel_id}/reopen":{"post":{"tags":["cbintel","Releases"],"summary":"Reopen","description":"closed → review — corrections only.","operationId":"reopen_api_v1_releases__rel_id__reopen_post","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ReasonRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reopen Api V1 Releases  Rel Id  Reopen Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/releases/{rel_id}/review":{"get":{"tags":["cbintel","Releases"],"summary":"Get Review","description":"The current cut's Dishaw findings with disposition state.","operationId":"get_review_api_v1_releases__rel_id__review_get","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Review Api V1 Releases  Rel Id  Review Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/releases/{rel_id}/send-back":{"post":{"tags":["cbintel","Releases"],"summary":"Send Back","description":"review → rerun: accepted findings ride the next cut.","operationId":"send_back_api_v1_releases__rel_id__send_back_post","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ReasonRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Send Back Api V1 Releases  Rel Id  Send Back Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/releases/{rel_id}/ship":{"post":{"tags":["cbintel","Releases"],"summary":"Ship","description":"review → closed. 422 SHIP_BLOCKED unless force (+reason).\n\nMinting is not sending: this is the send gate. Distribution itself still\nruns through intel-distribute (email/SMS/links) — record how in\n``shipped_via``.","operationId":"ship_api_v1_releases__rel_id__ship_post","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ShipRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Ship Api V1 Releases  Rel Id  Ship Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/releases/{rel_id}/trigger-build":{"post":{"tags":["cbintel","Releases"],"summary":"Trigger Build","description":"prep|rerun → building. 422 READINESS_FAIL unless force (+reason).","operationId":"trigger_build_api_v1_releases__rel_id__trigger_build_post","parameters":[{"name":"rel_id","in":"path","required":true,"schema":{"type":"string","title":"Rel Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__TriggerBuildRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Trigger Build Api V1 Releases  Rel Id  Trigger Build Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/screenshot-sync":{"post":{"tags":["cbintel","images"],"summary":"Capture a single screenshot synchronously and return bytes inline","description":"Sync Playwright capture for time-bounded callers (cbloom brand pipeline).\n\nPins ``prefers-color-scheme`` so light/dark renders are consistent\nacross browsers + sites. domcontentloaded + 2s settle — strict\nnetworkidle stalls on sites with persistent analytics/chat.","operationId":"screenshot_sync_api_v1_screenshot_sync_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ScreenshotSyncRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ScreenshotSyncResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/sos/documents":{"get":{"tags":["cbintel","Sos"],"summary":"List documents with filters","description":"Paginated, filterable list of documents.\n\nCombine filters freely — e.g. ``?state=MI&kind=rule&q=absentee`` for\n\"all Michigan rule documents matching 'absentee'\".","operationId":"list_documents_api_v1_sos_documents_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state code (case-insensitive)","title":"State"},"description":"Filter by state code (case-insensitive)"},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by classifier kind","title":"Kind"},"description":"Filter by classifier kind"},{"name":"min_confidence","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"description":"Minimum classifier confidence","default":0.0,"title":"Min Confidence"},"description":"Minimum classifier confidence"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Full-text search across title + summary + text","title":"Q"},"description":"Full-text search across title + summary + text"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__DocumentList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/sos/documents/{document_id}":{"get":{"tags":["cbintel","Sos"],"summary":"Fetch a single document with full text + entities","description":"Returns the full ``text`` payload plus extracted entities as lists.","operationId":"get_document_api_v1_sos_documents__document_id__get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__DocumentDetail"}}}},"404":{"description":"No document with that ID"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/sos/kinds":{"get":{"tags":["cbintel","Sos"],"summary":"List distinct classifier labels present in the corpus","operationId":"list_kinds_api_v1_sos_kinds_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Response List Kinds Api V1 Sos Kinds Get"}}}}}}},"/cbintel/api/v1/sos/search":{"get":{"tags":["cbintel","Sos"],"summary":"Semantic search across the corpus","description":"Embed the query via ``nomic-embed-text`` and rank documents by\ncosine similarity. Per-state vectl stores; when ``state`` is omitted\nwe federate the search across every state with a store.\n\nResults return the same shape as ``GET /documents`` (compact list view,\nno full ``text``) plus a ``score`` field. Fetch\n``/documents/{id}`` to get the full text for any hit.","operationId":"semantic_search_api_v1_sos_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"description":"Natural-language query — e.g. 'absentee ballot deadlines'","title":"Q"},"description":"Natural-language query — e.g. 'absentee ballot deadlines'"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Limit to one state (case-insensitive). Omit to search all.","title":"State"},"description":"Limit to one state (case-insensitive). Omit to search all."},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Limit to one classifier kind (rule/regulation/form/...)","title":"Kind"},"description":"Limit to one classifier kind (rule/regulation/form/...)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__cbintel__sos__api__SearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/sos/states":{"get":{"tags":["cbintel","Sos"],"summary":"List states with data in the corpus","description":"Distinct state codes present in ``sos_documents``.","operationId":"list_states_api_v1_sos_states_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Response List States Api V1 Sos States Get"}}}}}}},"/cbintel/api/v1/sos/stats":{"get":{"tags":["cbintel","Sos"],"summary":"Corpus stats — counts by state × kind","description":"Aggregate row counts and average confidence by state × kind.\n\nUseful for an app-team's dashboard / faceted-browse UI.","operationId":"stats_api_v1_sos_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__StatsResponse"}}}}}}},"/cbintel/api/v1/tuner/alerts":{"get":{"tags":["cbintel","Tuner"],"summary":"Recent reach-jump alerts (newest first)","description":"The reach-jump feed the warm job logs — each entry names the watch, the\nspiking hour, and how far above baseline it jumped. Newest first.","operationId":"list_alerts_api_v1_tuner_alerts_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Alerts Api V1 Tuner Alerts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/tuner/discover":{"get":{"tags":["cbintel","Tuner"],"summary":"Recurring spots (candidate campaign airings)","description":"Cluster recurring copy across the recent corpus and label each ad /\nprogram / unknown. Default ``kind=ad`` returns just the looping spots\n(campaign signal); each carries n_times (temporal recurrence), n_stations\n(reach), and the ad/program language scores behind the label.\n\nClusters only the most-recent ``window`` rows (the recurring-now signal)\nand caches per (kind, min_stations, window, hours, q) so it stays responsive\ninstead of re-running the ~O(n²) clustering on every hit.\n\n``hours`` restricts clustering to captures from the last N hours (the\ntime-window control); ``window`` still caps the row count for O(n²) safety.\n``q`` seeds a cluster on a search term (spans all kinds) — use it to track\nthe recurring copy + reach of a topic / candidate / brand by name.","operationId":"discover_api_v1_tuner_discover_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","description":"ad | program | unknown | all","default":"ad","title":"Kind"},"description":"ad | program | unknown | all"},{"name":"min_stations","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Min Stations"}},{"name":"window","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":100,"description":"most-recent rows to cluster","default":1500,"title":"Window"},"description":"most-recent rows to cluster"},{"name":"hours","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":8760,"minimum":1},{"type":"null"}],"description":"only cluster captures from the last N hours (time window)","title":"Hours"},"description":"only cluster captures from the last N hours (time window)"},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"seed a cluster on a search term — cluster only captures whose text matches all of q's tokens, across ALL kinds (ignores `kind`)","title":"Q"},"description":"seed a cluster on a search term — cluster only captures whose text matches all of q's tokens, across ALL kinds (ignores `kind`)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Discover Api V1 Tuner Discover Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/tuner/discover/warm":{"post":{"tags":["cbintel","Tuner"],"summary":"Pre-warm the recurring-spots cache (cron)","description":"Recompute + cache the standard time-window combos AND every saved watch\nterm so the Chatter Signals view loads instantly. Driven hourly by cbcron;\nthe windowed cache then serves the warmed clusters for the hour.","operationId":"discover_warm_api_v1_tuner_discover_warm_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Discover Warm Api V1 Tuner Discover Warm Post"}}}}}}},"/cbintel/api/v1/tuner/inventory":{"get":{"tags":["cbintel","Tuner"],"summary":"Full station inventory (~15.4k US AM/FM) + status","description":"The whole funnel, not just stations we've heard: every targeted US AM/FM\nstation (~15.4k) with a coverage ``status`` — ``heard`` (≥1 transcript),\n``resolved`` (playable stream, not yet heard), or ``no_stream`` (targeted,\nno stream found yet). Served from an hourly snapshot of the drip inventory\n(drip.duckdb is writer-locked by the live tick, so it can't be read at\nrequest time). Returns ``{total, counts, returned, stations:[...]}`` where\n``counts`` is the unfiltered funnel breakdown so the UI can always show the\nheadline coverage numbers.","operationId":"inventory_api_v1_tuner_inventory_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: heard | resolved | no_stream.","title":"Status"},"description":"Filter: heard | resolved | no_stream."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"2-letter state code (e.g. GA).","title":"State"},"description":"2-letter state code (e.g. GA)."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Substring match on callsign / city / owner.","title":"Q"},"description":"Substring match on callsign / city / owner."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20000,"minimum":1,"default":20000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Inventory Api V1 Tuner Inventory Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/tuner/stations":{"get":{"tags":["cbintel","Tuner"],"summary":"Per-station coverage","description":"One row per station: capture count, transcription success, configured\negress (direct/impera), and the most recent transcript. Use it to spot a\nstation whose stream went dead (success_rate cratering) at a glance.","operationId":"stations_api_v1_tuner_stations_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbintel__StationCoverage"},"type":"array","title":"Response Stations Api V1 Tuner Stations Get"}}}}}}},"/cbintel/api/v1/tuner/stats":{"get":{"tags":["cbintel","Tuner"],"summary":"Corpus overview","description":"Top-line corpus health: how many captures, how many transcribed, the\nsuccess rate (a low value means cbstt is dropping work), and the time span.","operationId":"stats_api_v1_tuner_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CorpusStats"}}}}}}},"/cbintel/api/v1/tuner/transcripts":{"get":{"tags":["cbintel","Tuner"],"summary":"Filterable transcript list","description":"Recent transcripts, newest first. Filter by callsign, status, and/or a\nkeyword query over the transcript text — the war-room's search over the\nradio corpus (candidate names, \"runoff\", turnout messaging, …).","operationId":"transcripts_api_v1_tuner_transcripts_get","parameters":[{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one callsign.","title":"Callsign"},"description":"Filter to one callsign."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status (e.g. succeeded).","title":"Status"},"description":"Filter by status (e.g. succeeded)."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keyword search over transcript text — case-insensitive, space-separated terms ALL must appear (AND). e.g. q=ossoff runoff.","title":"Q"},"description":"Keyword search over transcript text — case-insensitive, space-separated terms ALL must appear (AND). e.g. q=ossoff runoff."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbintel__TranscriptRow"},"title":"Response Transcripts Api V1 Tuner Transcripts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/tuner/verify":{"get":{"tags":["cbintel","Tuner"],"summary":"Ad-match: where + when a spot aired","description":"Detect a known spot across the corpus. Returns one entry per airing\n(station, timestamp, match score, egress) — the verification record.","operationId":"verify_api_v1_tuner_verify_get","parameters":[{"name":"spot","in":"query","required":true,"schema":{"type":"string","minLength":4,"description":"Reference spot copy/tagline.","title":"Spot"},"description":"Reference spot copy/tagline."},{"name":"threshold","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"default":0.6,"title":"Threshold"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbintel__Airing"},"title":"Response Verify Api V1 Tuner Verify Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/tuner/watches":{"get":{"tags":["cbintel","Tuner"],"summary":"List saved watches","description":"The persisted watchlist — named term-clusters kept warm hourly, each with\nits hourly reach ``trend`` (sparkline) and live ``alert`` (reach-jump).","operationId":"list_watches_api_v1_tuner_watches_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbintel__Watch"},"type":"array","title":"Response List Watches Api V1 Tuner Watches Get"}}}}}},"post":{"tags":["cbintel","Tuner"],"summary":"Save a named term-cluster","description":"Persist a term as a named watch and warm it immediately so its first\nview is instant. De-dupes on (term, hours): re-saving updates the name.","operationId":"create_watch_api_v1_tuner_watches_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__WatchCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__Watch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/tuner/watches/{watch_id}":{"delete":{"tags":["cbintel","Tuner"],"summary":"Delete a saved watch","description":"Remove a watch by id.","operationId":"delete_watch_api_v1_tuner_watches__watch_id__delete","parameters":[{"name":"watch_id","in":"path","required":true,"schema":{"type":"string","title":"Watch Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Watch Api V1 Tuner Watches  Watch Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/tuner/watches/{watch_id}/backfill":{"post":{"tags":["cbintel","Tuner"],"summary":"Backfill a watch's reach trend","description":"Reconstruct ``hours_back`` hours of a watch's reach trend from the corpus\n(overwrites its existing trend). Use to deepen a watch's sparkline beyond the\nwindow seeded at create, or to repopulate after a gap.","operationId":"backfill_watch_api_v1_tuner_watches__watch_id__backfill_post","parameters":[{"name":"watch_id","in":"path","required":true,"schema":{"type":"string","title":"Watch Id"}},{"name":"hours_back","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"description":"how many past hours to reconstruct","default":48,"title":"Hours Back"},"description":"how many past hours to reconstruct"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Backfill Watch Api V1 Tuner Watches  Watch Id  Backfill Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/usage/":{"get":{"tags":["cbintel","usage"],"summary":"List usage records","description":"Query API usage records with filtering and pagination.\n\n**Filters:**\n- `user_id`: Filter by specific user\n- `operation`: Filter by operation type\n- `start_date`/`end_date`: Filter by time range\n- `success`: Filter by success status\n- `has_vpn`: Filter by VPN usage\n\nRecords are returned newest-first.","operationId":"list_usage_api_v1_usage__get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by user ID","title":"User Id"},"description":"Filter by user ID"},{"name":"operation","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbintel__OperationType"},{"type":"null"}],"description":"Filter by operation type","title":"Operation"},"description":"Filter by operation type"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of time range (ISO format)","title":"Start Date"},"description":"Start of time range (ISO format)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of time range (ISO format)","title":"End Date"},"description":"End of time range (ISO format)"},{"name":"success","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by success status","title":"Success"},"description":"Filter by success status"},{"name":"has_vpn","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by VPN usage","title":"Has Vpn"},"description":"Filter by VPN usage"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number","default":1,"title":"Page"},"description":"Page number"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Results per page","default":50,"title":"Page Size"},"description":"Results per page"}],"responses":{"200":{"description":"Paginated usage records","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__UsageResponse"},"example":{"records":[{"request_id":"req_abc123","timestamp":"2026-02-27T10:30:00Z","user_id":"apikey_xyz","operation":"fetch","path":"/api/v1/fetch","method":"POST","status_code":200,"duration_ms":342,"success":true}],"total":1,"page":1,"page_size":50}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/usage/summary":{"get":{"tags":["cbintel","usage"],"summary":"Get usage summary","description":"Get aggregated usage statistics for a time period.\n\n**Summary includes:**\n- Request counts (total, successful, failed)\n- Latency statistics (avg, min, max, p95)\n- Breakdown by operation type\n- Unique users and request counts per user\n- VPN usage statistics (requests, countries)\n- Traffic totals (bytes sent/received)\n\n**Period types:**\n- `hour`: Last hour or specified hour\n- `day`: Last 24 hours (default)\n- `week`: Last 7 days\n- `month`: Last 30 days","operationId":"get_usage_summary_api_v1_usage_summary_get","parameters":[{"name":"period_type","in":"query","required":false,"schema":{"enum":["hour","day","week","month"],"type":"string","description":"Aggregation period","default":"day","title":"Period Type"},"description":"Aggregation period"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Custom start date (overrides period_type)","title":"Start Date"},"description":"Custom start date (overrides period_type)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Custom end date","title":"End Date"},"description":"Custom end date"}],"responses":{"200":{"description":"Aggregated usage statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__UsageSummary"},"example":{"period_type":"day","period_start":"2026-02-26T10:30:00Z","period_end":"2026-02-27T10:30:00Z","total_requests":1247,"successful_requests":1189,"failed_requests":58,"avg_duration_ms":420,"by_operation":{"fetch":800,"crawl":200,"graph":150},"unique_users":3}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/usage/users/":{"get":{"tags":["cbintel","usage"],"summary":"List tracked users","description":"Get all users who have made API requests.","operationId":"list_users_api_v1_usage_users__get","responses":{"200":{"description":"List of tracked users","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbintel__UserInfo"},"type":"array","title":"Response List Users Api V1 Usage Users  Get"},"example":[{"user_id":"apikey_xyz","first_seen":"2026-01-15T08:00:00Z","last_seen":"2026-02-27T10:30:00Z","request_count":4521}]}}}}}},"/cbintel/api/v1/usage/users/{user_id}":{"get":{"tags":["cbintel","usage"],"summary":"Get user info","description":"Get detailed information about a specific API user.","operationId":"get_user_api_v1_usage_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"User details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__UserInfo"},"example":{"user_id":"apikey_xyz","first_seen":"2026-01-15T08:00:00Z","last_seen":"2026-02-27T10:30:00Z","request_count":4521}}}},"404":{"description":"User not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/usage/{request_id}":{"get":{"tags":["cbintel","usage"],"summary":"Get usage record","description":"Get a specific usage record by its request ID.","operationId":"get_usage_record_api_v1_usage__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Single usage record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__UsageRecord"},"example":{"request_id":"req_abc123","timestamp":"2026-02-27T10:30:00Z","user_id":"apikey_xyz","operation":"fetch","path":"/api/v1/fetch","method":"POST","status_code":200,"duration_ms":342,"success":true}}}},"404":{"description":"Usage record not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"delete":{"tags":["cbintel","usage"],"summary":"Delete usage record","description":"Delete a specific usage record. Returns 204 on success.","operationId":"delete_usage_record_api_v1_usage__request_id__delete","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Record deleted","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Usage Record Api V1 Usage  Request Id  Delete"},"example":{"status":"deleted","request_id":"req_abc123"}}}},"404":{"description":"Usage record not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/vectl/local/search":{"post":{"tags":["cbintel","jobs"],"summary":"Local semantic search against a workspace vector store","description":"Semantic search against the local per-workspace vectl store.\n\nEmbeds the query via ollama (``nomic-embed-text``) and ranks the\nworkspace's vectors by cosine similarity. Independent of cbfiles —\nthis is the path used by anything that landed in vectl through\ncbintel's ``crawl_operation`` / ``store_chunks`` flow.","operationId":"local_search_api_v1_vectl_local_search_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__LocalSearchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__LocalSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"get":{"tags":["cbintel","jobs"],"summary":"Local semantic search (GET variant)","description":"GET-style local search — same shape as POST, easier from a browser/curl.","operationId":"local_search_get_api_v1_vectl_local_search_get","parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"Query"}},{"name":"store","in":"query","required":false,"schema":{"type":"string","default":"default","title":"Store"}},{"name":"k","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"K"}},{"name":"min_score","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"default":0.0,"title":"Min Score"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__LocalSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/vectl/local/stores":{"get":{"tags":["cbintel","jobs"],"summary":"List local vector stores","description":"Inventory of vector stores on disk under ``/var/lib/cbintel/vectl``.","operationId":"list_local_stores_api_v1_vectl_local_stores_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbintel__LocalStoreInfo"},"type":"array","title":"Response List Local Stores Api V1 Vectl Local Stores Get"}}}}}}},"/cbintel/api/v1/vectl/search":{"get":{"tags":["cbintel","jobs"],"summary":"Semantic search across indexed files","description":"Semantic search via GET (query-string parameters).\n\nProxies to the cbfiles search API at files.campaignbrain.dev.","operationId":"search_get_api_v1_vectl_search_get","parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Search query text","title":"Query"},"description":"Search query text"},{"name":"buckets","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Buckets to search","title":"Buckets"},"description":"Buckets to search"},{"name":"k","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Number of results","default":10,"title":"K"},"description":"Number of results"},{"name":"min_score","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"description":"Minimum similarity","default":0.0,"title":"Min Score"},"description":"Minimum similarity"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__cbintel__jobs__routers__vectl__SearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"post":{"tags":["cbintel","jobs"],"summary":"Semantic search across indexed files","description":"Semantic search via POST (JSON body).\n\nProxies to the cbfiles search API at files.campaignbrain.dev.","operationId":"search_post_api_v1_vectl_search_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__SearchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__cbintel__jobs__routers__vectl__SearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/workspaces":{"post":{"tags":["cbintel","workspaces"],"summary":"Create workspace","description":"Create a new workspace for organizing jobs and artifacts.\n\n## What This Does\n\nCreates a workspace that can:\n- Group related jobs together\n- Organize artifacts under a common prefix\n- Track execution history and statistics\n- Optionally enable document indexing for search\n\n## Use Cases\n\n- **Client Projects**: Create a workspace per client for organization\n- **Research Topics**: Group related research jobs\n- **Campaigns**: Organize all jobs for a campaign\n\n## Response\n\nReturns the created workspace with its ID for use in job submissions.","operationId":"create_workspace_api_v1_workspaces_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CreateWorkspaceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__WorkspaceResponse"}}}},"201":{"description":"Workspace created","content":{"application/json":{"example":{"workspace_id":"ws_abc123","name":"Michigan 2026 Research","description":"Research workspace for Michigan governor's race","status":"active","created_at":"2026-02-27T10:30:00Z","job_count":0,"artifact_count":0,"tags":["michigan","governor"],"index_enabled":false}}}},"400":{"description":"Invalid request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"get":{"tags":["cbintel","workspaces"],"summary":"List workspaces","description":"List all workspaces.\n\n## Query Parameters\n\n- **status**: Filter by status (active, archived)\n\n## Response\n\nReturns list of workspace summaries.","operationId":"list_workspaces_api_v1_workspaces_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}}],"responses":{"200":{"description":"List of workspaces","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__cbintel__jobs__routers__workspaces__WorkspaceListResponse"},"example":{"workspaces":[{"workspace_id":"ws_abc123","name":"Michigan 2026 Research","description":"","status":"active","created_at":"2026-02-20T15:00:00Z","job_count":5,"artifact_count":12,"tags":["michigan","governor"],"index_enabled":false}],"total":1}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/workspaces/{workspace_id}":{"get":{"tags":["cbintel","workspaces"],"summary":"Get workspace","description":"Get workspace details by ID.","operationId":"get_workspace_api_v1_workspaces__workspace_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}}],"responses":{"200":{"description":"Workspace details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__WorkspaceResponse"},"example":{"workspace_id":"ws_abc123","name":"Michigan 2026 Research","description":"Research workspace for Michigan governor's race","status":"active","created_at":"2026-02-27T10:30:00Z","job_count":5,"artifact_count":12,"tags":["michigan","governor"],"index_enabled":false}}}},"404":{"description":"Workspace not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"delete":{"tags":["cbintel","workspaces"],"summary":"Delete workspace and cascade-delete all jobs, artifacts, and vectors","description":"Delete a workspace and everything in it.\n\nCascade order: cancel running jobs → delete vectl vectors → delete MinIO\nfiles → delete Redis state. Use `?dry_run=true` to preview.\n\nIf any worker cannot be confirmed stopped, **nothing is deleted** — see\n`jobs_still_running` on the receipt, or `?force=true` (cbintel#244).","operationId":"delete_workspace_api_v1_workspaces__workspace_id__delete","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","description":"Preview what would be deleted","default":false,"title":"Dry Run"},"description":"Preview what would be deleted"},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Delete even if a worker could not be confirmed stopped","default":false,"title":"Force"},"description":"Delete even if a worker could not be confirmed stopped"}],"responses":{"200":{"description":"Workspace deleted","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Workspace Api V1 Workspaces  Workspace Id  Delete"}}}},"404":{"description":"Workspace not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/workspaces/{workspace_id}/artifacts":{"get":{"tags":["cbintel","workspaces"],"summary":"List workspace artifacts","description":"List all artifacts in a workspace with optional filtering.\n\n## What This Does\n\nReturns artifacts registered during job execution with working public URLs.\nExpired presigned URLs are automatically reconstructed.\n\n## Query Parameters\n\n- **run_id**: Filter artifacts by the job/run that produced them\n- **stage**: Filter by pipeline stage (e.g., 'output', 'fetch', 'screenshot')\n\n## Response\n\nEach artifact includes a stable `content_url` pointing to files.campaignbrain.dev.","operationId":"list_workspace_artifacts_api_v1_workspaces__workspace_id__artifacts_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Id"}},{"name":"stage","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage"}}],"responses":{"200":{"description":"List of artifacts with working URLs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ArtifactListResponse"},"example":{"workspace_id":"ws_abc123","artifacts":[{"artifact_id":"art_001","artifact_type":"json","filename":"crawl_result.json","title":"Crawl Result","size_bytes":45000,"created_at":"2026-02-27T10:35:00Z","run_id":"job_a1b2c3d4","stage":"output","content_url":"https://files.campaignbrain.dev/store/cbintel-jobs/ws_abc123/crawl_result.json","metadata":{}}],"total":1}}}},"404":{"description":"Workspace not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/workspaces/{workspace_id}/artifacts/{artifact_id}":{"get":{"tags":["cbintel","workspaces"],"summary":"Get workspace artifact","description":"Get a single artifact by ID.\n\n## Response\n\nReturns artifact details including a stable `content_url`.","operationId":"get_workspace_artifact_api_v1_workspaces__workspace_id__artifacts__artifact_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}},{"name":"artifact_id","in":"path","required":true,"schema":{"type":"string","title":"Artifact Id"}}],"responses":{"200":{"description":"Artifact details with working URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ArtifactResponse"},"example":{"artifact_id":"art_001","artifact_type":"json","filename":"crawl_result.json","title":"Crawl Result","size_bytes":45000,"created_at":"2026-02-27T10:35:00Z","run_id":"job_a1b2c3d4","stage":"output","content_url":"https://files.campaignbrain.dev/store/cbintel-jobs/ws_abc123/crawl_result.json","metadata":{}}}}},"404":{"description":"Workspace or artifact not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/workspaces/{workspace_id}/jobs":{"get":{"tags":["cbintel","workspaces"],"summary":"List workspace jobs","description":"Get all jobs associated with a workspace.","operationId":"list_workspace_jobs_api_v1_workspaces__workspace_id__jobs_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}}],"responses":{"200":{"description":"List of job IDs","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Workspace Jobs Api V1 Workspaces  Workspace Id  Jobs Get"},"example":{"workspace_id":"ws_abc123","job_ids":["job_a1b2c3d4","job_e5f67890"],"total":2}}}},"404":{"description":"Workspace not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"post":{"tags":["cbintel","workspaces"],"summary":"Add job to workspace","description":"Associate a job with a workspace.","operationId":"add_job_to_workspace_api_v1_workspaces__workspace_id__jobs_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__AddJobRequest"}}}},"responses":{"200":{"description":"Job added","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Job To Workspace Api V1 Workspaces  Workspace Id  Jobs Post"},"example":{"workspace_id":"ws_abc123","job_id":"job_a1b2c3d4","message":"Job added to workspace"}}}},"404":{"description":"Workspace not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/youtube/accounts":{"post":{"tags":["cbintel","YouTube"],"summary":"Enroll Account","description":"Enroll a YouTube channel into the slow-drip transcript flow.\n\nIntent-log design: this endpoint writes ONLY the lock-free enrolled-accounts\nregistry (flock JSON — instant, never contends with the drip writer, no\nmid-tick 503s). The drip tick reconciles the registry into the inventory at\nits next start (``reconcile_registry``), seeding or reviving the account,\nthen backfills the catalog and watch-forwards new uploads. The eager\nchannel resolve (VPN-first) is kept so the caller gets the channel\ntitle/id confirmation and the registry row carries them.","operationId":"enroll_account_api_v1_youtube_accounts_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__EnrollRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__EnrollResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"get":{"tags":["cbintel","YouTube"],"summary":"List Accounts","description":"Enrolled channels with capture progress (lock-free).\n\nMerges the enrolled-accounts registry (so a just-enrolled channel with zero\ncaptures still shows) with the append-only transcript corpus (captured\ncounts, chars, most-recent titles).","operationId":"list_accounts_api_v1_youtube_accounts_get","parameters":[{"name":"recent","in":"query","required":false,"schema":{"type":"integer","maximum":10,"minimum":0,"description":"recent video titles per channel","default":3,"title":"Recent"},"description":"recent video titles per channel"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Accounts Api V1 Youtube Accounts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/youtube/accounts/{account}":{"delete":{"tags":["cbintel","YouTube"],"summary":"Unenroll Account Endpoint","description":"Unenroll a channel from the slow-drip flow — enrollment only.\n\nIntent-log design: drops the registry row (instant, lock-free); the drip\ntick's ``reconcile_registry`` retires the account target and its pending\ntranscript children (state→done, flagged — never deleted) on its next\nstart. Captured transcripts stay in the corpus — still listed, searchable\nand exportable — and re-enrolling the same handle resumes where it left\noff.","operationId":"unenroll_account_endpoint_api_v1_youtube_accounts__account__delete","parameters":[{"name":"account","in":"path","required":true,"schema":{"type":"string","title":"Account"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__UnenrollResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/youtube/export":{"get":{"tags":["cbintel","YouTube"],"summary":"Export Transcripts","description":"Aggregate captured transcripts into one .txt and return a public link.\n\nUploads the concatenated dump to cbfiles (public bucket) and returns its\npermanent ``files.campaignbrain.dev`` URL. Content-addressed: an unchanged\ncorpus re-exports to the same immutable URL, so repeated clicks are cheap.","operationId":"export_transcripts_api_v1_youtube_export_get","parameters":[{"name":"account","in":"query","required":false,"schema":{"type":"string","description":"scope to one channel handle; blank = all channels","default":"","title":"Account"},"description":"scope to one channel handle; blank = all channels"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Export Transcripts Api V1 Youtube Export Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/api/v1/youtube/search":{"get":{"tags":["cbintel","YouTube"],"summary":"Search Transcripts","description":"Full-text search across captured transcripts (newest capture first).","operationId":"search_transcripts_api_v1_youtube_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"keyword(s); ALL space-separated terms must match","default":"","title":"Q"},"description":"keyword(s); ALL space-separated terms must match"},{"name":"account","in":"query","required":false,"schema":{"type":"string","description":"scope to one channel handle","default":"","title":"Account"},"description":"scope to one channel handle"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":30,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Search Transcripts Api V1 Youtube Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/health":{"get":{"tags":["cbintel","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HealthResponse"}}}}}}},"/cbintel/health/detailed":{"get":{"tags":["cbintel","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HealthResponse"}}}}}}},"/cbintel/planner/sessions":{"post":{"tags":["cbintel","planner"],"summary":"Create Session","description":"Create a new research planning session.\n\nStarts a multi-turn research planning conversation:\n1. Extracts entity from goal\n2. Recalls prior knowledge from Scout\n3. Generates initial graph\n4. Returns session with follow-up questions\n\nArgs:\n    request: Session creation request with goal\n    agent: Planner agent\n\nReturns:\n    Created session with initial message and questions","operationId":"create_session_planner_sessions_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CreateSessionRequest"}}}},"responses":{"201":{"description":"Session created with initial questions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CreateSessionResponse"},"example":{"session":{"session_id":"plan_abc123","original_goal":"Research AI regulation in California","current_phase":"planning","entity_name":"AI regulation","entity_type":"topic","turn_count":0},"message":"I've identified this as research about AI regulation. Let me ask a few clarifying questions.","suggested_action":"answer"}}}},"422":{"description":"Validation error"}}},"get":{"tags":["cbintel","planner"],"summary":"List Sessions","description":"List sessions with optional filtering.\n\nArgs:\n    store: Planner store\n    workspace_id: Filter by workspace\n    phase: Filter by phase\n    limit: Maximum sessions to return\n\nReturns:\n    List of sessions","operationId":"list_sessions_planner_sessions_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"}},{"name":"phase","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbintel__PlannerPhase"},{"type":"null"}],"title":"Phase"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"List of planning sessions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbintel__ResearchSession"},"title":"Response List Sessions Planner Sessions Get"},"example":[{"session_id":"plan_abc123","original_goal":"Research AI regulation in California","current_phase":"execution","turn_count":2}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/planner/sessions/{session_id}":{"get":{"tags":["cbintel","planner"],"summary":"Get Session","description":"Get session by ID.\n\nArgs:\n    session_id: Session identifier\n    store: Planner store\n\nReturns:\n    Session details","operationId":"get_session_planner_sessions__session_id__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Session details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ResearchSession"},"example":{"session_id":"plan_abc123","original_goal":"Research AI regulation in California","current_phase":"planning","entity_name":"AI regulation","entity_type":"topic","turn_count":1,"coverage_score":0.35}}}},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"delete":{"tags":["cbintel","planner"],"summary":"Delete Session","description":"Delete a session.\n\nArgs:\n    session_id: Session to delete\n    store: Planner store","operationId":"delete_session_planner_sessions__session_id__delete","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"204":{"description":"Session deleted"},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/planner/sessions/{session_id}/complete":{"post":{"tags":["cbintel","planner"],"summary":"Complete Session","description":"Complete the session and optionally memorize to Scout.\n\nFinalizes the research session:\n1. Generates final summary\n2. Optionally saves findings to Scout memory\n3. Marks session as complete\n\nArgs:\n    session_id: Session identifier\n    request: Completion request\n    agent: Planner agent\n\nReturns:\n    Completed session with memory result","operationId":"complete_session_planner_sessions__session_id__complete_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CompleteRequest"}}}},"responses":{"200":{"description":"Session completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__CompleteResponse"},"example":{"session":{"session_id":"plan_abc123","current_phase":"complete","memorized":true},"message":"Session complete. Findings saved to Scout memory.","memory_result":{"saved":true,"facts_count":15}}}}},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/planner/sessions/{session_id}/execute":{"post":{"tags":["cbintel","planner"],"summary":"Execute Session","description":"Execute the current graph for a session.\n\nStarts graph execution and transitions session to execution phase.\nPoll /sessions/{id} to track execution status.\n\nArgs:\n    session_id: Session identifier\n    _request: Execute request (currently empty)\n    agent: Planner agent\n    redis: Redis client\n\nReturns:\n    Session with job ID","operationId":"execute_session_planner_sessions__session_id__execute_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ExecuteRequest"}}}},"responses":{"200":{"description":"Graph execution started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ExecuteResponse"},"example":{"session":{"session_id":"plan_abc123","current_phase":"execution"},"job_id":"job_a1b2c3d4e5f67890","message":"Executing opponent_deep_dive..."}}}},"400":{"description":"No graph to execute"},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/planner/sessions/{session_id}/gaps":{"get":{"tags":["cbintel","planner"],"summary":"Get Session Gaps","description":"Get identified gaps for a session.\n\nArgs:\n    session_id: Session identifier\n    store: Planner store\n\nReturns:\n    Dictionary of gaps by priority","operationId":"get_session_gaps_planner_sessions__session_id__gaps_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Session gaps by priority","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Session Gaps Planner Sessions  Session Id  Gaps Get"},"example":{"session_id":"plan_abc123","total_gaps":3,"unaddressed":2,"by_priority":{"high":[{"description":"No financial disclosure data","priority":"high"}],"medium":[{"description":"Missing early career history","priority":"medium"}],"low":[]}}}}},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/planner/sessions/{session_id}/respond":{"post":{"tags":["cbintel","planner"],"summary":"Respond To Session","description":"Process user response and advance the workflow.\n\nHandles user input during the planning conversation:\n- Free text responses\n- Answers to specific questions\n- Explicit actions (execute, expand, skip, schedule, complete)\n\nArgs:\n    session_id: Session identifier\n    request: User response\n    agent: Planner agent\n\nReturns:\n    Updated session with next message and questions","operationId":"respond_to_session_planner_sessions__session_id__respond_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__RespondRequest"}}}},"responses":{"200":{"description":"Response processed, session advanced","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__RespondResponse"},"example":{"session":{"session_id":"plan_abc123","current_phase":"planning","turn_count":1},"message":"Thank you. I've refined the research plan based on your input.","questions":["Should we focus on state-level or federal regulations?"],"suggested_action":"answer"}}}},"404":{"description":"Session not found"},"422":{"description":"Validation error"}}}},"/cbintel/planner/sessions/{session_id}/result":{"post":{"tags":["cbintel","planner"],"summary":"Process Result","description":"Process execution result and transition to analysis.\n\nCalled when graph execution completes.\n\nArgs:\n    session_id: Session identifier\n    job_id: Completed job ID\n    result: Job result data\n    agent: Planner agent\n\nReturns:\n    Updated session with analysis","operationId":"process_result_planner_sessions__session_id__result_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"job_id","in":"query","required":true,"schema":{"type":"string","title":"Job Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Result"}}}},"responses":{"200":{"description":"Result processed, session transitioned to analysis","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__RespondResponse"},"example":{"session":{"session_id":"plan_abc123","current_phase":"analysis"},"message":"Execution complete. Here's what we found...","questions":["Would you like to dig deeper into any area?"],"suggested_action":"answer"}}}},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/planner/sessions/{session_id}/results/{turn_number}":{"get":{"tags":["cbintel","planner"],"summary":"Get Turn Result","description":"Get raw execution result for a specific turn.\n\nUse this to fetch the full raw_result which is excluded from\nsession responses due to size.\n\nArgs:\n    session_id: Session identifier\n    turn_number: Turn number (1-indexed)\n    store: Planner store\n\nReturns:\n    Raw result data for the turn","operationId":"get_turn_result_planner_sessions__session_id__results__turn_number__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"turn_number","in":"path","required":true,"schema":{"type":"integer","title":"Turn Number"}}],"responses":{"200":{"description":"Raw execution result for a specific turn","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Turn Result Planner Sessions  Session Id  Results  Turn Number  Get"},"example":{"session_id":"plan_abc123","turn_number":1,"job_id":"job_a1b2c3d4e5f67890","status":"completed","raw_result":{"urls_processed":42,"synthesis":"..."}}}}},"404":{"description":"Session or turn not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/planner/sessions/{session_id}/schedule":{"post":{"tags":["cbintel","planner"],"summary":"Schedule Session","description":"Schedule periodic execution for a session.\n\nCreates a schedule via the Scheduler API to run the research\ngraph periodically.\n\nArgs:\n    session_id: Session identifier\n    request: Schedule request with cron expression\n    agent: Planner agent\n    redis: Redis client\n\nReturns:\n    Session with schedule ID","operationId":"schedule_session_planner_sessions__session_id__schedule_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ScheduleRequest"}}}},"responses":{"200":{"description":"Schedule created for recurring execution","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ScheduleResponse"},"example":{"session":{"session_id":"plan_abc123","schedule_requested":true},"schedule_id":"sched_xyz789","message":"Scheduled to run: 0 9 * * 1-5"}}}},"400":{"description":"No graph to schedule"},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/planner/sessions/{session_id}/summary":{"get":{"tags":["cbintel","planner"],"summary":"Get Session Summary","description":"Get summary of a session.\n\nArgs:\n    session_id: Session identifier\n    store: Planner store\n\nReturns:\n    Session summary","operationId":"get_session_summary_planner_sessions__session_id__summary_get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Session summary","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Session Summary Planner Sessions  Session Id  Summary Get"},"example":{"session_id":"plan_abc123","goal":"Research AI regulation in California","entity":{"name":"AI regulation","type":"topic"},"phase":"complete","turns":3,"coverage":0.85,"facts_discovered":27,"prior_knowledge":{"found":true,"facts":12},"schedule":{"requested":false},"memorized":true}}}},"404":{"description":"Session not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/prime":{"get":{"tags":["cbintel","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__PrimeResponse"}}}}}}},"/cbintel/scheduler/schedules":{"post":{"tags":["cbintel","scheduler"],"summary":"Create Schedule","description":"Create a new schedule.\n\nCreates a schedule that will automatically execute a graph based on\nthe specified cron expression.\n\nArgs:\n    request: Schedule creation request\n    store: Schedule store\n\nReturns:\n    Created schedule","operationId":"create_schedule_scheduler_schedules_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ScheduleCreateRequest"}}}},"responses":{"201":{"description":"Schedule created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__Schedule"},"example":{"schedule_id":"sched_abc123","name":"daily_research","description":"Daily AI regulation research","cron":"0 9 * * 1-5","graph_name":"opponent_deep_dive","params":{"opponent_name":"John Smith"},"status":"enabled","created_at":"2026-02-27T10:00:00Z","next_run_at":"2026-02-28T09:00:00Z","run_count":0}}}},"400":{"description":"Invalid cron expression"},"409":{"description":"Schedule with this name already exists"},"422":{"description":"Validation error"}}},"get":{"tags":["cbintel","scheduler"],"summary":"List Schedules","description":"List all schedules.\n\nArgs:\n    store: Schedule store\n    status: Filter by status\n    tag: Filter by tag\n\nReturns:\n    List of schedule summaries","operationId":"list_schedules_scheduler_schedules_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbintel__ScheduleStatus"},{"type":"null"}],"title":"Status"}},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag"}}],"responses":{"200":{"description":"List of schedules","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ScheduleListResponse"},"example":{"schedules":[{"schedule_id":"sched_abc123","name":"daily_research","cron":"0 9 * * 1-5","graph_name":"opponent_deep_dive","status":"enabled","next_run_at":"2026-02-28T09:00:00Z","run_count":5}],"total":1}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/scheduler/schedules/{schedule_id}":{"get":{"tags":["cbintel","scheduler"],"summary":"Get Schedule","description":"Get schedule by ID.\n\nArgs:\n    schedule_id: Schedule identifier\n    store: Schedule store\n\nReturns:\n    Schedule details","operationId":"get_schedule_scheduler_schedules__schedule_id__get","parameters":[{"name":"schedule_id","in":"path","required":true,"schema":{"type":"string","title":"Schedule Id"}}],"responses":{"200":{"description":"Schedule details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__Schedule"},"example":{"schedule_id":"sched_abc123","name":"daily_research","cron":"0 9 * * 1-5","graph_name":"opponent_deep_dive","params":{"opponent_name":"John Smith"},"status":"enabled","next_run_at":"2026-02-28T09:00:00Z","last_run_at":"2026-02-27T09:00:00Z","run_count":5}}}},"404":{"description":"Schedule not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}},"patch":{"tags":["cbintel","scheduler"],"summary":"Update Schedule","description":"Update schedule configuration.\n\nArgs:\n    schedule_id: Schedule identifier\n    request: Update request\n    store: Schedule store\n\nReturns:\n    Updated schedule","operationId":"update_schedule_scheduler_schedules__schedule_id__patch","parameters":[{"name":"schedule_id","in":"path","required":true,"schema":{"type":"string","title":"Schedule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ScheduleUpdateRequest"}}}},"responses":{"200":{"description":"Schedule updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__Schedule"},"example":{"schedule_id":"sched_abc123","name":"daily_research","cron":"0 8 * * 1-5","status":"enabled","next_run_at":"2026-02-28T08:00:00Z"}}}},"404":{"description":"Schedule not found"},"422":{"description":"Validation error"}}},"delete":{"tags":["cbintel","scheduler"],"summary":"Delete Schedule","description":"Delete a schedule.\n\nArgs:\n    schedule_id: Schedule identifier\n    store: Schedule store","operationId":"delete_schedule_scheduler_schedules__schedule_id__delete","parameters":[{"name":"schedule_id","in":"path","required":true,"schema":{"type":"string","title":"Schedule Id"}}],"responses":{"204":{"description":"Schedule deleted"},"404":{"description":"Schedule not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/scheduler/schedules/{schedule_id}/runs":{"get":{"tags":["cbintel","scheduler"],"summary":"Get Schedule Runs","description":"Get recent runs for a schedule.\n\nArgs:\n    schedule_id: Schedule identifier\n    store: Schedule store\n    limit: Maximum number of runs to return\n\nReturns:\n    List of recent runs","operationId":"get_schedule_runs_scheduler_schedules__schedule_id__runs_get","parameters":[{"name":"schedule_id","in":"path","required":true,"schema":{"type":"string","title":"Schedule Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Recent schedule runs","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Schedule Runs Scheduler Schedules  Schedule Id  Runs Get"},"example":{"schedule_id":"sched_abc123","schedule_name":"daily_research","runs":[{"run_id":"run_001","job_id":"job_abc","status":"completed","started_at":"2026-02-27T09:00:00Z"}],"total_runs":5}}}},"404":{"description":"Schedule not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/scheduler/schedules/{schedule_id}/status":{"put":{"tags":["cbintel","scheduler"],"summary":"Update Schedule Status","description":"Update schedule status (enable/disable/pause).\n\nArgs:\n    schedule_id: Schedule identifier\n    request: Status update request\n    store: Schedule store\n\nReturns:\n    Updated schedule","operationId":"update_schedule_status_scheduler_schedules__schedule_id__status_put","parameters":[{"name":"schedule_id","in":"path","required":true,"schema":{"type":"string","title":"Schedule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__ScheduleStatusUpdate"}}}},"responses":{"200":{"description":"Schedule status updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__Schedule"},"example":{"schedule_id":"sched_abc123","name":"daily_research","status":"paused"}}}},"404":{"description":"Schedule not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/scheduler/schedules/{schedule_id}/trigger":{"post":{"tags":["cbintel","scheduler"],"summary":"Trigger Schedule","description":"Manually trigger a schedule to run immediately.\n\nArgs:\n    schedule_id: Schedule identifier\n    store: Schedule store\n    redis: Redis client\n\nReturns:\n    Triggered job info","operationId":"trigger_schedule_scheduler_schedules__schedule_id__trigger_post","parameters":[{"name":"schedule_id","in":"path","required":true,"schema":{"type":"string","title":"Schedule Id"}}],"responses":{"200":{"description":"Schedule triggered immediately","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Trigger Schedule Scheduler Schedules  Schedule Id  Trigger Post"},"example":{"status":"triggered","schedule_id":"sched_abc123","job_id":"job_a1b2c3d4e5f67890"}}}},"404":{"description":"Schedule not found"},"500":{"description":"Failed to spawn job"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/scheduler/tick":{"post":{"tags":["cbintel","scheduler"],"summary":"Process Tick","description":"Process scheduler tick.\n\nThis endpoint should be called every minute (by cron or system timer).\nIt finds all due schedules and spawns graph jobs for them.\n\nReturns:\n    Tick execution result","operationId":"process_tick_scheduler_tick_post","responses":{"200":{"description":"Tick processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__TickResponse"},"example":{"tick_id":"tick_20260227_103000","timestamp":"2026-02-27T10:30:00Z","schedules_due":2,"jobs_spawned":2,"errors":[],"success":true}}}}}}},"/cbintel/scheduler/ticks":{"get":{"tags":["cbintel","scheduler"],"summary":"Get Recent Ticks","description":"Get recent tick history.\n\nArgs:\n    store: Schedule store\n    limit: Maximum number of ticks to return\n\nReturns:\n    List of recent tick records","operationId":"get_recent_ticks_scheduler_ticks_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":60,"title":"Limit"}}],"responses":{"200":{"description":"Recent tick history","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbintel__TickRecord"},"title":"Response Get Recent Ticks Scheduler Ticks Get"},"example":[{"tick_id":"tick_20260227_103000","timestamp":"2026-02-27T10:30:00Z","schedules_due":2,"jobs_spawned":2,"errors":[]}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbintel__HTTPValidationError"}}}}}}},"/cbintel/status":{"get":{"tags":["cbintel","Health"],"summary":"Status","description":"Component health status with Redis, workers, and usage storage details.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbintel/status/deep":{"get":{"tags":["cbintel","Health"],"summary":"Deep Status","description":"Deep connectivity check against all external microservices.","operationId":"deep_status_status_deep_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbissues/api/v1/issues":{"get":{"tags":["cbissues","Issues"],"summary":"List issues","description":"List issues for a repository with optional filters.\n\n**Query Parameters:**\n- `repo`: Repository in owner/name format (required)\n- `state`: Issue state filter (open, closed, all)\n- `labels`: Comma-separated label filter\n- `assignee`: Filter by assignee username\n- `author`: Filter by author username\n- `milestone`: Filter by milestone\n- `search`: Search query string\n- `limit`: Maximum results (default 30, max 100)\n\n**LLM Guidance:** Use this endpoint to query issues. Combine filters\nfor targeted results (e.g., open bugs assigned to a user).","operationId":"list_issues_api_v1_issues_get","parameters":[{"name":"repo","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Repository (owner/name)","title":"Repo"},"description":"Repository (owner/name)"},{"name":"state","in":"query","required":false,"schema":{"type":"string","description":"Issue state","default":"open","title":"State"},"description":"Issue state"},{"name":"labels","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated labels","title":"Labels"},"description":"Comma-separated labels"},{"name":"assignee","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Assignee username","title":"Assignee"},"description":"Assignee username"},{"name":"author","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Author username","title":"Author"},"description":"Author username"},{"name":"milestone","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Milestone name","title":"Milestone"},"description":"Milestone name"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search query","title":"Search"},"description":"Search query"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max results","default":30,"title":"Limit"},"description":"Max results"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"List of issues","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__IssueListResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"403":{"description":"Repository access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HTTPValidationError"}}}}}},"post":{"tags":["cbissues","Issues"],"summary":"Create issue","description":"Create a new issue in a repository.\n\n**Request Body:**\n- `repo`: Target repository (owner/name)\n- `title`: Issue title (required)\n- `body`: Issue body (Markdown supported)\n- `labels`: Labels to apply\n- `assignees`: Users to assign\n- `milestone`: Milestone to associate\n\n**LLM Guidance:** Use this endpoint to create new issues. Always include\na descriptive title. Use labels for categorization.","operationId":"create_issue_api_v1_issues_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__IssueCreate"}}}},"responses":{"201":{"description":"Issue created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__IssueResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"403":{"description":"Repository access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}}}}},"/cbissues/api/v1/issues/{repo}/{number}":{"get":{"tags":["cbissues","Issues"],"summary":"Get issue","description":"Get a single issue by repository and number.\n\n**Path Parameters:**\n- `repo`: Repository in owner/name format\n- `number`: Issue number\n\n**Query Parameters:**\n- `include_comments`: Include comment details (default false)\n\n**LLM Guidance:** Use this endpoint to get full issue details including\nbody, labels, assignees, and optionally comments.","operationId":"get_issue_api_v1_issues__repo___number__get","parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Repository (owner/name)","title":"Repo"},"description":"Repository (owner/name)"},{"name":"number","in":"path","required":true,"schema":{"type":"integer","description":"Issue number","title":"Number"},"description":"Issue number"},{"name":"include_comments","in":"query","required":false,"schema":{"type":"boolean","description":"Include comments","default":false,"title":"Include Comments"},"description":"Include comments"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Issue details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__IssueResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"403":{"description":"Repository access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"404":{"description":"Issue not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HTTPValidationError"}}}}}},"patch":{"tags":["cbissues","Issues"],"summary":"Update issue","description":"Update an existing issue.\n\n**Path Parameters:**\n- `repo`: Repository in owner/name format\n- `number`: Issue number\n\n**Request Body:**\n- `title`: New title (optional)\n- `body`: New body (optional)\n- `add_labels`: Labels to add\n- `remove_labels`: Labels to remove\n- `add_assignees`: Users to add\n- `remove_assignees`: Users to remove\n- `milestone`: Milestone to set\n- `remove_milestone`: Remove milestone\n\n**LLM Guidance:** Use this endpoint to modify issues. For label changes,\nuse add_labels/remove_labels rather than replacing all labels.","operationId":"update_issue_api_v1_issues__repo___number__patch","parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Repository (owner/name)","title":"Repo"},"description":"Repository (owner/name)"},{"name":"number","in":"path","required":true,"schema":{"type":"integer","description":"Issue number","title":"Number"},"description":"Issue number"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__IssueUpdate"}}}},"responses":{"200":{"description":"Issue updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__IssueResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"403":{"description":"Repository access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"404":{"description":"Issue not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}}}},"delete":{"tags":["cbissues","Issues"],"summary":"Delete issue","description":"Delete an issue (requires admin permissions).\n\n**Path Parameters:**\n- `repo`: Repository in owner/name format\n- `number`: Issue number\n\n**Note:** This action is irreversible. Consider closing instead.\n\n**LLM Guidance:** Prefer closing issues over deleting. Only delete\ntest or spam issues.","operationId":"delete_issue_api_v1_issues__repo___number__delete","parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Repository (owner/name)","title":"Repo"},"description":"Repository (owner/name)"},{"name":"number","in":"path","required":true,"schema":{"type":"integer","description":"Issue number","title":"Number"},"description":"Issue number"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Issue deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__SuccessResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"403":{"description":"Repository access denied or insufficient permissions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"404":{"description":"Issue not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HTTPValidationError"}}}}}}},"/cbissues/api/v1/issues/{repo}/{number}/close":{"post":{"tags":["cbissues","Issues"],"summary":"Close issue","description":"Close an open issue.\n\n**Path Parameters:**\n- `repo`: Repository in owner/name format\n- `number`: Issue number\n\n**Request Body (optional):**\n- `comment`: Closing comment\n- `reason`: Close reason (completed, not_planned)\n\n**LLM Guidance:** Use 'completed' for resolved issues, 'not_planned' for\nwon't-fix or duplicate issues.","operationId":"close_issue_api_v1_issues__repo___number__close_post","parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Repository (owner/name)","title":"Repo"},"description":"Repository (owner/name)"},{"name":"number","in":"path","required":true,"schema":{"type":"integer","description":"Issue number","title":"Number"},"description":"Issue number"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbissues__IssueClose"},{"type":"null"}],"title":"Data"}}}},"responses":{"200":{"description":"Issue closed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__IssueResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"403":{"description":"Repository access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"404":{"description":"Issue not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HTTPValidationError"}}}}}}},"/cbissues/api/v1/issues/{repo}/{number}/comment":{"post":{"tags":["cbissues","Issues"],"summary":"Add comment","description":"Add a comment to an issue.\n\n**Path Parameters:**\n- `repo`: Repository in owner/name format\n- `number`: Issue number\n\n**Request Body:**\n- `body`: Comment body (Markdown supported)\n\n**LLM Guidance:** Comments support Markdown formatting. Use code blocks\nfor code snippets and mention users with @username.","operationId":"add_comment_api_v1_issues__repo___number__comment_post","parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Repository (owner/name)","title":"Repo"},"description":"Repository (owner/name)"},{"name":"number","in":"path","required":true,"schema":{"type":"integer","description":"Issue number","title":"Number"},"description":"Issue number"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__IssueComment"}}}},"responses":{"201":{"description":"Comment added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__IssueResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"403":{"description":"Repository access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"404":{"description":"Issue not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}}}}},"/cbissues/api/v1/issues/{repo}/{number}/projects":{"post":{"tags":["cbissues","Issues"],"summary":"Add to projects","description":"Add an issue to one or more GitHub Projects.\n\n**Path Parameters:**\n- `repo`: Repository in owner/name format\n- `number`: Issue number\n\n**Request Body:**\n- `project_numbers`: List of project numbers to add to\n- `owner`: Project owner (default: Nominate-AI)\n\n**LLM Guidance:** Use this to organize issues into project boards\nfor tracking and planning.","operationId":"add_to_projects_api_v1_issues__repo___number__projects_post","parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Repository (owner/name)","title":"Repo"},"description":"Repository (owner/name)"},{"name":"number","in":"path","required":true,"schema":{"type":"integer","description":"Issue number","title":"Number"},"description":"Issue number"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ProjectAssignment"}}}},"responses":{"201":{"description":"Issue added to projects successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__SuccessResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"403":{"description":"Repository access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"404":{"description":"Issue not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HTTPValidationError"}}}}}}},"/cbissues/api/v1/issues/{repo}/{number}/projects/{project_number}":{"delete":{"tags":["cbissues","Issues"],"summary":"Remove from project","description":"Remove an issue from a GitHub Project.\n\n**Path Parameters:**\n- `repo`: Repository in owner/name format\n- `number`: Issue number\n- `project_number`: Project number\n\n**Query Parameters:**\n- `item_id`: Project item ID to remove\n- `owner`: Project owner (default: Nominate-AI)\n\n**LLM Guidance:** You need the project item ID, not the issue number.\nGet this from the project items list.","operationId":"remove_from_project_api_v1_issues__repo___number__projects__project_number__delete","parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Repository (owner/name)","title":"Repo"},"description":"Repository (owner/name)"},{"name":"number","in":"path","required":true,"schema":{"type":"integer","description":"Issue number","title":"Number"},"description":"Issue number"},{"name":"project_number","in":"path","required":true,"schema":{"type":"integer","description":"Project number","title":"Project Number"},"description":"Project number"},{"name":"item_id","in":"query","required":true,"schema":{"type":"string","description":"Project item ID","title":"Item Id"},"description":"Project item ID"},{"name":"owner","in":"query","required":false,"schema":{"type":"string","description":"Project owner","default":"Nominate-AI","title":"Owner"},"description":"Project owner"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Issue removed from project successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__SuccessResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"403":{"description":"Repository access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"404":{"description":"Issue or project item not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HTTPValidationError"}}}}}}},"/cbissues/api/v1/issues/{repo}/{number}/reopen":{"post":{"tags":["cbissues","Issues"],"summary":"Reopen issue","description":"Reopen a closed issue.\n\n**Path Parameters:**\n- `repo`: Repository in owner/name format\n- `number`: Issue number\n\n**Request Body (optional):**\n- `comment`: Reopening comment\n\n**LLM Guidance:** Include a comment explaining why the issue is being reopened.","operationId":"reopen_issue_api_v1_issues__repo___number__reopen_post","parameters":[{"name":"repo","in":"path","required":true,"schema":{"type":"string","description":"Repository (owner/name)","title":"Repo"},"description":"Repository (owner/name)"},{"name":"number","in":"path","required":true,"schema":{"type":"integer","description":"Issue number","title":"Number"},"description":"Issue number"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbissues__IssueReopen"},{"type":"null"}],"title":"Data"}}}},"responses":{"200":{"description":"Issue reopened successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__IssueResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"403":{"description":"Repository access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"404":{"description":"Issue not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HTTPValidationError"}}}}}}},"/cbissues/api/v1/projects":{"get":{"tags":["cbissues","Projects"],"summary":"List projects","description":"List GitHub Projects for an organization.\n\n**Query Parameters:**\n- `owner`: Organization or user (default: Nominate-AI)\n\n**LLM Guidance:** Use this endpoint to discover available projects\nfor organizing issues into boards.","operationId":"list_projects_api_v1_projects_get","parameters":[{"name":"owner","in":"query","required":false,"schema":{"type":"string","description":"Project owner","default":"Nominate-AI","title":"Owner"},"description":"Project owner"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"List of GitHub Projects","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ProjectListResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HTTPValidationError"}}}}}}},"/cbissues/api/v1/projects/{project_number}/items":{"get":{"tags":["cbissues","Projects"],"summary":"List project items","description":"List items in a GitHub Project.\n\n**Path Parameters:**\n- `project_number`: Project number\n\n**Query Parameters:**\n- `owner`: Project owner (default: Nominate-AI)\n- `limit`: Maximum items to return (default 100)\n\n**LLM Guidance:** Use this to see what issues are already in a project.\nThe item ID is needed for removal operations.","operationId":"list_project_items_api_v1_projects__project_number__items_get","parameters":[{"name":"project_number","in":"path","required":true,"schema":{"type":"integer","description":"Project number","title":"Project Number"},"description":"Project number"},{"name":"owner","in":"query","required":false,"schema":{"type":"string","description":"Project owner","default":"Nominate-AI","title":"Owner"},"description":"Project owner"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max items","default":100,"title":"Limit"},"description":"Max items"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"List of project items","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbissues__ProjectItemResponse"},"title":"Response List Project Items Api V1 Projects  Project Number  Items Get"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HTTPValidationError"}}}}}}},"/cbissues/api/v1/repos":{"get":{"tags":["cbissues","Issues"],"summary":"List allowed repositories","description":"List all repositories accessible through this API.\n\n**Access restricted to:**\n- `Nominate-AI/cb*` repositories\n- `tinymachines/*` repositories\n\n**LLM Guidance:** Use this endpoint to discover available repositories\nbefore performing issue operations.","operationId":"list_repos_api_v1_repos_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"List of allowed repositories","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__RepoListResponse"}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"500":{"description":"GitHub API error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HTTPValidationError"}}}}}}},"/cbissues/health":{"get":{"tags":["cbissues","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HealthResponse"}}}}}}},"/cbissues/health/detailed":{"get":{"tags":["cbissues","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__HealthResponse"}}}}}}},"/cbissues/prime":{"get":{"tags":["cbissues","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__PrimeResponse"}}}}}}},"/cbissues/status":{"get":{"tags":["cbissues","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbissues__StatusResponse"}}}}}}},"/cbiterable/api/v1/gateway/audit":{"get":{"tags":["cbiterable","CBITERABLE","Gateway Admin"],"summary":"Query gateway audit log","description":"Read the append-only gateway audit trail, newest first. Every vendor request lands here with its outcome mode: `mirror` / `live` (served), `denied` (no allowlist rule or over-rate), `blocked` (hard blocklist), `unauthorized` (bad token), `disabled` (kill switch), `shed` (org gate busy — vendor told to retry). Filter by consumer and/or ISO timestamp.","operationId":"query_audit_api_v1_gateway_audit_get","parameters":[{"name":"consumer_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one consumer ID.","title":"Consumer Id"},"description":"Filter to one consumer ID."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only rows at/after this ISO-8601 UTC timestamp.","examples":["2026-07-08T00:00:00+00:00"],"title":"Since"},"description":"Only rows at/after this ISO-8601 UTC timestamp."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Max rows to return.","default":200,"title":"Limit"},"description":"Max rows to return."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__AuditPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/gateway/consumers":{"get":{"tags":["cbiterable","CBITERABLE","Gateway Admin"],"summary":"List gateway consumers","description":"List all gateway consumers (token hashes redacted).","operationId":"list_consumers_api_v1_gateway_consumers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbiterable__ConsumerPublic"},"type":"array","title":"Response List Consumers Api V1 Gateway Consumers Get"}}}}}},"post":{"tags":["cbiterable","CBITERABLE","Gateway Admin"],"summary":"Create gateway consumer","description":"Create a vendor consumer scoped to one tenant (Iterable project) and mint their gateway token. **The plaintext token appears only in this response** — store it now; only its SHA-256 hash persists. The consumer starts with whatever allowlist rules you pass (default: none — nothing is reachable until rules are added).","operationId":"create_consumer_api_v1_gateway_consumers_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__ConsumerCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__ConsumerCreated"}}}},"404":{"description":"The tenant to scope this consumer to does not exist."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/gateway/consumers/{consumer_id}":{"get":{"tags":["cbiterable","CBITERABLE","Gateway Admin"],"summary":"Get gateway consumer","description":"Fetch one gateway consumer by ID (token hash redacted).","operationId":"get_consumer_api_v1_gateway_consumers__consumer_id__get","parameters":[{"name":"consumer_id","in":"path","required":true,"schema":{"type":"string","title":"Consumer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__ConsumerPublic"}}}},"404":{"description":"No consumer with this ID."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}},"patch":{"tags":["cbiterable","CBITERABLE","Gateway Admin"],"summary":"Update gateway consumer","description":"PATCH a consumer: rename, replace the allowlist (`rules` is a full replacement, not a merge), adjust the live-call rate limit, or flip the kill switch (`enabled: false` cuts the vendor off instantly).","operationId":"update_consumer_api_v1_gateway_consumers__consumer_id__patch","parameters":[{"name":"consumer_id","in":"path","required":true,"schema":{"type":"string","title":"Consumer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__ConsumerUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__ConsumerPublic"}}}},"404":{"description":"No consumer with this ID."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/gateway/consumers/{consumer_id}/rotate":{"post":{"tags":["cbiterable","CBITERABLE","Gateway Admin"],"summary":"Rotate consumer token","description":"Mint a replacement gateway token for this consumer. The old token stops working immediately. **The new plaintext token appears only in this response.**","operationId":"rotate_consumer_token_api_v1_gateway_consumers__consumer_id__rotate_post","parameters":[{"name":"consumer_id","in":"path","required":true,"schema":{"type":"string","title":"Consumer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__ConsumerCreated"}}}},"404":{"description":"No consumer with this ID."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/gateway/org-status":{"get":{"tags":["cbiterable","CBITERABLE","Gateway Admin"],"summary":"Get Iterable org-gate status","description":"Live snapshot of the shared org gate — the single prioritized queue in front of the Iterable org quota that both internal pipelines and vendor pass-through traffic flow through.\n\nRead this to answer *why is upstream slow*: a non-zero `backoff.level` means Iterable pushed back (429/5xx) and the gate is pacing **everyone**; a deep `waiting.vendor` count with zero backoff means vendor traffic is simply out-running the org budget and yielding to pipelines, as designed.","operationId":"org_gate_status_api_v1_gateway_org_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__OrgGateStatus"}}}}}}},"/cbiterable/api/v1/tenants/":{"post":{"tags":["cbiterable","CBITERABLE","Tenants"],"summary":"Create tenant","description":"Register a new tenant, binding a human-readable name to one Iterable project API key. The new tenant gets an isolated DuckDB data store. Returns the created tenant (HTTP 201) with the API key masked.","operationId":"create_tenant_api_v1_tenants__post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__TenantCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__TenantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}},"get":{"tags":["cbiterable","CBITERABLE","Tenants"],"summary":"List tenants","description":"List all registered tenants. By default only active tenants are returned; pass `include_inactive=true` to include soft-deleted/disabled ones. Iterable API keys are masked (`abcd…wxyz`).","operationId":"list_tenants_api_v1_tenants__get","parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbiterable__TenantResponse"},"title":"Response List Tenants Api V1 Tenants  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}":{"get":{"tags":["cbiterable","CBITERABLE","Tenants"],"summary":"Get tenant","description":"Fetch a single tenant by ID. Returns HTTP 404 if no tenant matches. The Iterable API key is masked.","operationId":"get_tenant_api_v1_tenants__tenant_id__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__TenantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}},"patch":{"tags":["cbiterable","CBITERABLE","Tenants"],"summary":"Update tenant","description":"Partially update a tenant (PATCH semantics): only the fields provided in the body are changed. Use it to rotate the Iterable API key, rename the tenant, toggle `is_active`, or adjust the scheduled-sync settings. The response masks the API key (it is not echoed back).","operationId":"update_tenant_api_v1_tenants__tenant_id__patch","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__TenantUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__TenantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}},"delete":{"tags":["cbiterable","CBITERABLE","Tenants"],"summary":"Delete tenant","description":"Delete a tenant. By default this is a soft delete (the tenant is marked inactive and retained). Pass `hard=true` to permanently remove the tenant record. Returns HTTP 204 on success, 404 if not found.","operationId":"delete_tenant_api_v1_tenants__tenant_id__delete","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"hard","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Hard"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/campaigns/":{"get":{"tags":["cbiterable","CBITERABLE","Campaigns"],"summary":"List campaigns from cache","description":"Return all campaigns for this tenant from the local DuckDB cache. Read-only — run a `campaigns` sync first to populate, or this returns an empty set.","operationId":"list_campaigns_api_v1_tenants__tenant_id__campaigns__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/campaigns/{campaign_id}":{"get":{"tags":["cbiterable","CBITERABLE","Campaigns"],"summary":"Get campaign from cache","description":"Fetch a single campaign by its numeric Iterable campaign ID from the local DuckDB cache. Returns HTTP 404 if not present in the cache.","operationId":"get_campaign_api_v1_tenants__tenant_id__campaigns__campaign_id__get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","title":"Campaign Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/channels/":{"get":{"tags":["cbiterable","CBITERABLE","Channels"],"summary":"List channels from cache","description":"Return all channels for this tenant from the local DuckDB cache. Read-only — run a `channels` sync first to populate, or this returns an empty set.","operationId":"list_channels_api_v1_tenants__tenant_id__channels__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/channels/{channel_id}":{"get":{"tags":["cbiterable","CBITERABLE","Channels"],"summary":"Get channel from cache","description":"Fetch a single channel by its numeric Iterable channel ID from the local DuckDB cache. Returns HTTP 404 if not present in the cache.","operationId":"get_channel_api_v1_tenants__tenant_id__channels__channel_id__get","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"integer","title":"Channel Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/key/reveal":{"post":{"tags":["cbiterable","CBITERABLE","Tenants"],"summary":"Reveal tenant API key","description":"Return the tenant's Iterable API key in PLAINTEXT. This is the only endpoint that does; list/get/create/patch responses mask it. Each call is logged. Use it only when you need the raw key to talk to Iterable directly (e.g. webhook bootstrap) — normal data access should go through cbiterable itself.","operationId":"reveal_tenant_key_api_v1_tenants__tenant_id__key_reveal_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__TenantKeyReveal"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/lists/":{"get":{"tags":["cbiterable","CBITERABLE","Lists"],"summary":"List lists from cache","description":"Return all lists for this tenant from the local DuckDB cache. The cache is populated by a sync job — run a `lists` sync first, or this returns an empty set.","operationId":"list_lists_api_v1_tenants__tenant_id__lists__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}},"post":{"tags":["cbiterable","CBITERABLE","Lists"],"summary":"Create list in Iterable","description":"Create a new list directly in the tenant's Iterable project. Returns the created list from the Iterable API (HTTP 201). The local cache is not updated until the next `lists` sync.","operationId":"create_list_api_v1_tenants__tenant_id__lists__post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__ListCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/lists/{list_id}":{"delete":{"tags":["cbiterable","CBITERABLE","Lists"],"summary":"Delete list in Iterable","description":"Delete a list directly in the tenant's Iterable project by its numeric Iterable list ID. Returns HTTP 204 on success.","operationId":"delete_list_api_v1_tenants__tenant_id__lists__list_id__delete","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"integer","title":"List Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/message_types/":{"get":{"tags":["cbiterable","CBITERABLE","Message Types"],"summary":"List message types from cache","description":"Return all message types for this tenant from the local DuckDB cache. Read-only — run a `message_types` sync first to populate, or this returns an empty set.","operationId":"list_message_types_api_v1_tenants__tenant_id__message_types__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/message_types/{message_type_id}":{"get":{"tags":["cbiterable","CBITERABLE","Message Types"],"summary":"Get message type from cache","description":"Fetch a single message type by its numeric Iterable message-type ID from the local DuckDB cache. Returns HTTP 404 if not present in the cache.","operationId":"get_message_type_api_v1_tenants__tenant_id__message_types__message_type_id__get","parameters":[{"name":"message_type_id","in":"path","required":true,"schema":{"type":"integer","title":"Message Type Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/query/":{"post":{"tags":["cbiterable","CBITERABLE","Query"],"summary":"Run ad-hoc SQL query","description":"Execute a read-only SQL SELECT against one artifact's DuckDB store for this tenant (e.g. the `campaigns` table). Only SELECT statements are allowed — dangerous/mutating keywords are blocked and rejected with HTTP 400. Results are capped by `limit` (max 10,000 rows).","operationId":"execute_query_api_v1_tenants__tenant_id__query__post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__QueryRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__QueryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/sync/":{"post":{"tags":["cbiterable","CBITERABLE","Sync"],"summary":"Start sync","description":"Kick off background sync jobs (Iterable → DuckDB) for the requested resource types. Returns one accepted `SyncJob` per resource type (HTTP 202); poll each job's status via `GET .../sync/{job_id}`. Rejects unknown resource types with HTTP 400.","operationId":"start_sync_api_v1_tenants__tenant_id__sync__post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__SyncRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbiterable__SyncJob"},"title":"Response Start Sync Api V1 Tenants  Tenant Id  Sync  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}},"get":{"tags":["cbiterable","CBITERABLE","Sync"],"summary":"List sync jobs","description":"List all known sync jobs for this tenant (most recent first), including in-flight and completed jobs loaded from persisted state.","operationId":"list_sync_jobs_api_v1_tenants__tenant_id__sync__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbiterable__SyncJob"},"title":"Response List Sync Jobs Api V1 Tenants  Tenant Id  Sync  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/sync/schedule":{"get":{"tags":["cbiterable","CBITERABLE","Sync"],"summary":"Get sync schedule","description":"Return the tenant's scheduled-sync configuration: the auto-sync interval in minutes (None = disabled), which resource types are synced on schedule, and when a sync job last completed successfully.","operationId":"get_sync_schedule_api_v1_tenants__tenant_id__sync_schedule_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/sync/{job_id}":{"get":{"tags":["cbiterable","CBITERABLE","Sync"],"summary":"Get sync job status","description":"Poll a single sync job by ID to track its progress and final outcome. Returns HTTP 404 if the job does not exist or belongs to another tenant.","operationId":"get_sync_status_api_v1_tenants__tenant_id__sync__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__SyncJob"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/templates/":{"get":{"tags":["cbiterable","CBITERABLE","Templates"],"summary":"List templates from cache","description":"Return all templates for this tenant from the local DuckDB cache. Read-only — run a `templates` sync first to populate, or this returns an empty set.","operationId":"list_templates_api_v1_tenants__tenant_id__templates__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/templates/{template_id}":{"get":{"tags":["cbiterable","CBITERABLE","Templates"],"summary":"Get template from cache","description":"Fetch a single template by its numeric Iterable template ID from the local DuckDB cache. Returns HTTP 404 if not present in the cache.","operationId":"get_template_api_v1_tenants__tenant_id__templates__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"integer","title":"Template Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/users/":{"post":{"tags":["cbiterable","CBITERABLE","Users"],"summary":"Create/update user in Iterable","description":"Upsert a user profile directly in the tenant's Iterable project. Creates the user if they don't exist, otherwise merges the supplied `data_fields` into the existing profile. Returns the Iterable API response.","operationId":"create_or_update_user_api_v1_tenants__tenant_id__users__post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__UserUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/api/v1/tenants/{tenant_id}/users/{email}":{"get":{"tags":["cbiterable","CBITERABLE","Users"],"summary":"Get user from Iterable","description":"Look up a user profile by email address in real time, directly from the tenant's Iterable project (not the local cache). Returns HTTP 404 if Iterable has no matching user.","operationId":"get_user_api_v1_tenants__tenant_id__users__email__get","parameters":[{"name":"email","in":"path","required":true,"schema":{"type":"string","title":"Email"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/gateway/api/{iterable_path}":{"get":{"tags":["cbiterable","Gateway"],"summary":"Iterable-shaped vendor pass-through","description":"Call this exactly as you would call `https://api.iterable.com/api/…`, with your CampaignBrain-issued token in the `Api-Key` header.\n\nRequests are resolved against your consumer's **default-deny allowlist**: anything not explicitly granted returns HTTP 403. Reads we materialize locally (lists, campaigns, templates, channels, messageTypes) are served from the CampaignBrain mirror and carry `X-CB-Served-From: mirror` plus `X-CB-Mirror-Synced-At` freshness headers; other granted calls are forwarded to live Iterable under a per-consumer rate limit (HTTP 429 when exceeded, like Iterable's own over-quota response). Sends, triggers, exports, deletions, and all person-level data (PII reads and mutation) are blocked outright and cannot be granted.\n\nExample: `GET /gateway/api/lists` → `{\"lists\": [...]}`","operationId":"gateway_proxy_get","parameters":[{"name":"iterable_path","in":"path","required":true,"schema":{"type":"string","title":"Iterable Path"}}],"responses":{"200":{"description":"Upstream or mirror response, Iterable-shaped.","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing or unknown Api-Key token.","content":{"application/json":{"example":{"code":"BadApiToken","msg":"Unknown or missing Api-Key"}}}},"403":{"description":"Consumer disabled, endpoint not allowlisted, or hard-blocked.","content":{"application/json":{"example":{"code":"Forbidden","msg":"GET /api/campaigns is not enabled for this consumer"}}}},"429":{"description":"Per-consumer live-call rate limit exceeded, or the shared upstream quota is cooling down (check `Retry-After`).","content":{"application/json":{"example":{"code":"RateLimitExceeded","msg":"Slow down"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}},"post":{"tags":["cbiterable","Gateway"],"summary":"Iterable-shaped vendor pass-through","description":"Call this exactly as you would call `https://api.iterable.com/api/…`, with your CampaignBrain-issued token in the `Api-Key` header.\n\nRequests are resolved against your consumer's **default-deny allowlist**: anything not explicitly granted returns HTTP 403. Reads we materialize locally (lists, campaigns, templates, channels, messageTypes) are served from the CampaignBrain mirror and carry `X-CB-Served-From: mirror` plus `X-CB-Mirror-Synced-At` freshness headers; other granted calls are forwarded to live Iterable under a per-consumer rate limit (HTTP 429 when exceeded, like Iterable's own over-quota response). Sends, triggers, exports, deletions, and all person-level data (PII reads and mutation) are blocked outright and cannot be granted.\n\nExample: `GET /gateway/api/lists` → `{\"lists\": [...]}`","operationId":"gateway_proxy_post","parameters":[{"name":"iterable_path","in":"path","required":true,"schema":{"type":"string","title":"Iterable Path"}}],"responses":{"200":{"description":"Upstream or mirror response, Iterable-shaped.","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing or unknown Api-Key token.","content":{"application/json":{"example":{"code":"BadApiToken","msg":"Unknown or missing Api-Key"}}}},"403":{"description":"Consumer disabled, endpoint not allowlisted, or hard-blocked.","content":{"application/json":{"example":{"code":"Forbidden","msg":"GET /api/campaigns is not enabled for this consumer"}}}},"429":{"description":"Per-consumer live-call rate limit exceeded, or the shared upstream quota is cooling down (check `Retry-After`).","content":{"application/json":{"example":{"code":"RateLimitExceeded","msg":"Slow down"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/gateway/data/{tenant}/{product}":{"get":{"tags":["cbiterable","Gateway Data Products"],"summary":"Paged product records","description":"Read the product's harvested records, newest first. Filter with `since`/`until` (YYYY-MM-DD, on the record timestamp) and page with `limit`/`offset`. Every response carries `fetched_at` — the harvest time of the data you are reading. Authenticate with your CampaignBrain-issued token in the `Api-Key` header.","operationId":"product_records_gateway_data__tenant___product__get","parameters":[{"name":"tenant","in":"path","required":true,"schema":{"type":"string","title":"Tenant"}},{"name":"product","in":"path","required":true,"schema":{"type":"string","title":"Product"}},{"name":"since","in":"query","required":false,"schema":{"type":"string","description":"YYYY-MM-DD lower bound (inclusive)","default":"","title":"Since"},"description":"YYYY-MM-DD lower bound (inclusive)"},{"name":"until","in":"query","required":false,"schema":{"type":"string","description":"YYYY-MM-DD upper bound (inclusive)","default":"","title":"Until"},"description":"YYYY-MM-DD upper bound (inclusive)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/gateway/data/{tenant}/{product}/status":{"get":{"tags":["cbiterable","Gateway Data Products"],"summary":"Product freshness","description":"Harvest freshness: when the store was last refreshed (`fetched_at`), the newest record timestamp, and total record count. Poll THIS to schedule your sync — the data endpoint serves the same snapshot until the next harvest.","operationId":"product_status_gateway_data__tenant___product__status_get","parameters":[{"name":"tenant","in":"path","required":true,"schema":{"type":"string","title":"Tenant"}},{"name":"product","in":"path","required":true,"schema":{"type":"string","title":"Product"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/gateway/data/{tenant}/{product}/summary":{"get":{"tags":["cbiterable","Gateway Data Products"],"summary":"Monthly rollup","description":"Donation count + gross by month over the whole harvested history.","operationId":"product_summary_gateway_data__tenant___product__summary_get","parameters":[{"name":"tenant","in":"path","required":true,"schema":{"type":"string","title":"Tenant"}},{"name":"product","in":"path","required":true,"schema":{"type":"string","title":"Product"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HTTPValidationError"}}}}}}},"/cbiterable/health":{"get":{"tags":["cbiterable","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HealthResponse"}}}}}}},"/cbiterable/health/detailed":{"get":{"tags":["cbiterable","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__HealthResponse"}}}}}}},"/cbiterable/prime":{"get":{"tags":["cbiterable","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__PrimeResponse"}}}}}}},"/cbiterable/status":{"get":{"tags":["cbiterable","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbiterable__StatusResponse"}}}}}}},"/cbkb/api/v1/hello":{"get":{"tags":["cbkb","CBKB"],"summary":"Hello world (auth + tenant smoke test)","description":"Authenticated smoke endpoint. Resolves the caller's cbauth JWT, enforces the `cbkb.query.invoke` permission, and echoes back the derived `tenant_id` and `user_id`. Use it to confirm that the bearer token, permission scopes, and tenant binding all resolve end-to-end before exercising the real KB endpoints.","operationId":"hello_api_v1_hello_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Hello Api V1 Hello Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}}},"/cbkb/api/v1/kb/audit/{record_id}":{"get":{"tags":["cbkb","KB Trace"],"summary":"List audit entries for a record (tenant-scoped)","description":"Return the full audit trail for a single record: every create/patch action with its actor, before/after snapshots, and a human-readable diff summary, oldest-first. Returns 404 when the record has no audit rows for this tenant (covering both 'no such record' and 'belongs to another tenant' without leaking existence). Requires `cbkb.records.read`.","operationId":"get_audit_trace_api_v1_kb_audit__record_id__get","parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__AuditTrace"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}}},"/cbkb/api/v1/kb/query":{"post":{"tags":["cbkb","KB Query"],"summary":"Compose a knowledge-base answer","description":"Compose a single, cited knowledge-base answer for the caller's tenant.\n\nThis is **the** retrieval API. For one `question` it:\n\n1. Ranks the tenant's **Layer-2** structured facts (`kb_records`).\n2. Optionally merges **Layer-1** narrative context (cbindex workspace search)\n   and **Layer-4** historical context (cbscout). Both are best-effort —\n   if the dependency is unreachable the query still succeeds.\n3. Optionally runs the **Layer-3** LLM synthesizer (via cbai) to rule-adapt the\n   phrasing using the tenant's brand/voice rules. Set `synthesize: false` to\n   skip synthesis and get the verbatim top-ranked record body (staff-raw\n   lookup); if cbai is unreachable the query also falls back to verbatim.\n4. Logs the whole retrieval — the returned `retrieval_log_id` can be replayed\n   later via `GET /kb/retrieval/{log_id}`.\n\nReturns an `answer_text`, a `confidence` score, the `sources` cited (each tagged\nwith its `source_tier`), any `rules_applied`, optional `live_data`, and the\n`retrieval_log_id`. Requires `cbkb.query.invoke`.","operationId":"kb_query_api_v1_kb_query_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__QueryRequest","description":"The question plus retrieval controls — see `QueryRequest`."}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__QueryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}}},"/cbkb/api/v1/kb/records":{"get":{"tags":["cbkb","KB Records"],"summary":"List records","description":"List the Layer-2 structured-fact records for the caller's tenant. Optionally narrow by `category`, `scope`, or `status`. Results are always scoped to the JWT tenant — records belonging to other tenants are never returned. Requires `cbkb.records.read`.","operationId":"list_records_api_v1_kb_records_get","parameters":[{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to records in this category.","title":"Category"},"description":"Filter to records in this category."},{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to records in this scope.","title":"Scope"},"description":"Filter to records in this scope."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by lifecycle status (e.g. `active`, `archived`).","title":"Status"},"description":"Filter by lifecycle status (e.g. `active`, `archived`)."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__RecordList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}},"post":{"tags":["cbkb","KB Records"],"summary":"Create a record","description":"Create a new Layer-2 structured-fact record for the caller's tenant. Any `tenant_id` supplied in the body is **ignored** and rebound to the JWT tenant (a warning is logged). The creation is recorded in the tenant's audit log. Returns the persisted record (HTTP 201). Requires `cbkb.records.write`.","operationId":"create_record_api_v1_kb_records_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__RecordCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__Record"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}}},"/cbkb/api/v1/kb/records/{record_id}":{"get":{"tags":["cbkb","KB Records"],"summary":"Get one record","description":"Fetch a single Layer-2 record by id, scoped to the caller's tenant. Returns 404 if the record does not exist **or** belongs to a different tenant (existence is never leaked across tenants). Requires `cbkb.records.read`.","operationId":"get_record_api_v1_kb_records__record_id__get","parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__Record"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}},"patch":{"tags":["cbkb","KB Records"],"summary":"Patch a record","description":"Partially update a Layer-2 record. Only the fields present in the request body are changed (unset fields are left untouched); any `tenant_id` in the body is stripped. The change is written to the tenant's audit log. Returns 404 if the record is missing or belongs to another tenant. Requires `cbkb.records.write`.","operationId":"patch_record_api_v1_kb_records__record_id__patch","parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__RecordPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__Record"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}}},"/cbkb/api/v1/kb/retrieval":{"get":{"tags":["cbkb","KB Trace"],"summary":"List recent retrieval-log rows (tenant-scoped)","description":"Replay the retrieval log: a tenant-scoped, newest-first list of past `/kb/query` calls and what each one retrieved and answered. Filter by `scope`, `surface`, and a `since`/`until` time window, and page with `limit`/`offset`. The response carries `next_offset` (non-null when more rows exist) so callers can page without a separate COUNT. Requires `cbkb.records.read`.","operationId":"list_retrieval_logs_api_v1_kb_retrieval_get","parameters":[{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to retrievals made within this scope.","title":"Scope"},"description":"Filter to retrievals made within this scope."},{"name":"surface","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to retrievals issued from this surface (caller channel).","title":"Surface"},"description":"Filter to retrievals issued from this surface (caller channel)."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 timestamp, inclusive lower bound","title":"Since"},"description":"ISO 8601 timestamp, inclusive lower bound"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 timestamp, exclusive upper bound","title":"Until"},"description":"ISO 8601 timestamp, exclusive upper bound"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Maximum rows to return (1-200, default 50).","default":50,"title":"Limit"},"description":"Maximum rows to return (1-200, default 50)."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Row offset for pagination (default 0).","default":0,"title":"Offset"},"description":"Row offset for pagination (default 0)."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__RetrievalLogList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}}},"/cbkb/api/v1/kb/retrieval/{log_id}":{"get":{"tags":["cbkb","KB Trace"],"summary":"Fetch a retrieval-log row (tenant-scoped)","description":"Fetch a single retrieval-log row by id — typically the `retrieval_log_id` returned by a prior `/kb/query` — to inspect exactly what that query retrieved (`retrieved_ids`, pointers), how confident it was, and what it answered. Returns 404 if the row is missing or belongs to another tenant. Requires `cbkb.records.read`.","operationId":"get_retrieval_log_api_v1_kb_retrieval__log_id__get","parameters":[{"name":"log_id","in":"path","required":true,"schema":{"type":"string","title":"Log Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__RetrievalLog"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}}},"/cbkb/api/v1/kb/rules":{"get":{"tags":["cbkb","KB Rules"],"summary":"List rules","description":"List the Layer-3 brand/voice rules for the caller's tenant. Optionally filter by `rule_type` or by `active` state. Rules are tenant-scoped. Requires `cbkb.records.read`.","operationId":"list_rules_api_v1_kb_rules_get","parameters":[{"name":"rule_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to rules of this type (e.g. `voice`).","title":"Rule Type"},"description":"Filter to rules of this type (e.g. `voice`)."},{"name":"active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"If set, return only active (`true`) or inactive (`false`) rules.","title":"Active"},"description":"If set, return only active (`true`) or inactive (`false`) rules."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__RuleList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}},"post":{"tags":["cbkb","KB Rules"],"summary":"Create a rule","description":"Create a new Layer-3 brand/voice rule for the caller's tenant. The new rule starts at `version` 1; any body-supplied `tenant_id` or `version` is ignored. Returns the persisted rule (HTTP 201). Requires `cbkb.rules.write`.","operationId":"create_rule_api_v1_kb_rules_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__RuleCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__Rule"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}}},"/cbkb/api/v1/kb/rules/{rule_id}":{"get":{"tags":["cbkb","KB Rules"],"summary":"Get one rule","description":"Fetch a single Layer-3 rule by id, scoped to the caller's tenant. Returns 404 if the rule is missing or belongs to another tenant. Requires `cbkb.records.read`.","operationId":"get_rule_api_v1_kb_rules__rule_id__get","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__Rule"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}},"patch":{"tags":["cbkb","KB Rules"],"summary":"Patch a rule","description":"Partially update a Layer-3 rule. Only the fields present in the body are changed; `tenant_id` and `version` are stripped (the version is managed server-side). Returns 404 if the rule is missing or belongs to another tenant. Requires `cbkb.rules.write`.","operationId":"patch_rule_api_v1_kb_rules__rule_id__patch","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__RulePatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__Rule"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HTTPValidationError"}}}}}}},"/cbkb/health":{"get":{"tags":["cbkb","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HealthResponse"}}}}}}},"/cbkb/health/detailed":{"get":{"tags":["cbkb","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__HealthResponse"}}}}}}},"/cbkb/prime":{"get":{"tags":["cbkb","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__PrimeResponse"}}}}}}},"/cbkb/status":{"get":{"tags":["cbkb","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkb__StatusResponse"}}}}}}},"/cbkrnl/api/v1/configs":{"get":{"tags":["cbkrnl","CBKRNL"],"summary":"Kernel strategy catalog","description":"Return the named kernel configurations and the builders they resolve to.","operationId":"configs_api_v1_configs_get","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Configs Api V1 Configs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkrnl__HTTPValidationError"}}}}}}},"/cbkrnl/api/v1/lint":{"post":{"tags":["cbkrnl","CBKRNL"],"summary":"Lint an agent graph","description":"Run the six graph rules and return every finding.\n\nMirrors `python -m cbkrnl.lint` for callers that cannot import the package\n— CI hooks in other repos, cbforge op graphs, agents over MCP.","operationId":"lint_api_v1_lint_post","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkrnl__Body_lint_api_v1_lint_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkrnl__LintResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkrnl__HTTPValidationError"}}}}}}},"/cbkrnl/api/v1/tool-schema":{"get":{"tags":["cbkrnl","CBKRNL"],"summary":"Kernel tool schema","description":"Return `cbkrnl.kernel.TOOL_SCHEMA` — the kernel as a callable tool.","operationId":"tool_schema_api_v1_tool_schema_get","parameters":[{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Tool Schema Api V1 Tool Schema Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkrnl__HTTPValidationError"}}}}}}},"/cbkrnl/health":{"get":{"tags":["cbkrnl","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkrnl__HealthResponse"}}}}}}},"/cbkrnl/health/detailed":{"get":{"tags":["cbkrnl","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkrnl__HealthResponse"}}}}}}},"/cbkrnl/prime":{"get":{"tags":["cbkrnl","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkrnl__PrimeResponse"}}}}}}},"/cbkrnl/status":{"get":{"tags":["cbkrnl","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbkrnl__StatusResponse"}}}}}}},"/cblinks/api/v1/admin/links":{"get":{"tags":["cblinks","Admin"],"summary":"List all links (admin, cross-owner)","description":"Every link across every owner (each row carries its ``owner_id``).","operationId":"list_all_links_api_v1_admin_links_get","parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","description":"Include deactivated links","default":true,"title":"Include Inactive"},"description":"Include deactivated links"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"default":1000,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblinks__LinkResponse"},"title":"Response List All Links Api V1 Admin Links Get"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"403":{"description":"Not a cbauth admin key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblinks/api/v1/admin/links/{slug}":{"get":{"tags":["cblinks","Admin"],"summary":"Get any link (admin)","description":"Read a single link regardless of owner.","operationId":"get_any_link_api_v1_admin_links__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__LinkResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"403":{"description":"Not a cbauth admin key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}},"patch":{"tags":["cblinks","Admin"],"summary":"Update any link (admin)","description":"Update any link regardless of owner — the operator rescue path (cblinks#9)\nfor links whose owner is an internal service identity (e.g. a cbfiles-minted\nshare whose presigned target is about to expire).\n\n**Updatable fields:** `target_url`, `title`, `is_active`, plus\n`expires_in_days` (resets the link's expiry to N days from now).","operationId":"update_any_link_api_v1_admin_links__slug__patch","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__AdminLinkUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__LinkResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"403":{"description":"Not a cbauth admin key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblinks/api/v1/admin/links/{slug}/stats":{"get":{"tags":["cblinks","Admin"],"summary":"Any link's analytics (admin)","description":"Click analytics for any link regardless of owner (owner_id=None bypass).","operationId":"get_any_link_stats_api_v1_admin_links__slug__stats_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__LinkStats"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"403":{"description":"Not a cbauth admin key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblinks/api/v1/links":{"post":{"tags":["cblinks","Links"],"summary":"Create short link","description":"Create a new short link.\n\n**Slug Generation:**\n- If `slug` is not provided, a random 6-character slug is generated\n- Custom slugs must be 1-64 chars, lowercase alphanumeric with hyphens\n- No leading/trailing hyphens allowed\n\n**Expiry:**\n- Set `expires_in_days` for auto-expiring links\n- Omit for links that never expire\n\n**Returns:** Full link details including the short URL for sharing.","operationId":"create_link_api_v1_links_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__LinkCreate"}}}},"responses":{"201":{"description":"Link created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__LinkResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"409":{"description":"Slug already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}},"get":{"tags":["cblinks","Links"],"summary":"List your links","description":"List all links owned by the authenticated user.\n\n**Pagination:**\n- Use `limit` and `offset` for pagination\n- Default limit is 100, max is 1000\n\n**Filtering:**\n- Set `include_inactive=true` to include disabled links","operationId":"list_links_api_v1_links_get","parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","description":"Include disabled links","default":false,"title":"Include Inactive"},"description":"Include disabled links"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Max results to return","default":100,"title":"Limit"},"description":"Max results to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip","default":0,"title":"Offset"},"description":"Number of results to skip"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblinks__LinkResponse"},"title":"Response List Links Api V1 Links Get"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblinks/api/v1/links/{slug}":{"get":{"tags":["cblinks","Links"],"summary":"Get link details","description":"Get details for a specific link. Only the owner can view link details.","operationId":"get_link_api_v1_links__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Link details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__LinkResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"403":{"description":"Not the link owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"404":{"description":"Link not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}},"patch":{"tags":["cblinks","Links"],"summary":"Update link","description":"Update a link's properties. Only the owner can update.\n\n**Updatable fields:**\n- `target_url`: Change the destination URL\n- `title`: Change the display title\n- `is_active`: Enable/disable the link (disabled links return 410)","operationId":"update_link_api_v1_links__slug__patch","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__LinkUpdate"}}}},"responses":{"200":{"description":"Link updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__LinkResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"403":{"description":"Not the link owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"404":{"description":"Link not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}},"delete":{"tags":["cblinks","Links"],"summary":"Delete link","description":"Delete a link and all its analytics data. Only the owner can delete.","operationId":"delete_link_api_v1_links__slug__delete","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Link deleted"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"403":{"description":"Not the link owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"404":{"description":"Link not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblinks/api/v1/links/{slug}/stats":{"get":{"tags":["cblinks","Links"],"summary":"Get link analytics","description":"Get click analytics for a link. Only the owner can view stats.\n\n**Metrics:**\n- `total_clicks`: All-time click count\n- `clicks_today`: Clicks since midnight UTC\n- `clicks_this_week`: Clicks since Monday UTC\n- `top_referrers`: Top 10 referrer URLs with click counts","operationId":"get_link_stats_api_v1_links__slug__stats_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Link analytics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__LinkStats"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"403":{"description":"Not the link owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"404":{"description":"Link not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblinks/api/v1/share":{"post":{"tags":["cblinks","Share"],"summary":"Create a share (presigned upload + short link)","description":"Mint a presigned PUT URL against cbfiles and atomically create a short link\nthat will redirect to the eventual public CDN URL.\n\n**Flow:**\n1. Call this endpoint with a `filename` → get back `upload_url` + `short_url`\n2. Upload the file with `curl -X PUT --upload-file <path> \"<upload_url>\"`\n   (the response includes a ready-to-run `upload_curl` you can copy/paste)\n3. Share `short_url` — it 302s to `public_url` once the upload lands\n\n**TTLs:**\n- `upload_expires_in_seconds` (default 3600) controls how long the PUT URL works\n- `expires_in_days` (default 30, or null for never) controls short-link expiry\n\nThe slug is auto-generated and is shared between the link and the cbfiles\nobject key (`share/<slug>/<filename>`) — so the redirect target is\npredictable from the slug alone.","operationId":"create_share_api_v1_share_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ShareRequest"}}}},"responses":{"201":{"description":"Share created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ShareResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"409":{"description":"Slug collision (retry)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Malformed request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"502":{"description":"cbfiles unreachable or rejected the request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}}}}},"/cblinks/api/v1/signed-actions/mint":{"post":{"tags":["cblinks","Signed Actions"],"summary":"Mint a signed-action token","description":"Mint a one-shot capability token authorising a specific action on a specific\nsubject.\n\nThe returned `signed_token_id` is opaque — store it and pass it back to\n`POST /api/v1/signed-actions/verify` when the action is actually performed.\nThe token is single-use: a successful verify atomically marks it consumed.\n\n**TTL:** falls back to the service-wide default if `ttl_seconds` is omitted.","operationId":"mint_signed_action_api_v1_signed_actions_mint_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__MintSignedActionRequest"}}}},"responses":{"201":{"description":"Token minted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__MintSignedActionResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Malformed request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}}}}},"/cblinks/api/v1/signed-actions/verify":{"post":{"tags":["cblinks","Signed Actions"],"summary":"Verify-and-consume a signed-action token","description":"Atomically verify a signed-action token and, on success, mark it consumed.\n\n**Always returns 200** for token-related outcomes — `valid` distinguishes\nsuccess from rejection. Non-2xx is reserved for cblinks-side outages, which\nthe caller (e.g. cbgreenroom) should treat as fail-closed.\n\n**Rejection reasons:**\n- `invalid_token` — unknown id\n- `expired` — token's TTL has passed\n- `consumed` — token was already used\n- `action_mismatch` — token was minted for a different action (token NOT consumed)\n- `subject_mismatch` — token was minted for a different subject (token NOT consumed)\n\n**Atomicity:** the consume happens in the same DuckDB transaction as the\nverify, so two concurrent verify calls on the same fresh token result in\nexactly one `valid: true` and one `valid: false, reason: consumed`.","operationId":"verify_signed_action_api_v1_signed_actions_verify_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__VerifySignedActionRequest"}}}},"responses":{"200":{"description":"Verify result (success or rejection)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__VerifySignedActionResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Malformed request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}}}}},"/cblinks/api/v1/users":{"post":{"tags":["cblinks","Users"],"summary":"Create user","description":"Create a new service user account.\n\n**Note:** This endpoint requires authentication. Only existing users can create new users.\nNew users don't have API keys until you create one for them.","operationId":"create_user_api_v1_users_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ServiceUserCreate"}}}},"responses":{"201":{"description":"User created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ServiceUser"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"409":{"description":"Email already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}},"get":{"tags":["cblinks","Users"],"summary":"List users","description":"List all active service users.","operationId":"list_users_api_v1_users_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblinks__ServiceUser"},"title":"Response List Users Api V1 Users Get"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblinks/api/v1/users/{user_id}":{"get":{"tags":["cblinks","Users"],"summary":"Get user","description":"Get a specific user by ID.","operationId":"get_user_api_v1_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"User details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ServiceUser"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblinks/api/v1/users/{user_id}/keys":{"post":{"tags":["cblinks","Users"],"summary":"Create API key","description":"Create a new API key for a user.\n\n**Important:** The API key is only shown once in the response.\nStore it securely - it cannot be retrieved later.\n\n**Key format:** `cblinks_{43-character-random-token}`","operationId":"create_api_key_api_v1_users__user_id__keys_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"name","in":"query","required":false,"schema":{"type":"string","default":"api-key","title":"Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"201":{"description":"API key created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__APIKeyCreateResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}},"get":{"tags":["cblinks","Users"],"summary":"List API keys","description":"List all API keys for a user. Note: The actual key values are not returned.","operationId":"list_api_keys_api_v1_users__user_id__keys_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblinks__APIKeyInfo"},"title":"Response List Api Keys Api V1 Users  User Id  Keys Get"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblinks/api/v1/users/{user_id}/keys/{key_id}":{"delete":{"tags":["cblinks","Users"],"summary":"Revoke API key","description":"Revoke an API key. The key will no longer be usable for authentication.","operationId":"revoke_api_key_api_v1_users__user_id__keys__key_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Key Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblinks/health":{"get":{"tags":["cblinks","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HealthResponse"}}}}}}},"/cblinks/health/detailed":{"get":{"tags":["cblinks","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HealthResponse"}}}}}}},"/cblinks/prime":{"get":{"tags":["cblinks","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__PrimeResponse"}}}}}}},"/cblinks/status":{"get":{"tags":["cblinks","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__StatusResponse"}}}}}}},"/cblinks/{slug}":{"get":{"tags":["cblinks","Redirect"],"summary":"Redirect to target URL","description":"Follow a short link to its destination.\n\nReturns a 307 Temporary Redirect to the target URL.\n\n**Methods:** `GET` and `HEAD` resolve identically. `HEAD` returns the same\nstatus and `Location` with no body, and is *not* counted as a click — probes\nfrom link checkers, mail scanners and unfurlers must not inflate a link\nowner's analytics.\n\n**Tracking:**\n- Each `GET` redirect is logged for analytics\n- IP addresses are hashed for privacy\n- Referrer and User-Agent are captured\n\n**Errors:**\n- 404: Link not found\n- 410: Link expired or disabled","operationId":"redirect_to_target__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"307":{"description":"Redirect to target URL"},"404":{"description":"Link not found"},"410":{"description":"Link expired or disabled"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblinks__HTTPValidationError"}}}}}}},"/cblist/api/v1/enrich-jobs/{job_id}":{"get":{"tags":["cblist","CBLIST"],"summary":"Poll an async enrichment job","operationId":"enrich_job_status_api_v1_enrich_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Enrich Job Status Api V1 Enrich Jobs  Job Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/ingest":{"post":{"tags":["cblist","CBLIST"],"summary":"Upload a file, parse it, and suggest a mapping","description":"Step 1: parse the file, mint a draft list, stage rows, return a suggested\ncolumn mapping + a sample for review. Nothing is committed to the list yet.","operationId":"ingest_upload_api_v1_ingest_post","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cblist__Body_ingest_upload_api_v1_ingest_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Ingest Upload Api V1 Ingest Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/ingest/{job_id}":{"get":{"tags":["cblist","CBLIST"],"summary":"Get an ingest job","operationId":"ingest_status_api_v1_ingest__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Ingest Status Api V1 Ingest  Job Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/ingest/{job_id}/execute":{"post":{"tags":["cblist","CBLIST"],"summary":"Commit the ingest into the list","description":"Step 2: apply the (possibly edited) mapping, dedup, and store the rows.\nBody `{\"auto_promote\": true}` also upserts the rows into cbcrm golden persons.","operationId":"ingest_execute_api_v1_ingest__job_id__execute_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"default":{},"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Ingest Execute Api V1 Ingest  Job Id  Execute Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/ingest/{job_id}/mappings":{"put":{"tags":["cblist","CBLIST"],"summary":"Override the column mapping","operationId":"ingest_set_mappings_api_v1_ingest__job_id__mappings_put","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__Body_ingest_set_mappings_api_v1_ingest__job_id__mappings_put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Ingest Set Mappings Api V1 Ingest  Job Id  Mappings Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/internal/enrich/tick":{"post":{"tags":["cblist","CBLIST"],"summary":"Drain one enrichment-job window (cbcron)","description":"Advance the oldest active enrichment job by one chunk for this tenant.\nRegistered as a cbcron job at deploy time; idempotent + safe to call often.","operationId":"enrich_tick_api_v1_internal_enrich_tick_post","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Enrich Tick Api V1 Internal Enrich Tick Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/lists":{"post":{"tags":["cblist","CBLIST"],"summary":"Create a list","operationId":"create_list_api_v1_lists_post","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__ListCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__ListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}},"get":{"tags":["cblist","CBLIST"],"summary":"List all lists","operationId":"list_lists_api_v1_lists_get","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblist__ListOut"},"title":"Response List Lists Api V1 Lists Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/lists/{list_id}":{"get":{"tags":["cblist","CBLIST"],"summary":"Get one list","operationId":"get_list_api_v1_lists__list_id__get","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__ListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}},"patch":{"tags":["cblist","CBLIST"],"summary":"Update a list","operationId":"update_list_api_v1_lists__list_id__patch","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__ListUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__ListOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}},"delete":{"tags":["cblist","CBLIST"],"summary":"Soft-delete a list","operationId":"delete_list_api_v1_lists__list_id__delete","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete List Api V1 Lists  List Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/lists/{list_id}/enrich":{"post":{"tags":["cblist","CBLIST"],"summary":"Enrich a list: geocode → district","description":"Geocode rows (cbmodels hygiene/cbgeo) then assign districts (cbdistricts).\nBest-effort + idempotent — re-running fills only what's still missing.\nBody: `{\"stages\": [\"geocode\",\"district\"]}` (both by default).","operationId":"enrich_list_route_api_v1_lists__list_id__enrich_post","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"default":{},"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Enrich List Route Api V1 Lists  List Id  Enrich Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/lists/{list_id}/members":{"get":{"tags":["cblist","CBLIST"],"summary":"Resolve a list to recipients for a send channel","description":"Resolve non-deleted rows to `{contact, name, merge_vars}` for `channel`.\n\nThis is the contract cbemail (`?channel=email`) and cbsms (`?channel=sms`)\ncall at send time instead of assembling throwaway recipient arrays.","operationId":"get_members_api_v1_lists__list_id__members_get","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"channel","in":"query","required":false,"schema":{"type":"string","pattern":"^(email|sms)$","default":"email","title":"Channel"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblist__MemberOut"},"title":"Response Get Members Api V1 Lists  List Id  Members Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/lists/{list_id}/promote":{"post":{"tags":["cblist","CBLIST"],"summary":"Promote rows to cbcrm golden persons","description":"Upsert each un-linked row into cbcrm (`POST /persons/upsert`, dedup on\npid/svid/email) and record the returned person_id in row_person_link. cbcrm\nowns the person; cblist keeps the link. Idempotent — re-running promotes only new rows.","operationId":"promote_route_api_v1_lists__list_id__promote_post","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Promote Route Api V1 Lists  List Id  Promote Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/lists/{list_id}/rows":{"post":{"tags":["cblist","CBLIST"],"summary":"Append rows","operationId":"add_rows_api_v1_lists__list_id__rows_post","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__RowsIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__RowsAdded"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}},"get":{"tags":["cblist","CBLIST"],"summary":"Get rows (paginated)","operationId":"get_rows_api_v1_lists__list_id__rows_get","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Rows Api V1 Lists  List Id  Rows Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments":{"post":{"tags":["cblist","CBLIST"],"summary":"Create a segment","description":"Save a segment. The AST is validated now so a definition that could\nnever compile is refused at write time rather than at every later read.","operationId":"create_segment_api_v1_segments_post","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__SegmentCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__SegmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}},"get":{"tags":["cblist","CBLIST"],"summary":"List saved segments","operationId":"list_segments_api_v1_segments_get","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblist__SegmentOut"},"title":"Response List Segments Api V1 Segments Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/fields":{"get":{"tags":["cblist","CBLIST"],"summary":"Filterable fields for one source","description":"The field set a builder should render, with operators already resolved.\n\nOperators come from the server's own type matrix, so a console cannot\noffer one cblist would later reject. Enum values are probed live because\nthe fabric's ``sample_values`` is empty for every column surveyed and the\nmock's hard-coded STATES list does not generalise.","operationId":"segment_fields_api_v1_segments_fields_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","description":"fabric | list","default":"fabric","title":"Kind"},"description":"fabric | list"},{"name":"dataset","in":"query","required":true,"schema":{"type":"string","description":"Fabric dataset id, or cblist list_id.","title":"Dataset"},"description":"Fabric dataset id, or cblist list_id."},{"name":"table","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Table within the dataset. Required when kind=fabric.","title":"Table"},"description":"Table within the dataset. Required when kind=fabric."},{"name":"probe_enums","in":"query","required":false,"schema":{"type":"boolean","description":"Probe DISTINCT values for enum-looking columns.","default":true,"title":"Probe Enums"},"description":"Probe DISTINCT values for enum-looking columns."},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__FieldsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/preview":{"post":{"tags":["cblist","CBLIST"],"summary":"Evaluate an unsaved segment definition","description":"Reach (and optionally a sample) for a definition that is not saved yet.\n\nThis is what the builder's live counter calls on every edit, so clients\nshould debounce it — each call is a real query against the federation, not\nan estimate.","operationId":"preview_segment_api_v1_segments_preview_post","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__PreviewIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__PreviewOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/sources":{"get":{"tags":["cblist","CBLIST"],"summary":"Pools this user can build a segment over","description":"Curated fabric sources plus this tenant's own cblist Lists.\n\nFabric entries carry ``visible=false`` when the caller's grants do not\nreach them: a missing grant is worth saying out loud, where a silently\nshorter picker just looks like the data does not exist.","operationId":"segment_sources_api_v1_segments_sources_get","parameters":[{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblist__SourceOut"},"title":"Response Segment Sources Api V1 Segments Sources Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/values":{"get":{"tags":["cblist","CBLIST"],"summary":"Distinct values of one field, for a searchable picker","description":"The vocabulary of one column, for a picker that can search.\n\nDistinct from the `enum_values` on `/segments/fields`, which answers \"is\nthis a dropdown\" and returns `[]` for anything wider than 50. A county\ncolumn is wider than that in most states -- Michigan 83, Georgia 159 -- and\nis precisely the field a person needs to pick from by name.\n\n⚠️ `truncated` is part of the answer. A shortened vocabulary renders as a\ncomplete picker that silently cannot reach half its options.","operationId":"segment_values_api_v1_segments_values_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","description":"fabric | list","default":"fabric","title":"Kind"},"description":"fabric | list"},{"name":"dataset","in":"query","required":true,"schema":{"type":"string","description":"Fabric dataset id, or cblist list_id.","title":"Dataset"},"description":"Fabric dataset id, or cblist list_id."},{"name":"table","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Table. Required when kind=fabric.","title":"Table"},"description":"Table. Required when kind=fabric."},{"name":"field","in":"query","required":true,"schema":{"type":"string","description":"Field key to enumerate.","title":"Field"},"description":"Field key to enumerate."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":500,"title":"Limit"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Segment Values Api V1 Segments Values Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/{segment_id}":{"get":{"tags":["cblist","CBLIST"],"summary":"Get one segment","operationId":"get_segment_api_v1_segments__segment_id__get","parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__SegmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}},"patch":{"tags":["cblist","CBLIST"],"summary":"Update a segment","operationId":"update_segment_api_v1_segments__segment_id__patch","parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__SegmentUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__SegmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}},"delete":{"tags":["cblist","CBLIST"],"summary":"Soft-delete a segment","operationId":"delete_segment_api_v1_segments__segment_id__delete","parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Segment Api V1 Segments  Segment Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/{segment_id}/activate":{"post":{"tags":["cblist","CBLIST"],"summary":"Push a segment to a delivery platform (not implemented)","description":"Reserved for the inject-to-platform step. Deliberately a 501.\n\nThe seam is real — a segment ends by being downloaded or pushed into a\ncampaign — but neither delivery integration exists yet, and an endpoint\nthat silently accepted the call and did nothing would be worse than one\nthat says so. Use `/export` for the download path meanwhile.","operationId":"activate_segment_api_v1_segments__segment_id__activate_post","parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"destination","in":"query","required":true,"schema":{"type":"string","description":"victorytext | conservativeconnector","title":"Destination"},"description":"victorytext | conservativeconnector"},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"501":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Activate Segment Api V1 Segments  Segment Id  Activate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/{segment_id}/execute":{"post":{"tags":["cblist","CBLIST"],"summary":"Re-evaluate a saved segment and record the run","description":"Evaluate, then append to ``execution_log`` and refresh the cached reach.\n\nFailures are logged too. A segment that has started erroring is exactly\nwhat the history is for — silently recording only successes would hide the\nday a source changed shape underneath it.","operationId":"execute_segment_api_v1_segments__segment_id__execute_post","parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"sample","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":0,"default":0,"title":"Sample"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__PreviewOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/{segment_id}/export":{"post":{"tags":["cblist","CBLIST"],"summary":"Freeze a segment's membership as a versioned snapshot","description":"Materialise the current membership into a List, and return it.\n\nA segment is a live query; a send needs something that cannot change\nunderneath it. Freezing to a List reuses the export/versioning that\nalready exists there rather than inventing a second snapshot format, and\nthe result is a first-class thing you can re-send, enrich or promote.","operationId":"export_segment_api_v1_segments__segment_id__export_post","parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"channel","in":"query","required":false,"schema":{"type":"string","pattern":"^(email|sms)$","default":"email","title":"Channel"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Export Segment Api V1 Segments  Segment Id  Export Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/{segment_id}/history":{"get":{"tags":["cblist","CBLIST"],"summary":"Recent runs for a segment","description":"Reach over time — the substrate for tracking a segment against campaign\nresults, and the first place to look when a segment starts misbehaving.","operationId":"segment_history_api_v1_segments__segment_id__history_get","parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Limit"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Segment History Api V1 Segments  Segment Id  History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/{segment_id}/members":{"get":{"tags":["cblist","CBLIST"],"summary":"Resolve a segment to recipients for a send channel","description":"The same contract `GET /lists/{id}/members` already serves.\n\nDeliberately identical in shape, so cbemail (`?channel=email`) and cbsms\n(`?channel=sms`) can point at a segment without learning anything new —\nthey resolve an audience at send time and neither knows nor cares whether\nit came from a stored list or a live query.\n\n⚠️ Which is exactly why `full` is a parameter and not the new default. A\ndownload and a send want different shapes of the same query: the download\nwants every way to reach a person, the send wants the one address it is\nabout to use. Widening the default would put two extra columns into every\nsend resolution in the fleet to serve a button in one app.","operationId":"segment_members_api_v1_segments__segment_id__members_get","parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"channel","in":"query","required":false,"schema":{"type":"string","pattern":"^(email|sms)$","default":"email","title":"Channel"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100000,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"full","in":"query","required":false,"schema":{"type":"boolean","description":"Also return every identity column the sources carry (email AND phone), not just the requested channel's — the contact-sheet shape a download wants. Off by default: the send contract is `contact`, and cbemail/cbsms must keep receiving exactly it.","default":false,"title":"Full"},"description":"Also return every identity column the sources carry (email AND phone), not just the requested channel's — the contact-sheet shape a download wants. Off by default: the send contract is `contact`, and cbemail/cbsms must keep receiving exactly it."},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblist__MemberOut"},"title":"Response Segment Members Api V1 Segments  Segment Id  Members Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/api/v1/segments/{segment_id}/projection":{"get":{"tags":["cblist","CBLIST"],"summary":"Projected outcome for a segment, from the CC scoring expectations","description":"\"How many?\" is the question a segment already answers. This is the next\none: *and then what?*\n\n`cc_contact_scores.segment_expectations` holds expected performance per\n(client, tier) — work already built and surfaced nowhere until now. The\nprojection scales it to THIS audience.\n\n**409, not an empty panel**, when the segment does not reach the scoring\ndataset. cbsegment is tenant-generic and this data belongs to one tenant,\nso the capability is checked rather than assumed, and a caller that cannot\nhave a projection is told why.\n\nRead the module docstring on `segment_projection` before changing the\narithmetic: the rates are per 1,000 SENDS (not per contact), the unsub rate\nis per 10,000 CONTACTS, and two thirds of the table has no revenue\nexpectation at all — which is reported as uncovered rather than as $0.00.","operationId":"segment_projection_api_v1_segments__segment_id__projection_get","parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}},{"name":"channel","in":"query","required":false,"schema":{"type":"string","pattern":"^(email|sms)$","default":"email","title":"Channel"}},{"name":"X-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Segment Projection Api V1 Segments  Segment Id  Projection Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HTTPValidationError"}}}}}}},"/cblist/health":{"get":{"tags":["cblist","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HealthResponse"}}}}}}},"/cblist/health/detailed":{"get":{"tags":["cblist","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__HealthResponse"}}}}}}},"/cblist/prime":{"get":{"tags":["cblist","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__PrimeResponse"}}}}}}},"/cblist/status":{"get":{"tags":["cblist","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblist__StatusResponse"}}}}}}},"/cblogs/":{"get":{"tags":["cblogs","Health"],"summary":"Service information","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Root  Get"}}}}}}},"/cblogs/api/v1/errors/recent":{"get":{"tags":["cblogs","Trace"],"summary":"Recent errors across all services","description":"Returns recent error-level log entries across all CB services with request IDs for further tracing. Use this to find what's broken, then use /trace/{rid} to dig in.","operationId":"recent_errors_api_v1_errors_recent_get","parameters":[{"name":"since","in":"query","required":false,"schema":{"type":"string","description":"How far back (e.g., 30m, 1h, 6h, 24h)","default":"1h","title":"Since"},"description":"How far back (e.g., 30m, 1h, 6h, 24h)"},{"name":"service","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to specific service (e.g., cbai)","title":"Service"},"description":"Filter to specific service (e.g., cbai)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"description":"Max entries to return","default":50,"title":"Limit"},"description":"Max entries to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Recent Errors Api V1 Errors Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/api/v1/groups":{"get":{"tags":["cblogs","Groups"],"summary":"List all log groups","description":"Return information about all available log groups.","operationId":"list_groups_api_v1_groups_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cblogs__GroupInfo"},"type":"array","title":"Response List Groups Api V1 Groups Get"}}}}}}},"/cblogs/api/v1/groups/{group}/query":{"get":{"tags":["cblogs","Groups"],"summary":"Query logs from a group","description":"Query log entries from a specific group.","operationId":"query_group_api_v1_groups__group__query_get","parameters":[{"name":"group","in":"path","required":true,"schema":{"$ref":"#/components/schemas/cblogs__LogGroup"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start time (ISO 8601)","title":"Since"},"description":"Start time (ISO 8601)"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End time (ISO 8601)","title":"Until"},"description":"End time (ISO 8601)"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":7,"minimum":0},{"type":"null"}],"description":"Max priority (0=emerg, 7=debug)","title":"Level"},"description":"Max priority (0=emerg, 7=debug)"},{"name":"pattern","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Grep pattern","title":"Pattern"},"description":"Grep pattern"},{"name":"unit","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by unit/identifier","title":"Unit"},"description":"Filter by unit/identifier"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Max entries to return","default":100,"title":"Limit"},"description":"Max entries to return"},{"name":"reverse","in":"query","required":false,"schema":{"type":"boolean","description":"Newest first","default":true,"title":"Reverse"},"description":"Newest first"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__QueryResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/api/v1/groups/{group}/stats":{"get":{"tags":["cblogs","Groups"],"summary":"Get group statistics","description":"Get statistics for a specific log group.","operationId":"get_group_stats_api_v1_groups__group__stats_get","parameters":[{"name":"group","in":"path","required":true,"schema":{"$ref":"#/components/schemas/cblogs__LogGroup"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Group Stats Api V1 Groups  Group  Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/api/v1/nginx":{"get":{"tags":["cblogs","Nginx"],"summary":"List nginx vhosts and log sizes","description":"Discover nginx vhosts from log files.","operationId":"list_vhosts_api_v1_nginx_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cblogs__VhostInfo"},"type":"array","title":"Response List Vhosts Api V1 Nginx Get"}}}}}}},"/cblogs/api/v1/nginx/{vhost}/access":{"get":{"tags":["cblogs","Nginx"],"summary":"Query nginx access logs","description":"Query nginx access logs for a specific vhost.","operationId":"query_access_api_v1_nginx__vhost__access_get","parameters":[{"name":"vhost","in":"path","required":true,"schema":{"type":"string","title":"Vhost"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start time (ISO 8601)","title":"Since"},"description":"Start time (ISO 8601)"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End time (ISO 8601)","title":"Until"},"description":"End time (ISO 8601)"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by HTTP status code","title":"Status"},"description":"Filter by HTTP status code"},{"name":"method","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by HTTP method","title":"Method"},"description":"Filter by HTTP method"},{"name":"path_pattern","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Regex for request path","title":"Path Pattern"},"description":"Regex for request path"},{"name":"ip","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by client IP","title":"Ip"},"description":"Filter by client IP"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Max entries","default":100,"title":"Limit"},"description":"Max entries"},{"name":"reverse","in":"query","required":false,"schema":{"type":"boolean","description":"Newest first","default":true,"title":"Reverse"},"description":"Newest first"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblogs__NginxAccessEntry"},"title":"Response Query Access Api V1 Nginx  Vhost  Access Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/api/v1/nginx/{vhost}/errors":{"get":{"tags":["cblogs","Nginx"],"summary":"Query nginx error logs","description":"Query nginx error logs for a specific vhost.","operationId":"query_errors_api_v1_nginx__vhost__errors_get","parameters":[{"name":"vhost","in":"path","required":true,"schema":{"type":"string","title":"Vhost"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start time (ISO 8601)","title":"Since"},"description":"Start time (ISO 8601)"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End time (ISO 8601)","title":"Until"},"description":"End time (ISO 8601)"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by error level (error, warn, crit)","title":"Level"},"description":"Filter by error level (error, warn, crit)"},{"name":"pattern","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Grep pattern","title":"Pattern"},"description":"Grep pattern"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Max entries","default":100,"title":"Limit"},"description":"Max entries"},{"name":"reverse","in":"query","required":false,"schema":{"type":"boolean","description":"Newest first","default":true,"title":"Reverse"},"description":"Newest first"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblogs__NginxErrorEntry"},"title":"Response Query Errors Api V1 Nginx  Vhost  Errors Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/api/v1/services":{"get":{"tags":["cblogs","Services"],"summary":"List all CB service identifiers","description":"List all known CB service syslog identifiers.","operationId":"list_services_api_v1_services_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Services Api V1 Services Get"}}}}}}},"/cblogs/api/v1/services/{service}/logs":{"get":{"tags":["cblogs","Services"],"summary":"Query service journal logs","description":"Query journal logs for a specific service.","operationId":"query_service_api_v1_services__service__logs_get","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","title":"Service"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start time (ISO 8601)","title":"Since"},"description":"Start time (ISO 8601)"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End time (ISO 8601)","title":"Until"},"description":"End time (ISO 8601)"},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":7,"minimum":0},{"type":"null"}],"description":"Max priority (0=emerg, 7=debug)","title":"Level"},"description":"Max priority (0=emerg, 7=debug)"},{"name":"pattern","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Grep pattern","title":"Pattern"},"description":"Grep pattern"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Max entries to return","default":100,"title":"Limit"},"description":"Max entries to return"},{"name":"reverse","in":"query","required":false,"schema":{"type":"boolean","description":"Newest first","default":true,"title":"Reverse"},"description":"Newest first"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__QueryResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/api/v1/status":{"get":{"tags":["cblogs","Status"],"summary":"System-wide log status dashboard","description":"Get per-group sizes, counts, and overall health.","operationId":"get_status_api_v1_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__SystemStatus"}}}}}}},"/cblogs/api/v1/syslog/crash-loops":{"get":{"tags":["cblogs","Syslog"],"summary":"Detect crash-looping services","description":"Detect services that are crash-looping (repeated failures).","operationId":"crash_loops_api_v1_syslog_crash_loops_get","parameters":[{"name":"minutes","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Time window in minutes","default":5,"title":"Minutes"},"description":"Time window in minutes"},{"name":"threshold","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":2,"description":"Error count to flag crash loop","default":5,"title":"Threshold"},"description":"Error count to flag crash loop"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblogs__CrashLoop"},"title":"Response Crash Loops Api V1 Syslog Crash Loops Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/api/v1/syslog/critical-patterns":{"get":{"tags":["cblogs","Syslog"],"summary":"Detect critical patterns","description":"Detect critical patterns: OOM kills, segfaults, disk I/O errors, kernel panics.","operationId":"critical_patterns_api_v1_syslog_critical_patterns_get","parameters":[{"name":"minutes","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Time window in minutes","default":5,"title":"Minutes"},"description":"Time window in minutes"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cblogs__CriticalPattern"},"title":"Response Critical Patterns Api V1 Syslog Critical Patterns Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/api/v1/syslog/errors-by-unit":{"get":{"tags":["cblogs","Syslog"],"summary":"Error counts by unit","description":"Get error counts grouped by systemd unit, top 20 noisiest.","operationId":"errors_by_unit_api_v1_syslog_errors_by_unit_get","parameters":[{"name":"minutes","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Time window in minutes","default":5,"title":"Minutes"},"description":"Time window in minutes"},{"name":"max_priority","in":"query","required":false,"schema":{"type":"integer","maximum":7,"minimum":0,"description":"Max priority level (0=emerg, 4=warning, 7=debug)","default":4,"title":"Max Priority"},"description":"Max priority level (0=emerg, 4=warning, 7=debug)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"integer"},"title":"Response Errors By Unit Api V1 Syslog Errors By Unit Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/api/v1/syslog/scan":{"get":{"tags":["cblogs","Syslog"],"summary":"Run syslog analysis scan","description":"Scan the system journal for errors, crash loops, and critical patterns.\n\nAnalyzes recent journal entries and returns:\n- **total_errors**: Total error count (after filtering noise)\n- **errors_by_unit**: Top 20 noisiest units\n- **crash_looping**: Services with repeated failures\n- **critical_patterns**: OOM kills, segfaults, disk I/O errors, kernel panics\n- **sample_messages**: Up to 10 sample error messages","operationId":"scan_api_v1_syslog_scan_get","parameters":[{"name":"minutes","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Time window in minutes","default":5,"title":"Minutes"},"description":"Time window in minutes"},{"name":"max_priority","in":"query","required":false,"schema":{"type":"integer","maximum":7,"minimum":0,"description":"Max priority level (0=emerg, 4=warning, 7=debug)","default":4,"title":"Max Priority"},"description":"Max priority level (0=emerg, 4=warning, 7=debug)"},{"name":"crash_loop_threshold","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":2,"description":"Error count to flag crash loop","default":5,"title":"Crash Loop Threshold"},"description":"Error count to flag crash loop"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__SyslogScan"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/api/v1/trace/{request_id}":{"get":{"tags":["cblogs","Trace"],"summary":"Trace a request across all services","description":"Grep all CB service journals for a request ID. Returns time-ordered log entries grouped by service, with error count and total duration.","operationId":"trace_request_api_v1_trace__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}},{"name":"since","in":"query","required":false,"schema":{"type":"string","description":"How far back to search (e.g., 1h, 24h, 7d)","default":"24h","title":"Since"},"description":"How far back to search (e.g., 1h, 24h, 7d)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Trace Request Api V1 Trace  Request Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HTTPValidationError"}}}}}}},"/cblogs/health":{"get":{"tags":["cblogs","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HealthResponse"}}}}}}},"/cblogs/health/detailed":{"get":{"tags":["cblogs","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__HealthResponse"}}}}}}},"/cblogs/prime":{"get":{"tags":["cblogs","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__PrimeResponse"}}}}}}},"/cblogs/status":{"get":{"tags":["cblogs","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cblogs__StatusResponse"}}}}}}},"/cbloom/api/v1/hello":{"get":{"tags":["cbloom","CBLOOM"],"summary":"Smoke-test the Loom service","description":"Returns a simple greeting confirming the Loom service is live and reachable.\n\nUse this endpoint as a lightweight smoke test when you need to verify the service is up without triggering any database or downstream calls.\n\n**Example response:**\n```json\n{\"message\": \"Hello from cbloom\"}\n```","operationId":"hello_api_v1_hello_get","responses":{"200":{"description":"Service is live","content":{"application/json":{"schema":{}}}}}}},"/cbloom/health":{"get":{"tags":["cbloom","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbloom__HealthResponse"}}}}}}},"/cbloom/health/detailed":{"get":{"tags":["cbloom","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbloom__HealthResponse"}}}}}}},"/cbloom/prime":{"get":{"tags":["cbloom","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbloom__PrimeResponse"}}}}}}},"/cbloom/status":{"get":{"tags":["cbloom","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbloom__StatusResponse"}}}}}}},"/cbmanim/api/v1/backend":{"get":{"tags":["cbmanim","CBMANIM"],"summary":"Render-engine readiness","description":"Whether the manim engine AND its sandbox are provisioned and callable.\n\n``ready`` means \"a render request will be attempted\": engine present, and\nin require mode the bwrap jail too. ``sandbox`` is ``active`` / ``missing``\n/ ``off`` so operators can tell a missing engine from a missing jail.","operationId":"backend_api_v1_backend_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbmanim/api/v1/render":{"post":{"tags":["cbmanim","CBMANIM"],"summary":"Render a Manim scene","description":"Render ``scene_name`` from the supplied source; returns artifact metadata.","operationId":"render_api_v1_render_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmanim__RenderRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmanim__HTTPValidationError"}}}}}}},"/cbmanim/api/v1/render/{job_id}/artifact":{"get":{"tags":["cbmanim","CBMANIM"],"summary":"Stream a rendered artifact","description":"Stream the file produced by a prior render job.","operationId":"artifact_api_v1_render__job_id__artifact_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmanim__HTTPValidationError"}}}}}}},"/cbmanim/api/v1/scenes/from-prompt":{"post":{"tags":["cbmanim","CBMANIM"],"summary":"Generate a scene from a brief","description":"NL brief → Manim source via cbai, validated with a retry loop.\n\nEach attempt is validated (by the real render when ``render`` is true, else a\n``--dry_run``); on failure the error is fed back to cbai for a correction, up\nto ``scene_max_attempts``. Response includes ``attempts`` and, on give-up,\n``failed``/``attempts_log``.","operationId":"from_prompt_api_v1_scenes_from_prompt_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmanim__PromptRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmanim__HTTPValidationError"}}}}}}},"/cbmanim/api/v1/warm":{"post":{"tags":["cbmanim","CBMANIM"],"summary":"Warm the prompt-to-scene model","description":"Pin ``AI_MODEL`` warm in cbai/ollama with one tiny chat.\n\nIdempotent, side-effect-free (no render, no code execution) — safe to poll.\nMeant for a cbcron keepalive so an idle model isn't cold on the next real\nprompt-to-scene. Always 200: ``warmed`` is false (not an error) when cbai is\nunreachable or the model can't load, since a keepalive must not alarm.","operationId":"warm_api_v1_warm_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbmanim/health":{"get":{"tags":["cbmanim","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmanim__HealthResponse"}}}}}}},"/cbmanim/health/detailed":{"get":{"tags":["cbmanim","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmanim__HealthResponse"}}}}}}},"/cbmanim/prime":{"get":{"tags":["cbmanim","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmanim__PrimeResponse"}}}}}}},"/cbmanim/status":{"get":{"tags":["cbmanim","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmanim__StatusResponse"}}}}}}},"/cbmaps/api/v1/posters":{"get":{"tags":["cbmaps","Posters","Posters"],"summary":"List generated posters","description":"Get a list of all generated poster filenames, sorted by creation time (newest first).","operationId":"list_posters_api_v1_posters_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__PosterListResponse"}}}}}}},"/cbmaps/api/v1/posters/generate":{"post":{"tags":["cbmaps","Posters","Posters"],"summary":"Generate a map poster","description":"Generate a map poster using one of three render modes:\n\n- **vector**: High-quality vector rendering using OpenStreetMap data\n- **raster**: Tile-based rendering with CartoDB styles\n- **district**: Congressional district with boundary overlay\n\nGeneration takes 10-60 seconds depending on complexity.","operationId":"generate_poster_api_v1_posters_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__GeneratePosterRequest"}}},"required":true},"responses":{"200":{"description":"Poster generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__PosterResponse"}}}},"400":{"description":"Invalid request (bad theme, missing fields)"},"500":{"description":"Server error during generation"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__HTTPValidationError"}}}}}}},"/cbmaps/api/v1/posters/themes":{"get":{"tags":["cbmaps","Posters","Themes"],"summary":"List available themes","description":"Get a list of all available poster themes with their display names and descriptions.","operationId":"list_themes_api_v1_posters_themes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__ThemeListResponse"}}}}}}},"/cbmaps/api/v1/posters/{filename}":{"get":{"tags":["cbmaps","Posters","Posters"],"summary":"Download a poster","description":"Download a generated poster PNG file by its filename.","operationId":"download_poster_api_v1_posters__filename__get","parameters":[{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Poster file","content":{"application/json":{"schema":{}},"image/png":{}}},"404":{"description":"Poster not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__HTTPValidationError"}}}}}}},"/cbmaps/api/v1/street/images":{"get":{"tags":["cbmaps","Street"],"summary":"Search Images","description":"## Search Street-Level Images Near a Point\n\nFinds crowdsourced street photos within `radius` meters of a lat/lon,\nmerged across providers and sorted nearest-first.\n\n### What This Endpoint Does\n\n1. **Checks the JIT search cache** — identical recent searches are served\n   from disk (TTL-controlled, default 24h)\n2. **Queries upstream on miss** — Mapillary Graph API and/or KartaView\n3. **Prefetches thumbnails** — the closest results' thumbnails are warmed\n   in the background, so the follow-up image requests are instant\n4. **Returns proxied URLs** — `thumb_url`/`image_url` point at this\n   service's image cache, never at short-lived upstream CDN URLs\n\n### Providers\n\n| Provider | Auth | Notes |\n|----------|------|-------|\n| `mapillary` | access token | skipped when unconfigured |\n| `kartaview` | none | always available |\n\n### Example\n\n```\nGET /api/v1/street/images?lat=48.8584&lon=2.2945&radius=150&limit=10\n```","operationId":"search_images_api_v1_street_images_get","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","maximum":90,"minimum":-90,"description":"Latitude of the search point (WGS84)","title":"Lat"},"description":"Latitude of the search point (WGS84)"},{"name":"lon","in":"query","required":true,"schema":{"type":"number","maximum":180,"minimum":-180,"description":"Longitude of the search point (WGS84)","title":"Lon"},"description":"Longitude of the search point (WGS84)"},{"name":"radius","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":10,"description":"Search radius in meters","default":100,"title":"Radius"},"description":"Search radius in meters"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum images to return","default":25,"title":"Limit"},"description":"Maximum images to return"},{"name":"provider","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbmaps__StreetProviderName"},{"type":"null"}],"description":"Restrict to one provider; omit to merge all configured providers","title":"Provider"},"description":"Restrict to one provider; omit to merge all configured providers"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__StreetImageSearchResponse"}}}},"502":{"description":"All queried upstream providers failed","content":{"application/json":{"example":{"detail":"kartaview search failed: HTTP 500"}}}},"503":{"description":"Requested provider is not configured","content":{"application/json":{"example":{"detail":"MAPILLARY_ACCESS_TOKEN is not set — mint a client token at https://www.mapillary.com/dashboard/developers"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__HTTPValidationError"}}}}}}},"/cbmaps/api/v1/street/images/{provider}/{image_id}.jpg":{"get":{"tags":["cbmaps","Street"],"summary":"Get Image","description":"## Fetch a Street Image (JIT-cached)\n\nReturns the JPEG bytes for one street-level photo, either from the local\ndisk cache or downloaded from the provider on first request — the same\njust-in-time model cbtiles uses for map tiles.\n\n### Why Proxy Instead of Linking Upstream?\n\nMapillary's thumbnail URLs are **signed and short-lived**; embedding them\nin a UI breaks after expiry. This endpoint's URLs are stable forever, and\nrepeat views cost zero upstream calls.\n\n### Caching Behavior\n\n- **Disk cache**: permanent until manually purged (like tiles)\n- **Cache-Control**: `public, max-age=86400` (1 day browser cache)\n- **In-flight dedupe**: concurrent requests for one image download once","operationId":"get_image_api_v1_street_images__provider___image_id__jpg_get","parameters":[{"name":"provider","in":"path","required":true,"schema":{"$ref":"#/components/schemas/cbmaps__StreetProviderName"}},{"name":"image_id","in":"path","required":true,"schema":{"type":"string","title":"Image Id"}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","description":"Image size bucket: 256, 1024, or 2048","default":1024,"title":"Size"},"description":"Image size bucket: 256, 1024, or 2048"}],"responses":{"200":{"description":"JPEG street image","content":{"application/json":{"schema":{}},"image/jpeg":{}},"headers":{"Cache-Control":{"description":"Browser caching directive","schema":{"type":"string","example":"public, max-age=86400"}}}},"400":{"description":"Unsupported size","content":{"application/json":{"example":{"detail":"size must be one of (256, 1024, 2048)"}}}},"404":{"description":"Image not found upstream or could not be downloaded","content":{"application/json":{"example":{"detail":"Image not found and could not be downloaded"}}}},"503":{"description":"Provider is not configured","content":{"application/json":{"example":{"detail":"MAPILLARY_ACCESS_TOKEN is not set"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__HTTPValidationError"}}}}}}},"/cbmaps/api/v1/street/providers":{"get":{"tags":["cbmaps","Street"],"summary":"List Providers","description":"## List Street-Imagery Providers\n\nReturns every known street-level imagery source and whether it is ready\nto serve requests. Use this to decide which `provider` values are valid\nfor `GET /street/images` — an unconfigured provider (e.g. Mapillary\nwithout an access token) rejects explicit requests with 503 and is\nsilently skipped in all-provider searches.","operationId":"list_providers_api_v1_street_providers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__StreetProviderListResponse"}}}}}}},"/cbmaps/api/v1/street/stats":{"get":{"tags":["cbmaps","Street"],"summary":"Street Stats","description":"## Street-Imagery Cache Statistics\n\nReports JIT-cache performance per provider (search/image hits and misses,\nfailed downloads) plus disk usage of the image cache — the same shape of\nnumbers cbtiles exposes at its `/stats` endpoint. Useful for verifying\nthe cache is actually absorbing repeat traffic.","operationId":"street_stats_api_v1_street_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Street Stats Api V1 Street Stats Get"}}}}}}},"/cbmaps/health":{"get":{"tags":["cbmaps","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__HealthResponse"}}}}}}},"/cbmaps/health/detailed":{"get":{"tags":["cbmaps","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__HealthResponse"}}}}}}},"/cbmaps/prime":{"get":{"tags":["cbmaps","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__PrimeResponse"}}}}}}},"/cbmaps/status":{"get":{"tags":["cbmaps","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmaps__StatusResponse"}}}}}}},"/cbmaps/ui":{"get":{"tags":["cbmaps","UI"],"summary":"Web interface","description":"Serve the interactive poster-builder single-page app. The static `ui.html` template is read from disk and the CDN, tile-server, and cbdistricts API URLs are substituted in at request time so the page talks to the correct backends per environment. Returns an HTML document; intended for browsers, not programmatic API clients.","operationId":"ui_ui_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/cbmaps/ui/cities.json":{"get":{"tags":["cbmaps","UI"],"summary":"Cities dataset","description":"Return the bundled cities dataset (city/country pairs) that backs the web UI's 'random city' picker. Served verbatim as a static JSON file; used only by the `/ui` front-end.","operationId":"cities_ui_cities_json_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbmarketing/api/v1/brands":{"get":{"tags":["cbmarketing","Brands"],"summary":"List brand kits","operationId":"list_brands_api_v1_brands_get","parameters":[{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__BrandKitList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}},"post":{"tags":["cbmarketing","Brands"],"summary":"Create brand kit","operationId":"create_brand_api_v1_brands_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__BrandKitInput"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__BrandKit"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}}},"/cbmarketing/api/v1/brands/{kit_id}":{"get":{"tags":["cbmarketing","Brands"],"summary":"Get a brand kit","operationId":"get_brand_api_v1_brands__kit_id__get","parameters":[{"name":"kit_id","in":"path","required":true,"schema":{"type":"integer","title":"Kit Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__BrandKit"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}},"put":{"tags":["cbmarketing","Brands"],"summary":"Update a brand kit","operationId":"update_brand_api_v1_brands__kit_id__put","parameters":[{"name":"kit_id","in":"path","required":true,"schema":{"type":"integer","title":"Kit Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__BrandKitInput"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__BrandKit"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}},"delete":{"tags":["cbmarketing","Brands"],"summary":"Soft-delete a brand kit","operationId":"delete_brand_api_v1_brands__kit_id__delete","parameters":[{"name":"kit_id","in":"path","required":true,"schema":{"type":"integer","title":"Kit Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}}},"/cbmarketing/api/v1/brands/{kit_id}/tokens":{"get":{"tags":["cbmarketing","Brands"],"summary":"Export design tokens","description":"Export the kit's colors + fonts as CSS custom properties or JSON tokens.","operationId":"export_tokens_api_v1_brands__kit_id__tokens_get","parameters":[{"name":"kit_id","in":"path","required":true,"schema":{"type":"integer","title":"Kit Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(css|json)$","default":"css","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}}},"/cbmarketing/api/v1/color/accessible":{"post":{"tags":["cbmarketing","Color"],"summary":"Make accessible","description":"Nudge a foreground color until it meets target contrast against a bg.","operationId":"accessible_api_v1_color_accessible_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__AccessibleRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__AccessibleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}}},"/cbmarketing/api/v1/color/contrast":{"post":{"tags":["cbmarketing","Color"],"summary":"WCAG contrast","description":"Compute the WCAG 2.1 contrast ratio + AA/AAA pass flags for two colors.","operationId":"contrast_api_v1_color_contrast_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__ContrastRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__ContrastResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}}},"/cbmarketing/api/v1/color/convert":{"get":{"tags":["cbmarketing","Color"],"summary":"Convert a hex color","description":"Return rgb + hsl for a hex color.","operationId":"convert_api_v1_color_convert_get","parameters":[{"name":"hex","in":"query","required":true,"schema":{"type":"string","title":"Hex"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__ConvertResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}}},"/cbmarketing/api/v1/color/palette":{"post":{"tags":["cbmarketing","Color"],"summary":"Harmony palette","description":"Generate a harmony palette around a base color.","operationId":"palette_api_v1_color_palette_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__PaletteRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__PaletteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}}},"/cbmarketing/api/v1/color/ramp":{"get":{"tags":["cbmarketing","Color"],"summary":"Tonal ramp (50-950)","description":"Tailwind-style tonal ramp from a base color.","operationId":"ramp_api_v1_color_ramp_get","parameters":[{"name":"base","in":"query","required":true,"schema":{"type":"string","title":"Base"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__RampResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}}},"/cbmarketing/api/v1/decide/moods":{"get":{"tags":["cbmarketing","Decide"],"summary":"List supported moods","description":"Return the moods the recommender understands.","operationId":"list_moods_api_v1_decide_moods_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbmarketing/api/v1/decide/palette":{"post":{"tags":["cbmarketing","Decide"],"summary":"Recommend a palette for a mood","description":"Recommend a coherent, accessibility-aware palette for a desired mood.\n\nDeterministic heuristics produce the palette; if `with_rationale` is set we\nadditionally ask cbai (over the mesh) for a short rationale — but the\npalette stands on its own if cbai is unreachable.","operationId":"decide_palette_api_v1_decide_palette_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__PaletteDecisionRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__PaletteDecisionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HTTPValidationError"}}}}}}},"/cbmarketing/health":{"get":{"tags":["cbmarketing","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HealthResponse"}}}}}}},"/cbmarketing/health/detailed":{"get":{"tags":["cbmarketing","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__HealthResponse"}}}}}}},"/cbmarketing/prime":{"get":{"tags":["cbmarketing","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__PrimeResponse"}}}}}}},"/cbmarketing/status":{"get":{"tags":["cbmarketing","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmarketing__StatusResponse"}}}}}}},"/cbmobile/api/debug/sessions":{"get":{"tags":["cbmobile","debug"],"summary":"List active remote-debug sessions","description":"Return the currently-connected remote-debug WebSocket sessions and the on-disk JSONL log path for each, plus a total count.","operationId":"debug_get_debug_sessions","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbmobile/contacts":{"get":{"tags":["cbmobile","contacts"],"summary":"List canvassing contacts","description":"Return a paginated list of canvassing contacts for the mobile walk-list view, optionally filtered by `status`. **Stub endpoint** — currently returns hardcoded in-memory contacts; the real list will be served from the walkbook DuckDB store.","operationId":"listContacts","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmobile__ContactList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmobile__HTTPValidationError"}}}}}}},"/cbmobile/contacts/{contact_id}":{"get":{"tags":["cbmobile","contacts"],"summary":"Get a single contact","description":"Fetch one canvassing contact by its identifier. **Stub endpoint** — currently echoes a hardcoded contact for the given id; the real lookup will hit the walkbook DuckDB store.","operationId":"getContact","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"integer","title":"Contact Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmobile__Contact"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmobile__HTTPValidationError"}}}}}}},"/cbmobile/health":{"get":{"tags":["cbmobile","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"Health_health","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmobile__HealthResponse"}}}}}}},"/cbmobile/health/detailed":{"get":{"tags":["cbmobile","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"Health_health_detailed","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmobile__HealthResponse"}}}}}}},"/cbmobile/prime":{"get":{"tags":["cbmobile","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"Health_prime","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmobile__PrimeResponse"}}}}}}},"/cbmobile/status":{"get":{"tags":["cbmobile","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"Health_status","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmobile__StatusResponse"}}}}}}},"/cbmobile/sync/checkpoint":{"get":{"tags":["cbmobile","sync"],"summary":"Get the current sync checkpoint","description":"Return the current offline-sync checkpoint the mobile client replays from after reconnecting. **Stub endpoint** — currently returns a fixed 'initial' checkpoint stamped with the server time; the real checkpoint will be derived from the walkbook DuckDB store.","operationId":"getSyncCheckpoint","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmobile__SyncCheckpoint"}}}}}}},"/cbmodels/api/v1/airtable/query":{"post":{"tags":["cbmodels","airtable"],"summary":"Execute SQL against synced Airtable data","description":"Run read-only DuckDB SQL against a tenant's synced Airtable base.","operationId":"airtable_query_api_v1_airtable_query_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__SQLRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Airtable Query Api V1 Airtable Query Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/airtable/schema/{tenant_id}/{base_id}":{"get":{"tags":["cbmodels","airtable"],"summary":"Get schema for an Airtable base","description":"Table names, column names/types, join relationships, row counts, and sample rows.","operationId":"airtable_schema_api_v1_airtable_schema__tenant_id___base_id__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"base_id","in":"path","required":true,"schema":{"type":"string","title":"Base Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Airtable Schema Api V1 Airtable Schema  Tenant Id   Base Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/airtable/sources":{"get":{"tags":["cbmodels","airtable"],"summary":"List Airtable data sources","description":"List all tenants, bases, and synced tables with row counts. Each base also includes the federation `catalog_name` (format `airtable_<tenant_slug>_<base_id_lower>`) so SQL queries via /api/v1/federation/query can address the same data. `catalog_name` is null when the base hasn't been synced into a federation catalog yet.","operationId":"airtable_sources_api_v1_airtable_sources_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Airtable Sources Api V1 Airtable Sources Get"}}}}}}},"/cbmodels/api/v1/airtable/sync":{"post":{"tags":["cbmodels","airtable"],"summary":"Trigger Airtable base sync","description":"Refresh synced data for a specific base. Returns sync job status.","operationId":"airtable_sync_api_v1_airtable_sync_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__SyncRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Airtable Sync Api V1 Airtable Sync Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/baseline/summary":{"get":{"tags":["cbmodels","baseline"],"summary":"Get baseline model summary","description":"## Get Baseline Summary\n\nReturns a summary of the pre-computed baseline model, which represents\nthe full voter population statistics.\n\n### What's in the Baseline\n\nThe baseline is computed offline from the full voter database and includes:\n\n- **Schema information** - Tables, columns, data types\n- **Summary statistics** - Row counts, null rates, distinct values\n- **Correlations** - Relationships between numeric columns (Pearson coefficient)\n- **Outliers** - Values outside normal range (IQR method)\n- **Patterns** - Data quality issues (missing data, class imbalance, skew)\n\n### When to Use\n\n- **Before analysis** - Check if baseline is loaded and current\n- **Exploratory questions** - \"What data do we have?\"\n- **Context setting** - Understand the scope of available data","operationId":"get_baseline_summary_api_v1_baseline_summary_get","responses":{"200":{"description":"Summary statistics for the entire baseline model","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__BaselineSummaryResponse"}}}}}}},"/cbmodels/api/v1/baseline/table/{table_name}":{"get":{"tags":["cbmodels","baseline"],"summary":"Get table baseline statistics","description":"## Get Table Statistics\n\nReturns detailed baseline statistics for a specific table in the database.\n\n### Available Tables\n\nCommon tables in the voter database:\n- **person** - Core voter records (demographics, contact info)\n- **person_custom_field** - Extended voter attributes (SVID, party, voter key)\n- **custom_field** - Custom field definitions\n\n### What's Returned\n\n- **Column statistics** - For each column: type, null rate, distinct count\n- **Correlations** - Significant correlations within this table\n- **Outliers** - Columns with detected outliers\n- **Patterns** - Data quality patterns specific to this table\n\n### Use Cases\n\n- **Exploration** - \"What columns does the person table have?\"\n- **Data quality** - \"Which columns have missing data?\"\n- **Correlations** - \"What's correlated with donation behavior?\"","operationId":"get_table_stats_api_v1_baseline_table__table_name__get","parameters":[{"name":"table_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the table to retrieve statistics for","examples":["person","person_custom_field","custom_field"],"title":"Table Name"},"description":"Name of the table to retrieve statistics for"}],"responses":{"200":{"description":"Detailed statistics for the specified table","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__TableStatsResponse"}}}},"404":{"description":"Table not found in baseline model","content":{"application/json":{"example":{"detail":"Table 'invalid_table' not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/baseline/tables":{"get":{"tags":["cbmodels","baseline"],"summary":"List all tables in baseline","description":"## List Available Tables\n\nReturns a list of all tables available in the baseline model.\n\n### Use Cases\n\n- **Discovery** - \"What tables are available for analysis?\"\n- **Validation** - Check if a table exists before querying it","operationId":"list_tables_api_v1_baseline_tables_get","responses":{"200":{"description":"List of table names with row counts","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Tables Api V1 Baseline Tables Get"}}}}}}},"/cbmodels/api/v1/behavioral/affinity":{"post":{"tags":["cbmodels","behavioral"],"summary":"Get client affinity rankings","description":"## Client Affinity Analysis\n\nDetermine which clients/organizations have the strongest engagement with\na voter segment. Results are ranked by affinity score.\n\n### Affinity Score Formula\n\n```\naffinity_score = (clicks * 3 + opens - unsubscribes * 5) / sends * 100\n```\n\n- **Clicks weighted 3x** - Strong positive signal\n- **Opens weighted 1x** - Moderate positive signal\n- **Unsubscribes weighted -5x** - Strong negative signal\n\n### Use Cases\n\n- **Targeting**: \"Which orgs should mail this segment?\"\n- **Avoidance**: \"Which orgs have low affinity (potential fatigue)?\"\n- **Optimization**: \"Where should we allocate send budget?\"\n\n### Example Interpretation\n\n```\n\"This segment has highest affinity for TPUSA (18.5) and AFP (15.2).\nAvoid sending from XYZ Corp (affinity -2.3, high unsub rate).\"\n```","operationId":"get_client_affinity_api_v1_behavioral_affinity_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ClientAffinityRequest"}}},"required":true},"responses":{"200":{"description":"Client affinity rankings sorted by score","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ClientAffinityResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/behavioral/enrich":{"post":{"tags":["cbmodels","behavioral"],"summary":"Enrich segment with behavioral data","description":"## Behavioral Enrichment\n\nEnrich a voter segment with email engagement, donation history, and client\naffinity data. This endpoint links SVIDs to behavioral records via email\naddress matching.\n\n### What This Endpoint Does\n\n1. **Resolves SVIDs to emails** - Finds email addresses for each voter\n2. **Matches behavioral data** - Links to email_clickers (41M records) and donations (3.2M records)\n3. **Computes engagement metrics** - Clicks, opens, campaigns, client breakdown\n4. **Builds individual profiles** - For segments < 1000, returns per-voter profiles\n\n### Key Metrics Returned\n\n- **match_rate** - % of SVIDs with behavioral data (indicator of data coverage)\n- **segment_is_clicker_rate** - % who have clicked emails (engagement indicator)\n- **segment_is_donor_rate** - % who have donated (value indicator)\n- **client_affinity** - Which organizations' content resonates with this segment\n\n### Use Cases\n\n- **Engagement Analysis**: \"How engaged is this donor segment with email campaigns?\"\n- **Client Discovery**: \"Which orgs should target this audience?\"\n- **Donor Identification**: \"Which of these voters have donated before?\"\n\n### Example Interpretation\n\n```\n\"Of 1000 voters provided, 612 (61%) have behavioral data.\n35% have clicked at least one email, and 8.5% are previous donors.\nTop client affinity: TPUSA (18.5 score), AFP (15.2 score)\"\n```","operationId":"enrich_behavioral_api_v1_behavioral_enrich_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__BehavioralEnrichmentRequest"}}},"required":true},"responses":{"200":{"description":"Behavioral enrichment with engagement metrics and client affinity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__BehavioralEnrichmentResponse"}}}},"503":{"description":"Behavioral data not loaded","content":{"application/json":{"example":{"detail":"Behavioral data not available"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/behavioral/propensity":{"post":{"tags":["cbmodels","behavioral"],"summary":"Score donor propensity","description":"## Donor Propensity Scoring\n\nScore voters for likelihood of making a donation based on behavioral signals.\nUses a rule-based model that can be upgraded to ML in the future.\n\n### Propensity Score Components\n\n| Factor | Weight | Rationale |\n|--------|--------|-----------|\n| Previous donor | +0.60 | Historical donors are most likely to donate again |\n| Click engagement | +0.15 | Clickers show investment in content |\n| Campaign diversity | +0.10 | Engaged across multiple campaigns = committed |\n| Base | +0.10 | Baseline probability |\n\n### Score Tiers\n\n- **High (>0.7)** - Strong candidates for donation asks\n- **Medium (0.4-0.7)** - Cultivation candidates\n- **Low (<0.4)** - Focus on engagement first\n\n### Use Cases\n\n- **Fundraising**: \"Who should we ask for donations?\"\n- **Prioritization**: \"Rank this list by donation likelihood\"\n- **Segmentation**: \"Split into high/medium/low propensity groups\"\n\n### Example Interpretation\n\n```\n\"Of 1000 voters, 52 are high-propensity (>0.7), 180 are medium, 768 are low.\nTop candidate: SVID-123 (score 0.85, previous donor, high engagement)\"\n```","operationId":"score_donor_propensity_api_v1_behavioral_propensity_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__DonorPropensityRequest"}}},"required":true},"responses":{"200":{"description":"Donor propensity scores with tier classification","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__DonorPropensityResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/behavioral/status":{"get":{"tags":["cbmodels","behavioral"],"summary":"Check behavioral data status","description":"## Behavioral Data Status\n\nCheck what behavioral data is loaded and available for enrichment.\n\n### Data Sources\n\n- **voter_database** - Currently loaded voter file (i360 or pocket)\n- **phone_email_map** - Links phone numbers to emails (182M records)\n- **email_clickers** - Email engagement data (41M records)\n- **donations** - Donation history (3.2M records)\n- **campaigns** - Campaign metadata (72K records)\n\n### When to Use\n\n- **Before analysis** - Verify behavioral data is ready\n- **Debugging** - Check if data files were loaded\n- **Monitoring** - Track data freshness","operationId":"behavioral_status_api_v1_behavioral_status_get","responses":{"200":{"description":"Status of behavioral data sources","content":{"application/json":{"schema":{}}}}}}},"/cbmodels/api/v1/campaign/duplicates":{"post":{"tags":["cbmodels","campaign"],"summary":"Find cross-source duplicates","description":"Find contacts that appear in multiple campaign sources.\n\nIdentifies high-value contacts who have engaged through multiple channels\n(e.g., donated AND volunteered AND attended events).\n\n**Use cases:**\n- \"Find super-supporters (in 5+ sources)\"\n- \"Who appears in both donor and volunteer lists?\"\n- \"Identify cross-source duplicates for deduplication\"","operationId":"find_duplicates_api_v1_campaign_duplicates_post","parameters":[{"name":"x-tenant-id","in":"header","required":true,"schema":{"type":"string","description":"Tenant slug (required)","title":"X-Tenant-Id"},"description":"Tenant slug (required)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CampaignDuplicatesRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CampaignDuplicatesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/campaign/enrich":{"post":{"tags":["cbmodels","campaign"],"summary":"Enrich contacts with campaign data","description":"Add campaign engagement data to a list of contacts.\n\nFor each email/phone, returns the number of sources they appear in and\nwhich campaigns they've engaged with.\n\n**Use cases:**\n- \"Which of these voters have donated?\"\n- \"Add campaign history to segment results\"\n- \"Enrich i360 results with campaign engagement\"","operationId":"enrich_contacts_api_v1_campaign_enrich_post","parameters":[{"name":"x-tenant-id","in":"header","required":true,"schema":{"type":"string","description":"Tenant slug (required)","title":"X-Tenant-Id"},"description":"Tenant slug (required)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CampaignEnrichRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CampaignEnrichResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/campaign/lookup":{"post":{"tags":["cbmodels","campaign"],"summary":"Find campaign records by email or phone","description":"Search across all campaign sources for records matching an email or phone.\n\nReturns full record data including all fields from the original source file.\nThis enables finding a contact's complete campaign engagement history.\n\n**Use cases:**\n- \"Show all campaign records for john@example.com\"\n- \"What sources have this phone number?\"\n- \"Get full donor/volunteer history for a contact\"","operationId":"lookup_contact_api_v1_campaign_lookup_post","parameters":[{"name":"x-tenant-id","in":"header","required":true,"schema":{"type":"string","description":"Tenant slug (required)","title":"X-Tenant-Id"},"description":"Tenant slug (required)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CampaignLookupRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CampaignLookupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/campaign/sources":{"get":{"tags":["cbmodels","campaign"],"summary":"List all campaign sources for tenant","description":"Get a list of all ingested campaign data sources with statistics.\n\nShows file names, row counts, column counts, and available fields for each source.\n\n**Use cases:**\n- \"What campaign data do we have?\"\n- \"List all donor files\"\n- \"How many records in each source?\"","operationId":"list_sources_api_v1_campaign_sources_get","parameters":[{"name":"x-tenant-id","in":"header","required":true,"schema":{"type":"string","description":"Tenant slug (required)","title":"X-Tenant-Id"},"description":"Tenant slug (required)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CampaignSourcesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cards/catalogs":{"get":{"tags":["cbmodels","cards"],"summary":"Card Catalog: connected data sources","description":"Returns one card per discovered catalog (Airtable, voter files, S3, workspaces, internal DBs) with table counts, row counts, freshness, tags, and the MCP tools that target it. Read-only inventory — no actions. Backs the Catalogs tab of cbloom's Card Catalog admin app. Pass ``?refresh=true`` to drop persistent manifest caches and re-introspect every catalog on this request (slow; useful after a known cbairtable restart unblocks lock contention). Scope it to one catalog with ``?refresh=true&catalog=<name>`` — much cheaper, and what a publisher should call after writing its workspace (avoids re-loading every S3 source).","operationId":"cards_catalogs","parameters":[{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Refresh"}},{"name":"catalog","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Catalog"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cards Catalogs"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cards/intents":{"get":{"tags":["cbmodels","cards"],"summary":"Card Catalog: MCP tool intents","description":"Returns one card per registered MCP tool with its name, description, targets ({catalog | model | scope}), and source module. Tools registered via the raw mcp_server.add_tool() (without going through register_tool() or register_route_intents()) are not included — v1 surfaces the tagged set, not 100% coverage. Backs the Intents tab of cbloom's Card Catalog.","operationId":"cards_intents","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Cards Intents"}}}}}}},"/cbmodels/api/v1/cards/models":{"get":{"tags":["cbmodels","cards"],"summary":"Card Catalog: trained models","description":"Returns one card per trained model (currently the routing model) with version, training metrics, feature columns, primary catalog, and the MCP tools that target it. Read-only inventory — no actions. Backs the Models tab of cbloom's Card Catalog admin app.","operationId":"cards_models","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Cards Models"}}}}}}},"/cbmodels/api/v1/cc/audit":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Recent CC form writes (audit log)","operationId":"audit_api_v1_cc_audit_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Audit Api V1 Cc Audit Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/commission":{"post":{"tags":["cbmodels","cc-forms"],"summary":"Create a commission row (audit-logged)","operationId":"commission_create_api_v1_cc_commission_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CommissionWrite"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Commission Create Api V1 Cc Commission Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/commission/{comm_id}":{"get":{"tags":["cbmodels","cc-forms"],"summary":"A single commission row (resolved)","operationId":"commission_get_api_v1_cc_commission__comm_id__get","parameters":[{"name":"comm_id","in":"path","required":true,"schema":{"type":"string","title":"Comm Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Commission Get Api V1 Cc Commission  Comm Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}},"patch":{"tags":["cbmodels","cc-forms"],"summary":"Edit a commission row (audit-logged)","operationId":"commission_update_api_v1_cc_commission__comm_id__patch","parameters":[{"name":"comm_id","in":"path","required":true,"schema":{"type":"string","title":"Comm Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CommissionWrite"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Commission Update Api V1 Cc Commission  Comm Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/commission/{comm_id}/archive":{"post":{"tags":["cbmodels","cc-forms"],"summary":"Soft-delete / restore a commission","operationId":"commission_archive_api_v1_cc_commission__comm_id__archive_post","parameters":[{"name":"comm_id","in":"path","required":true,"schema":{"type":"string","title":"Comm Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ArchiveAction"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Commission Archive Api V1 Cc Commission  Comm Id  Archive Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/deployment":{"post":{"tags":["cbmodels","cc-forms"],"summary":"Create a deployment (audit-logged)","operationId":"create_deployment_api_v1_cc_deployment_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__DeploymentCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Deployment Api V1 Cc Deployment Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/deployment/form-spec":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Field spec for the launch form","description":"The curated deployment-launch field spec. Select options are derived from\nthe DISTINCT values already present in the synced deployments view —\ncbairtable's schema endpoint strips Airtable's choice lists, and the live\ndata's distinct set is the realistic option set anyway. Cached — see\n_cached_spec.","operationId":"deployment_form_spec_api_v1_cc_deployment_form_spec_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Deployment Form Spec Api V1 Cc Deployment Form Spec Get"}}}}}}},"/cbmodels/api/v1/cc/deployment/{record_id}":{"patch":{"tags":["cbmodels","cc-forms"],"summary":"Edit a deployment (audit-logged)","operationId":"update_deployment_api_v1_cc_deployment__record_id__patch","parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__DeploymentUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Deployment Api V1 Cc Deployment  Record Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/deployments":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Deployment results (computed layer, filtered)","operationId":"deployments_api_v1_cc_deployments_get","parameters":[{"name":"month","in":"query","required":false,"schema":{"type":"string","description":"YYYY-MM filter on start_date","default":"","title":"Month"},"description":"YYYY-MM filter on start_date"},{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"substring over campaign / client / list owner","default":"","title":"Q"},"description":"substring over campaign / client / list owner"},{"name":"status","in":"query","required":false,"schema":{"type":"string","description":"exact master_status","default":"","title":"Status"},"description":"exact master_status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Deployments Api V1 Cc Deployments Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/donations":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Donation records with owner-match (paged)","operationId":"donations_api_v1_cc_donations_get","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","description":"Donation source key","default":"winred","title":"Source"},"description":"Donation source key"},{"name":"month","in":"query","required":false,"schema":{"type":"string","description":"YYYY-MM filter on occurred_at","default":"","title":"Month"},"description":"YYYY-MM filter on occurred_at"},{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"substring over email / name / campaign","default":"","title":"Q"},"description":"substring over email / name / campaign"},{"name":"attributed","in":"query","required":false,"schema":{"type":"string","description":"'yes' (owner-matched) | 'no' (orphan)","default":"","title":"Attributed"},"description":"'yes' (owner-matched) | 'no' (orphan)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Donations Api V1 Cc Donations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/donations/monthly":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Monthly raised series (attributed split)","description":"Newest-first monthly series: donations, dollars, and the attributed share.\n\nCC donation volume is lumpy by design — a month with 681 transactions sits\nbeside one with 71 — so a consumer must render a near-zero month as data,\nnot as a broken chart.","operationId":"donations_monthly_api_v1_cc_donations_monthly_get","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","description":"Donation source key","default":"winred","title":"Source"},"description":"Donation source key"},{"name":"months","in":"query","required":false,"schema":{"type":"integer","maximum":120,"minimum":1,"description":"Trailing months to return","default":24,"title":"Months"},"description":"Trailing months to return"},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Donations Monthly Api V1 Cc Donations Monthly Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/donations/spec":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Donation QA sources + freshness","description":"Source dropdown + per-source freshness, scoped to the caller's tenant\n(the loom proxy forwards the authed user's current_tenant).","operationId":"donations_spec_api_v1_cc_donations_spec_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Donations Spec Api V1 Cc Donations Spec Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/donations/summary":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Attribution rollup for QA","description":"Totals + attributable vs orphan ($ and %) + per-owner gross rollup.\n\nPer-owner amounts are **gross** (a multi-owner donation counts in each\nowner's bucket — for QA visibility, not the commission split, which the\noverlap-weighted engine handles separately).","operationId":"donations_summary_api_v1_cc_donations_summary_get","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"winred","title":"Source"}},{"name":"month","in":"query","required":false,"schema":{"type":"string","description":"YYYY-MM filter on occurred_at","default":"","title":"Month"},"description":"YYYY-MM filter on occurred_at"},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Donations Summary Api V1 Cc Donations Summary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/ledger/{ledger}":{"get":{"tags":["cbmodels","cc-forms"],"summary":"A ledger's row for a month","operationId":"ledger_row_api_v1_cc_ledger__ledger__get","parameters":[{"name":"ledger","in":"path","required":true,"schema":{"type":"string","title":"Ledger"}},{"name":"month","in":"query","required":true,"schema":{"type":"string","description":"YYYY-MM","title":"Month"},"description":"YYYY-MM"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}},"post":{"tags":["cbmodels","cc-forms"],"summary":"Upsert a ledger month (audit-logged)","operationId":"ledger_upsert_api_v1_cc_ledger__ledger__post","parameters":[{"name":"ledger","in":"path","required":true,"schema":{"type":"string","title":"Ledger"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__LedgerSave"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Ledger Upsert Api V1 Cc Ledger  Ledger  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/ledgers/spec":{"get":{"tags":["cbmodels","cc-forms"],"summary":"The three ledgers + their field specs","description":"Ledger definitions for the close screen. cc_costs / cc_other_income carry\na Status select (options from distinct synced values).","operationId":"ledgers_spec_api_v1_cc_ledgers_spec_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Ledgers Spec Api V1 Cc Ledgers Spec Get"}}}}}}},"/cbmodels/api/v1/cc/master/run":{"post":{"tags":["cbmodels","cc-forms"],"summary":"Launch the EOM Master build (background)","operationId":"master_run_api_v1_cc_master_run_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__MasterRun"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Master Run Api V1 Cc Master Run Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/master/status":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Status of the last Master run","operationId":"master_run_status_api_v1_cc_master_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Master Run Status Api V1 Cc Master Status Get"}}}}}}},"/cbmodels/api/v1/cc/owner":{"post":{"tags":["cbmodels","cc-forms"],"summary":"Create a list owner (audit-logged)","operationId":"owner_create_api_v1_cc_owner_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__OwnerWrite"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Owner Create Api V1 Cc Owner Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/owner/{owner_id}/commissions":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Commission rows for an owner","operationId":"owner_commissions_api_v1_cc_owner__owner_id__commissions_get","parameters":[{"name":"owner_id","in":"path","required":true,"schema":{"type":"string","title":"Owner Id"}},{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","description":"include soft-deleted commissions","default":false,"title":"Include Archived"},"description":"include soft-deleted commissions"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Owner Commissions Api V1 Cc Owner  Owner Id  Commissions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/owner/{record_id}":{"get":{"tags":["cbmodels","cc-forms"],"summary":"A single owner (values + resolved links)","operationId":"owner_get_api_v1_cc_owner__record_id__get","parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Owner Get Api V1 Cc Owner  Record Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}},"patch":{"tags":["cbmodels","cc-forms"],"summary":"Edit a list owner (audit-logged)","operationId":"owner_update_api_v1_cc_owner__record_id__patch","parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__OwnerWrite"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Owner Update Api V1 Cc Owner  Record Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/owner/{record_id}/archive":{"post":{"tags":["cbmodels","cc-forms"],"summary":"Soft-delete / restore an owner","operationId":"owner_archive_api_v1_cc_owner__record_id__archive_post","parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ArchiveAction"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Owner Archive Api V1 Cc Owner  Record Id  Archive Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/owners":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Browse/search list owners (snapshot)","operationId":"owners_list_api_v1_cc_owners_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"search name / plain name / LO code / POC","default":"","title":"Q"},"description":"search name / plain name / LO code / POC"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","description":"include soft-deleted owners","default":false,"title":"Include Archived"},"description":"include soft-deleted owners"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Owners List Api V1 Cc Owners Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/owners/form-spec":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Field spec for the List Owner form","operationId":"owners_form_spec_api_v1_cc_owners_form_spec_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Owners Form Spec Api V1 Cc Owners Form Spec Get"}}}}}}},"/cbmodels/api/v1/cc/owners/picker/{picker}":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Searchable options for an owner link field","operationId":"owners_picker_api_v1_cc_owners_picker__picker__get","parameters":[{"name":"picker","in":"path","required":true,"schema":{"type":"string","title":"Picker"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"case-insensitive substring filter","default":"","title":"Q"},"description":"case-insensitive substring filter"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{"type":"string"}},"title":"Response Owners Picker Api V1 Cc Owners Picker  Picker  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/owners/rate-picker":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Commission rate rows (Comm % options)","operationId":"owners_rate_picker_api_v1_cc_owners_rate_picker_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"filter by percent","default":"","title":"Q"},"description":"filter by percent"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":40,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{"type":"string"}},"title":"Response Owners Rate Picker Api V1 Cc Owners Rate Picker Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/owners/sync":{"post":{"tags":["cbmodels","cc-forms"],"summary":"Trigger a base resync (Airtable → snapshot)","operationId":"owners_trigger_sync_api_v1_cc_owners_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Owners Trigger Sync Api V1 Cc Owners Sync Post"}}}}}}},"/cbmodels/api/v1/cc/owners/sync-status":{"get":{"tags":["cbmodels","cc-forms"],"summary":"When the owner snapshot was last synced","operationId":"owners_sync_status_api_v1_cc_owners_sync_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Owners Sync Status Api V1 Cc Owners Sync Status Get"}}}}}}},"/cbmodels/api/v1/cc/picker/{picker}":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Searchable options for a link field","operationId":"picker_options_api_v1_cc_picker__picker__get","parameters":[{"name":"picker","in":"path","required":true,"schema":{"type":"string","title":"Picker"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"case-insensitive substring filter","default":"","title":"Q"},"description":"case-insensitive substring filter"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{"type":"string"}},"title":"Response Picker Options Api V1 Cc Picker  Picker  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/prospecting":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Prospecting rows (paged)","operationId":"prospecting_api_v1_cc_prospecting_get","parameters":[{"name":"month","in":"query","required":false,"schema":{"type":"string","description":"YYYY-MM filter on date","default":"","title":"Month"},"description":"YYYY-MM filter on date"},{"name":"client","in":"query","required":false,"schema":{"type":"string","description":"exact client_name","default":"","title":"Client"},"description":"exact client_name"},{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"substring over campaign / ad_content","default":"","title":"Q"},"description":"substring over campaign / ad_content"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Prospecting Api V1 Cc Prospecting Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/prospecting/spec":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Prospecting columns + clients + freshness","description":"Column inventory (drives the app's dynamic table), the client picker,\nand snapshot freshness for the cc-prospecting app.","operationId":"prospecting_spec_api_v1_cc_prospecting_spec_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Prospecting Spec Api V1 Cc Prospecting Spec Get"}}}}}}},"/cbmodels/api/v1/cc/prospecting/summary":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Prospecting totals + per-client rollup","description":"SUM of every additive measure (vendor_cost, emails, sessions,\ntransactions, revenue, …) overall and per client. calc_* rates are\nexcluded — they don't aggregate.","operationId":"prospecting_summary_api_v1_cc_prospecting_summary_get","parameters":[{"name":"month","in":"query","required":false,"schema":{"type":"string","description":"YYYY-MM filter on date","default":"","title":"Month"},"description":"YYYY-MM filter on date"},{"name":"client","in":"query","required":false,"schema":{"type":"string","description":"exact client_name","default":"","title":"Client"},"description":"exact client_name"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Prospecting Summary Api V1 Cc Prospecting Summary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/reconcile":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Local mirror vs Airtable system-of-record","description":"Confidence check for the dual-write store: which mirrored records are\nin the synced snapshot, which are live-only (sync lag), which are missing.","operationId":"reconcile_report_api_v1_cc_reconcile_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":300,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reconcile Report Api V1 Cc Reconcile Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/results-spec":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Editable-field spec + filter options","description":"Editable result fields (with select options from distinct synced values)\n+ the column order for the results table + status values for the filter.\nCached — see _cached_spec.","operationId":"results_spec_api_v1_cc_results_spec_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Results Spec Api V1 Cc Results Spec Get"}}}}}}},"/cbmodels/api/v1/cc/store/counts":{"get":{"tags":["cbmodels","cc-forms"],"summary":"Local mirror store size","operationId":"store_counts_api_v1_cc_store_counts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Store Counts Api V1 Cc Store Counts Get"}}}}}}},"/cbmodels/api/v1/cc/suppression/jobs":{"post":{"tags":["cbmodels","cc-suppression"],"summary":"Create Job","operationId":"create_job_api_v1_cc_suppression_jobs_post","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbmodels__Body_create_job_api_v1_cc_suppression_jobs_post"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Job Api V1 Cc Suppression Jobs Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}},"get":{"tags":["cbmodels","cc-suppression"],"summary":"Jobs List","operationId":"jobs_list_api_v1_cc_suppression_jobs_get","parameters":[{"name":"include_discarded","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Discarded"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Jobs List Api V1 Cc Suppression Jobs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/suppression/jobs/{job_id}":{"get":{"tags":["cbmodels","cc-suppression"],"summary":"Job Get","operationId":"job_get_api_v1_cc_suppression_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Job Get Api V1 Cc Suppression Jobs  Job Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/suppression/jobs/{job_id}/arm":{"post":{"tags":["cbmodels","cc-suppression"],"summary":"Job Arm","description":"Subscribe the job's resolved emails to its suppression lists.\n\nDry run (confirm=false) reports the plan — unfinished lists and their\nIterable project owners — without materializing an email or touching a\nlist. Confirm moves the job to `arming` and runs the leg in the\nbackground (the universe scan alone is ~43s); poll the job for the\nreceipt. Legal only from `resolved`; a partial arm leaves the job\n`resolved` with the finished lists recorded, so arming again retries\nexactly the rest.","operationId":"job_arm_api_v1_cc_suppression_jobs__job_id__arm_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ArmBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Job Arm Api V1 Cc Suppression Jobs  Job Id  Arm Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/suppression/jobs/{job_id}/discard":{"post":{"tags":["cbmodels","cc-suppression"],"summary":"Job Discard","operationId":"job_discard_api_v1_cc_suppression_jobs__job_id__discard_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__DiscardBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Job Discard Api V1 Cc Suppression Jobs  Job Id  Discard Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/cc/suppression/spec":{"get":{"tags":["cbmodels","cc-suppression"],"summary":"Suppression Spec","operationId":"suppression_spec_api_v1_cc_suppression_spec_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Suppression Spec Api V1 Cc Suppression Spec Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/creative/model_info":{"get":{"tags":["cbmodels","creative"],"summary":"Routing model version + health","description":"Returns the version, training timestamp, training row count, and held-out metrics of the routing model currently loaded by the service. Service status is 'ready' when the model is loaded, 'degraded' when the model file is missing or failed to load.","operationId":"model_info","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Model Info"}}}}}}},"/cbmodels/api/v1/creative/route_template":{"post":{"tags":["cbmodels","creative"],"summary":"Recommend list_owners to route a creative to","description":"Recommend list_owners (audiences) to route an email creative to. Predicts CTR per (template × list_owner) using a LightGBM model trained on historical CC engagement data (Iterable sends + clicks aggregated through list_owner attribution). Input: either an existing template_id (recXXX format) OR a raw HTML string of a draft. Output: ranked list_owner recommendations with predicted CTR and lift vs. broadcast baseline. ELI Data Services excluded. Use when the user asks 'who should I send this email to?', 'what list_owners would click on this?', 'route template X', 'what audiences match this creative?', or any creative-targeting question.","operationId":"route_template","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__RouteTemplateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Route Template"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/elections/{channel}/projection":{"get":{"tags":["cbmodels","Elections"],"summary":"Latest projection for a channel","description":"The derived projection at the channel's most recent results version.","operationId":"latest_projection_api_v1_elections__channel__projection_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Latest Projection Api V1 Elections  Channel  Projection Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/elections/{channel}/projection/recompute":{"post":{"tags":["cbmodels","Elections"],"summary":"Force a recompute now","description":"Recompute + persist + publish the projection immediately (ops/manual;\nthe reconcile loop does this automatically on each snapshot).","operationId":"recompute_api_v1_elections__channel__projection_recompute_post","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Recompute Api V1 Elections  Channel  Projection Recompute Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/elections/{channel}/projection/versions":{"get":{"tags":["cbmodels","Elections"],"summary":"Stored projection versions","description":"Every persisted (channel, version) — the materialized view is replayable.","operationId":"projection_versions_api_v1_elections__channel__projection_versions_get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Projection Versions Api V1 Elections  Channel  Projection Versions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/elections/{channel}/projection/{version}":{"get":{"tags":["cbmodels","Elections"],"summary":"Projection at a specific version","operationId":"projection_at_version_api_v1_elections__channel__projection__version__get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}},{"name":"version","in":"path","required":true,"schema":{"type":"integer","title":"Version"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Projection At Version Api V1 Elections  Channel  Projection  Version  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/datasets":{"get":{"tags":["cbmodels","fabric"],"summary":"List Datasets","description":"Every dataset visible to the caller, with its health re-derived now.\n\n``health`` is computed against the wall clock on the way out rather than\nserved from the column (cbmodels#249): the stored value is only as fresh\nas the last write, and discovery writes happen at service start — which\nis exactly how ``updated_at`` came to track the last restart instead of\nthe last data. Each row also carries the numbers behind the verdict:\n``age_days``, ``freshness_source`` (which of the three timestamps we\naged), ``cadence`` and ``cadence_grace_days``.","operationId":"list_datasets_api_v1_fabric_datasets_get","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Datasets Api V1 Fabric Datasets Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/datasets/unclaimed":{"get":{"tags":["cbmodels","fabric"],"summary":"List Unclaimed Datasets","description":"List datasets the caller could claim (visibility='unclaimed' or NULL).\n\nTwo perms, because two different callers legitimately need this list and\nboth are already entitled to its contents:\n\n* ``fabric.datasets.claim`` — the tenant admin about to claim one. This\n  is the cbloom claim UI's discovery path (cbloom#130) and the reason the\n  gate exists at all: ``visible_datasets()`` hides unclaimed rows from\n  non-admin callers (F1 close, audit R2.4), so without an explicit gate a\n  tenant admin could never see what there is to claim.\n* ``fabric.datasets.admin`` — ops. Added by cbmodels#249: an admin\n  already receives every one of these rows from ``GET /datasets`` (the\n  visibility filter passes unclaimed to admins), and this endpoint is a\n  strict subset filter over that same list, so gating them out protected\n  nothing and only broke ops — including the fleet's own\n  ``svc-fabric-ops`` service account, which holds ``admin`` + ``read``\n  and 403'd on the surface it is named for.\n\nRoute MUST be declared BEFORE `/datasets/{dataset_id}` so FastAPI\ndoesn't interpret \"unclaimed\" as a dataset_id path param.","operationId":"list_unclaimed_datasets_api_v1_fabric_datasets_unclaimed_get","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Unclaimed Datasets Api V1 Fabric Datasets Unclaimed Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/datasets/{dataset_id}":{"get":{"tags":["cbmodels","fabric"],"summary":"Dataset Detail","operationId":"dataset_detail_api_v1_fabric_datasets__dataset_id__get","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","title":"Dataset Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Dataset Detail Api V1 Fabric Datasets  Dataset Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/datasets/{dataset_id}/claim":{"post":{"tags":["cbmodels","fabric"],"summary":"Claim Dataset","description":"Claim an unclaimed (visibility='unclaimed' or legacy NULL) dataset\nfor the caller's tenant.\n\nAtomic UPDATE: sets owner_tenant=caller.current_tenant and\nvisibility='tenant'. Idempotent in shape — re-claim by the SAME\nowner returns 200 with the unchanged row; claim attempt against\nan already-tenant-or-global dataset returns 409 Conflict.\n\nAuth (rubric fabric.datasets.claim, tenant-scoped):\n  - 401 if no caller identity\n  - 400 if caller has no X-CB-Tenant-ID (nothing to claim FOR)\n  - 404 if dataset not in fabric.db\n  - 409 if dataset already has an owner / non-unclaimed visibility\n    AND the existing owner ≠ caller's tenant\n  - 200 + ClaimResponse on success (or idempotent re-claim by owner)","operationId":"claim_dataset_api_v1_fabric_datasets__dataset_id__claim_post","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","title":"Dataset Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ClaimResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/datasets/{dataset_id}/grants":{"post":{"tags":["cbmodels","fabric"],"summary":"Upsert Grant","operationId":"upsert_grant_api_v1_fabric_datasets__dataset_id__grants_post","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","title":"Dataset Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__GrantRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__GrantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}},"get":{"tags":["cbmodels","fabric"],"summary":"List Grants","operationId":"list_grants_api_v1_fabric_datasets__dataset_id__grants_get","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","title":"Dataset Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbmodels__GrantResponse"},"title":"Response List Grants Api V1 Fabric Datasets  Dataset Id  Grants Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/datasets/{dataset_id}/grants/{grantee_tenant}":{"delete":{"tags":["cbmodels","fabric"],"summary":"Revoke Grant","operationId":"revoke_grant_api_v1_fabric_datasets__dataset_id__grants__grantee_tenant__delete","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","title":"Dataset Id"}},{"name":"grantee_tenant","in":"path","required":true,"schema":{"type":"string","title":"Grantee Tenant"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/datasets/{dataset_id}/schema":{"get":{"tags":["cbmodels","fabric"],"summary":"Dataset Schema","operationId":"dataset_schema_api_v1_fabric_datasets__dataset_id__schema_get","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","title":"Dataset Id"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Refresh"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Dataset Schema Api V1 Fabric Datasets  Dataset Id  Schema Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/export":{"get":{"tags":["cbmodels","fabric"],"summary":"Export Fabric","description":"The `.fabric` corpus document — one self-describing artifact of the\nwhole (caller-visible) data fabric: every catalog with its tables, fields,\ntypes, canonical (normalized) names + confidence, flavor, PII class,\nentity keys and freshness. This is the portable form of fabric.db for\nexternal consumers, agents, and the search layer — fetch it, cache it,\nreason over it. Also written to disk by scripts/export_fabric.py.","operationId":"export_fabric_api_v1_fabric_export_get","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Export Fabric Api V1 Fabric Export Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/flavors":{"get":{"tags":["cbmodels","fabric"],"summary":"Flavors","description":"Introspection — returns the flavor enum + pydantic schemas.","operationId":"flavors_api_v1_fabric_flavors_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Flavors Api V1 Fabric Flavors Get"}}}}}}},"/cbmodels/api/v1/fabric/grants":{"get":{"tags":["cbmodels","fabric"],"summary":"List Grants For Tenant","description":"All grants relevant to the caller's tenant — on datasets it owns, plus\ngrants where it is the grantee. Returns [] when the caller has no tenant.","operationId":"list_grants_for_tenant_api_v1_fabric_grants_get","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbmodels__GrantWithDataset"},"title":"Response List Grants For Tenant Api V1 Fabric Grants Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/mappings":{"get":{"tags":["cbmodels","fabric"],"summary":"Mappings","operationId":"mappings_api_v1_fabric_mappings_get","parameters":[{"name":"dataset_id","in":"query","required":true,"schema":{"type":"string","title":"Dataset Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Mappings Api V1 Fabric Mappings Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/materialize/{driver_name}":{"post":{"tags":["cbmodels","fabric"],"summary":"Materialize","description":"Materialize an HTTP-pull driver's datasets into a federation catalog.\n\nPulls every dataset the driver exposes (re-upserting descriptor rows —\na scheduled call doubles as discovery refresh), writes one DuckDB per\ndataset-id prefix under data/fabric_cache/, and (re)registers it as a\nqueryable catalog with ownership inherited from the member datasets.\nGeneric: works for any driver in settings.fabric_http_drivers\n(cbpulse, cbbanking, future cbradio/CBApp pools).\n\n``?wait=false`` returns immediately and runs in the background — for\ncallers behind the mesh proxy's 30s upstream timeout (e.g. the cbcron\nrefresh jobs pulling large drivers like cbradio's 400K-row rates).\nThe per-driver lock still refuses overlapping runs; the last completed\nsummary is readable at GET /materialize/{driver_name}/last.","operationId":"materialize_api_v1_fabric_materialize__driver_name__post","parameters":[{"name":"driver_name","in":"path","required":true,"schema":{"type":"string","title":"Driver Name"}},{"name":"wait","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Wait"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Materialize Api V1 Fabric Materialize  Driver Name  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/materialize/{driver_name}/last":{"get":{"tags":["cbmodels","fabric"],"summary":"Materialize Last","description":"Last completed materialization summary for a driver (this process).","operationId":"materialize_last_api_v1_fabric_materialize__driver_name__last_get","parameters":[{"name":"driver_name","in":"path","required":true,"schema":{"type":"string","title":"Driver Name"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Materialize Last Api V1 Fabric Materialize  Driver Name  Last Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/pipelines":{"post":{"tags":["cbmodels","pipelines"],"summary":"Create Pipeline","description":"Create a pipeline: compile v1 and save it. Does NOT run.\n\nCompile failure = nothing saved (400 with the compiler error). Name\ncollisions within the tenant 409. The output catalog will be\n``pl_<name>`` once first run — pick a name that reads well in FQL.","operationId":"create_pipeline_api_v1_fabric_pipelines_post","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__PipelineCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Pipeline Api V1 Fabric Pipelines Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}},"get":{"tags":["cbmodels","pipelines"],"summary":"List Pipelines","description":"List pipelines visible to the caller (own tenant + global + own private).\n\nEach row carries head_version and the last run's status/dataset so a\nUI can render the catalog without N+1 calls.","operationId":"list_pipelines_api_v1_fabric_pipelines_get","parameters":[{"name":"name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Pipelines Api V1 Fabric Pipelines Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/pipelines/runs/{run_id}":{"get":{"tags":["cbmodels","pipelines"],"summary":"Run Detail","description":"Poll a run: status queued|running|succeeded|failed, plus row_count/\nbytes/duration_ms on success and error on failure.","operationId":"run_detail_api_v1_fabric_pipelines_runs__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Run Detail Api V1 Fabric Pipelines Runs  Run Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/pipelines/sweep":{"post":{"tags":["cbmodels","pipelines"],"summary":"Sweep","description":"Manual dataset sweep for the caller's tenant (design §3 Cull).\n\nCulls the tenant's idle pipeline-born datasets (30d TTL) and deletes\nsuperseded run files past their 7d grace — the endpoint the quota\nerror tells you to call. Scripts are never touched.","operationId":"sweep_api_v1_fabric_pipelines_sweep_post","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Sweep Api V1 Fabric Pipelines Sweep Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/pipelines/{pipeline_id}":{"patch":{"tags":["cbmodels","pipelines"],"summary":"Set Visibility","description":"Change a pipeline's visibility (visibility model v2, 2026-07-07).\n\nprivate = creator-only · tenant = default · global = super users only\n(fabric.pipelines.admin). Owner-tenant gated, and going private is\ncreator-gated — a teammate can't lock the team out of a shared script.\nCascades to the materialized output dataset (pl_<name>) so the catalog's\nread scope follows the script immediately, not at next run.","operationId":"set_visibility_api_v1_fabric_pipelines__pipeline_id__patch","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__VisibilityRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Visibility Api V1 Fabric Pipelines  Pipeline Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}},"get":{"tags":["cbmodels","pipelines"],"summary":"Pipeline Detail","description":"Pipeline detail: head version (sql + frozen contract) + last run.","operationId":"pipeline_detail_api_v1_fabric_pipelines__pipeline_id__get","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Pipeline Detail Api V1 Fabric Pipelines  Pipeline Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}},"delete":{"tags":["cbmodels","pipelines"],"summary":"Delete Pipeline","description":"Script cull: SOFT-delete the pipeline and sweep its output dataset.\n\nVersions and run history stay readable; the tombstoned dataset row\nkeeps its lineage. Culling is the only way a script dies, and it is\nalways manual (design §3).","operationId":"delete_pipeline_api_v1_fabric_pipelines__pipeline_id__delete","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Pipeline Api V1 Fabric Pipelines  Pipeline Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/pipelines/{pipeline_id}/compile":{"post":{"tags":["cbmodels","pipelines"],"summary":"Dry Compile","description":"Dry-compile arbitrary SQL against this pipeline's identity (cycle\ncheck included). Saves nothing — the chat's draft-fix loop endpoint.\nReturns the would-be contract: input_catalogs, output_schema, notes.","operationId":"dry_compile_api_v1_fabric_pipelines__pipeline_id__compile_post","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__SqlRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Dry Compile Api V1 Fabric Pipelines  Pipeline Id  Compile Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/pipelines/{pipeline_id}/run":{"post":{"tags":["cbmodels","pipelines"],"summary":"Run","description":"Run the head version (or ``?version=``); materializes ``pl_<name>``.\n\n``?wait=false`` starts the run in the background and returns\nimmediately — poll GET /{id}/runs (newest first). Overlapping runs of\none pipeline 409. A run that would exceed the tenant's storage quota\nis refused with the sweep endpoint named in the error.","operationId":"run_api_v1_fabric_pipelines__pipeline_id__run_post","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"version","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Version"}},{"name":"wait","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Wait"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Run Api V1 Fabric Pipelines  Pipeline Id  Run Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/pipelines/{pipeline_id}/runs":{"get":{"tags":["cbmodels","pipelines"],"summary":"Run History","description":"Run history, newest first.","operationId":"run_history_api_v1_fabric_pipelines__pipeline_id__runs_get","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Run History Api V1 Fabric Pipelines  Pipeline Id  Runs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/pipelines/{pipeline_id}/versions":{"post":{"tags":["cbmodels","pipelines"],"summary":"Add Version","description":"Compile-gated new version; advances the head pointer. The previous\nversion stays immutable underneath (git model, one level deep).","operationId":"add_version_api_v1_fabric_pipelines__pipeline_id__versions_post","parameters":[{"name":"pipeline_id","in":"path","required":true,"schema":{"type":"string","title":"Pipeline Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__SqlRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Version Api V1 Fabric Pipelines  Pipeline Id  Versions Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/profiles":{"get":{"tags":["cbmodels","fabric"],"summary":"Profiles","description":"Characterization profiles for every visible dataset — the catalog a\nsearch engine (or external consumer) browses to request custom cuts\n(cbmodels#168). flavor + normalized-field coverage + freshness + PII class\n+ join surface, all derived from the registry.","operationId":"profiles_api_v1_fabric_profiles_get","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Profiles Api V1 Fabric Profiles Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/reports/{name}":{"get":{"tags":["cbmodels","fabric"],"summary":"Report","description":"Run a named canonical report and return JSON.\n\nSupported reports: cash_position","operationId":"report_api_v1_fabric_reports__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"as_of_date","in":"query","required":true,"schema":{"type":"string","format":"date","title":"As Of Date"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Report Api V1 Fabric Reports  Name  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/fabric/staleness":{"get":{"tags":["cbmodels","fabric"],"summary":"Staleness Report","description":"The age of every visible dataset, ranked worst first — the alert surface.\n\nThis is what cbmodels#249 asked for once the evidence existed: a single\nplace that answers \"what in the fabric has stopped moving\", computed from\nage rather than from whether the file still opens. Four buckets:\n\n* ``stale`` — past its declared cadence's grace window, or past the\n  undeclared-age outer bound, or carrying no freshness evidence at all.\n  This is the alert set.\n* ``ok`` — inside its promise.\n* ``undeclared`` — no cadence declared, so \"overdue\" is not computable\n  for it beyond the outer bound. Not a failure; a to-do. Shrinking this\n  bucket is how the alert gets sharper, and it is deliberately visible\n  rather than buried, because an undeclared dataset is the state that\n  let nine frozen ``cc_*`` catalogs look healthy for two months.\n* ``no_evidence`` — the subset of ``stale`` that has no timestamp at all\n  (a zombie catalog, or a driver that never reports one). Broken out\n  because the fix is different: those need a writer, not a feeder.\n\nRead-only and cheap — one registry scan, no catalog I/O. Safe to poll\nfrom a cbcron job; ``scripts/fabric_staleness_alert.py`` does exactly\nthat and is the intended alerting path.","operationId":"staleness_report_api_v1_fabric_staleness_get","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Staleness Report Api V1 Fabric Staleness Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/federation/catalogs":{"get":{"tags":["cbmodels","federation"],"summary":"List all federated data catalogs","description":"List data catalogs visible to the caller — Airtable bases, voter files, election results, Druid analytics, S3 sources, workspaces. Shows table counts, row counts, and data freshness. Catalogs the caller's tenant has no access to are filtered out (F1 close).\n\n**Query syntax:** Each catalog is attached as a DuckDB schema. Address tables as `catalog_name.table_name`. The table name usually matches the catalog's source name.\n\n**Example workflow:**\n1. Call this endpoint to see `s3_vt_deployment_table` (table_count=1).\n2. Query it: `SELECT * FROM s3_vt_deployment_table.vt_deployment_table LIMIT 5`.\n3. JOIN across catalogs: `SELECT d.name, COUNT(*) FROM s3_vt_send_attempts.vt_send_attempts s JOIN s3_vt_deployment_table.vt_deployment_table d ON s.deployment_id = d.id GROUP BY d.name ORDER BY 2 DESC LIMIT 5`.","operationId":"data_catalog_api_v1_federation_catalogs_get","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Data Catalog Api V1 Federation Catalogs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/federation/query":{"post":{"tags":["cbmodels","federation"],"summary":"Execute cross-catalog SQL query","description":"Execute read-only DuckDB SQL across one or more attached catalogs.\n\n**Syntax:** `SELECT ... FROM catalog_name.table_name`. Call `/catalogs` first to discover catalog names and table schemas.\n\n**Example:** `SELECT d.name, COUNT(*) AS sends FROM s3_vt_send_attempts.vt_send_attempts s JOIN s3_vt_deployment_table.vt_deployment_table d ON s.deployment_id = d.id GROUP BY d.name ORDER BY sends DESC LIMIT 5`.\n\nJOINs across catalogs work natively. DuckDB dialect — use standard aggregates, CTEs, window functions as needed. 30-second timeout.\n\nCatalogs the caller's tenant cannot see are refused at ATTACH (returned in `attach_errors` with reason 'not visible to caller').\n\n**Locked catalogs:** a cbdatabase-managed store being written is served via a live-query fallback (`attach_fallbacks` notes the point-in-time copy); if that fails too, the query returns **503** with `error_code: catalog_locked` — retry later, don't fix the SQL.\n\n**Provenance:** every result (success or error) carries `query_id`, the executed `sql`, `tables_referenced` (`catalog.table`), `catalogs_attached`, `row_count`, `truncated`, and `query_time_ms` — enough to show the operator exactly what data backed the answer.\n\n**Errors never answer 200.** Every failure keeps the body above and adds an `error_code` naming its cause: **400** when the caller must change the SQL (`full_scan_guard`, `statement_not_allowed`, `sql_error`), **403** `catalog_not_visible`, **500** `execution_failed`, **502** `catalog_unavailable`, **503** `catalog_locked`, **504** timeout.","operationId":"data_query_api_v1_federation_query_post","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__QueryRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Data Query Api V1 Federation Query Post"}}}},"400":{"description":"The query did not run, or ran and was rejected as written; either way the caller must change the request. `error` carries the message and how to proceed. `error_code` is `full_scan_guard` (unbounded read of an event-stream or >50M-row catalog: add a date bound, LIMIT or SAMPLE, or retry with allow_full_scan=true once the user has confirmed), `statement_not_allowed` (not a read-only SELECT), or `sql_error` (DuckDB rejected the SQL: parser, binder, catalog, or conversion error, e.g. a misspelled column).","content":{"application/json":{"example":{"error":"full-scan guard: 'list_owner_matrix' is event-stream class (temperament=event-driven) and the query has no date bound, LIMIT, or SAMPLE ...","error_code":"full_scan_guard","catalogs_attached":[],"query_id":"8802b5ba1aa8","sql":"SELECT * FROM list_owner_matrix.list_email_membership","tables_referenced":["list_owner_matrix.list_email_membership"]}}}},"403":{"description":"`catalog_not_visible`: the SQL references a catalog the caller cannot read. `attach_errors` names it."},"500":{"description":"`execution_failed`: the query ran and failed for a reason that is not the SQL as written (IO, memory, internal). `error` carries DuckDB's message."},"502":{"description":"`catalog_unavailable`: a referenced catalog could not be attached (file missing, attach failure). `attach_errors` names it."},"503":{"description":"`catalog_locked`: a writer holds a referenced catalog and the live-query fallback could not serve it (payload in `detail`, Retry-After set); or federated query capacity is full. Retry later."},"504":{"description":"The query ran past the federated query timeout."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/federation/query/export":{"post":{"tags":["cbmodels","federation"],"summary":"Run a query and export the FULL result set to a download link","description":"The bridge past the 10,000-row inline cap: executes the SELECT with NO row limit, lands the complete result in cbfiles as parquet or csv, and returns a signed `download_url` (valid 7 days).\n\n**When to use:** a /query result came back `truncated: true` and the user wants the full set; the user asks for a file, download, export, or link; or a result should be attached to a project. For results the user will re-query in chat, prefer a saved pipeline instead — this produces a FILE, not a catalog.\n\n**Limits:** 120s wall clock, 512MB file cap — narrow the query (columns, date bound) if refused. Same visibility rules and full-scan guard as /query.\n\n**Returns:** `download_url`, `bucket`, `object_path`, `bytes`, `rows_exported`, `expires_in`, plus the standard provenance trio (`query_id`, `sql`, `tables_referenced`).\n\n**Errors never answer 200**, exactly as for /query, plus export's own: **413** `export_too_large`, **502** `export_upload_failed`, **504** `export_timeout`.","operationId":"data_query_export_api_v1_federation_query_export_post","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ExportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Data Query Export Api V1 Federation Query Export Post"}}}},"400":{"description":"The query did not run, or ran and was rejected as written; either way the caller must change the request. `error` carries the message and how to proceed. `error_code` is `full_scan_guard` (unbounded read of an event-stream or >50M-row catalog: add a date bound, LIMIT or SAMPLE, or retry with allow_full_scan=true once the user has confirmed), `statement_not_allowed` (not a read-only SELECT), or `sql_error` (DuckDB rejected the SQL: parser, binder, catalog, or conversion error, e.g. a misspelled column).","content":{"application/json":{"example":{"error":"full-scan guard: 'list_owner_matrix' is event-stream class (temperament=event-driven) and the query has no date bound, LIMIT, or SAMPLE ...","error_code":"full_scan_guard","catalogs_attached":[],"query_id":"8802b5ba1aa8","sql":"SELECT * FROM list_owner_matrix.list_email_membership","tables_referenced":["list_owner_matrix.list_email_membership"]}}}},"403":{"description":"`catalog_not_visible`: the SQL references a catalog the caller cannot read. `attach_errors` names it."},"500":{"description":"`execution_failed`: the query ran and failed for a reason that is not the SQL as written (IO, memory, internal). `error` carries DuckDB's message."},"502":{"description":"`catalog_unavailable` as for /query, or `export_upload_failed`: the file could not be stored in cbfiles."},"503":{"description":"`catalog_locked`: a writer holds a referenced catalog and the live-query fallback could not serve it (payload in `detail`, Retry-After set); or federated query capacity is full. Retry later."},"504":{"description":"`export_timeout`: the export ran past its wall-clock limit; narrow the query or add a date bound."},"413":{"description":"`export_too_large`: the result is past the export file cap; narrow the query."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/health":{"get":{"tags":["cbmodels","health"],"summary":"Check API health status","description":"## Health Check\n\nReturns the current health status of the API, including:\n\n- **Model status** - Is the baseline model loaded?\n- **Database status** - Is the DuckDB connection active?\n- **SVID configuration** - Is the State Voter ID field configured?\n\n### When to Use\n\n- **Before analysis** - Verify all systems are ready\n- **Monitoring** - Integrate with health check systems\n- **Debugging** - Identify which component is failing\n\n### Health States\n\n- **healthy** - All systems operational\n- **degraded** - Some components unavailable (analysis may fail)","operationId":"health_check_api_v1_health_get","responses":{"200":{"description":"Current health status of all API components","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__cbmodels__api__schemas__HealthResponse"}}}}}}},"/cbmodels/api/v1/health/i360-portal":{"get":{"tags":["cbmodels","health"],"summary":"i360 portal search smoke test","description":"Fires a known MI search through the portal API and verifies counts.","operationId":"i360_portal_ping_api_v1_health_i360_portal_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response I360 Portal Ping Api V1 Health I360 Portal Get"}}}}}}},"/cbmodels/api/v1/hygiene/address/clean":{"post":{"tags":["cbmodels","hygiene"],"summary":"Clean and geocode a single address","description":"Standardize and geocode a single address string.\n\nThis endpoint:\n1. Sends the address to geo.campaignbrain.dev for geocoding\n2. Parses the result into standardized address components\n3. Optionally looks up congressional district via districts.campaignbrain.dev\n\n**Use cases:**\n- Cleaning user-entered addresses\n- Geocoding addresses for mapping\n- Standardizing addresses for deduplication\n- Looking up congressional districts for addresses","operationId":"clean_address_api_v1_hygiene_address_clean_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__AddressCleanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__AddressCleanResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/hygiene/address/clean/batch":{"post":{"tags":["cbmodels","hygiene"],"summary":"Clean and geocode multiple addresses","description":"Standardize and geocode multiple addresses in a single request.\n\nThis endpoint:\n1. Processes addresses concurrently (up to 10 parallel requests)\n2. Returns results in the same order as the input\n3. Handles partial failures gracefully - check each result's 'matched' field\n\n**Limits:**\n- Maximum 100 addresses per request\n- Timeout of 30 seconds per address\n\n**Use cases:**\n- Batch cleaning of address lists\n- Processing CSV/Excel address columns\n- Bulk geocoding for mapping applications","operationId":"clean_addresses_batch_api_v1_hygiene_address_clean_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__AddressCleanBatchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__AddressCleanBatchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/i360/portal-search":{"post":{"tags":["cbmodels","i360"],"summary":"Search i360 voter portal (all 50 states)","description":"Run a voter count through the i360 portal with geographic, demographic, and behavioral filters. Returns individuals, residences, phones (mobile + landline), and emails. Works across all 50 states — not limited to MI. City/county/district names are auto-resolved to i360 numeric IDs.","operationId":"i360_portal_search_api_v1_i360_portal_search_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__PortalSearchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response I360 Portal Search Api V1 I360 Portal Search Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/intake/credentials/{probe_id}":{"post":{"tags":["cbmodels","intake"],"summary":"Submit credentials and re-probe","operationId":"submit_credentials_api_v1_intake_credentials__probe_id__post","parameters":[{"name":"probe_id","in":"path","required":true,"schema":{"type":"string","title":"Probe Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CredentialsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Submit Credentials Api V1 Intake Credentials  Probe Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/intake/ingest":{"post":{"tags":["cbmodels","intake"],"summary":"Commit a probed source to a dataset","operationId":"ingest_endpoint_api_v1_intake_ingest_post","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__IngestRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Ingest Endpoint Api V1 Intake Ingest Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/intake/jobs/{job_id}":{"get":{"tags":["cbmodels","intake"],"summary":"Poll ingest job status","operationId":"get_job_api_v1_intake_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Job Api V1 Intake Jobs  Job Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/intake/probe":{"post":{"tags":["cbmodels","intake"],"summary":"Probe a source","operationId":"probe_endpoint_api_v1_intake_probe_post","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ProbeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Probe Endpoint Api V1 Intake Probe Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/intake/probe-file":{"post":{"tags":["cbmodels","intake"],"summary":"Probe an uploaded file (multipart)","description":"Multipart sibling of POST /probe for file uploads.\n\nReads the upload's full bytes, computes a SHA256 content-hash, and uses\n``upload://<sha>`` as the synthetic source URI so the idempotent-replay\nmachinery (caller-scoped source_hash lookup) works the same as for\nurl/text probes. On ``ingest_ready``, the bytes are persisted under\nINTAKE_UPLOADS_DIR/<probe_id>.<ext> so ``_ingest_file`` can find them\nlater — the absolute path is stamped into ``report[\"persisted_path\"]``.\nUnsupported uploads (too large, unknown format) do NOT persist bytes.","operationId":"probe_file_endpoint_api_v1_intake_probe_file_post","parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbmodels__Body_probe_file_endpoint_api_v1_intake_probe_file_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Probe File Endpoint Api V1 Intake Probe File Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/intake/probes":{"get":{"tags":["cbmodels","intake"],"summary":"List caller's recent probes","operationId":"list_probes_api_v1_intake_probes_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Probes Api V1 Intake Probes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/intake/probes/{probe_id}":{"get":{"tags":["cbmodels","intake"],"summary":"Fetch a probe report","operationId":"get_probe_api_v1_intake_probes__probe_id__get","parameters":[{"name":"probe_id","in":"path","required":true,"schema":{"type":"string","title":"Probe Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Probe Api V1 Intake Probes  Probe Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/airtable-sync":{"post":{"tags":["cbmodels","master"],"summary":"Airtable Sync","description":"Fire a refresh of the CC Master Airtable base (the accounting-side gate).\n\nThe build's accounting inputs (cc_other_income, cc_costs, deployment\npricing) come from cbairtable's snapshot of the CC Master base; it only\nmoves when the base is re-synced. This drives that sync in the background\n(single-flight) and auto-republishes the snapshot cbairtable#19. Poll\n``GET /airtable-sync/status``; then re-check ``/months/{month}/readiness``.","operationId":"airtable_sync_api_v1_master_airtable_sync_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__AirtableSyncRequest","default":{"mode":"auto"}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/airtable-sync/status":{"get":{"tags":["cbmodels","master"],"summary":"Airtable Sync Status","description":"Current status of the CC Airtable base refresh (idle/running/done/error).","operationId":"airtable_sync_status_api_v1_master_airtable_sync_status_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/freshness":{"get":{"tags":["cbmodels","master"],"summary":"Freshness","description":"Upstream-data freshness signal — Iterable + Airtable.\n\nOperationalizes the CC data canon: \"Iterable feed completeness +\nAirtable freshness is the canonical signal for any CC anomaly.\"\nCached in-process for 5 minutes (Iterable scan against the\n13.9B-row table is real-time-money).","operationId":"freshness_api_v1_master_freshness_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/inbox":{"get":{"tags":["cbmodels","master"],"summary":"Inbox","operationId":"inbox_api_v1_master_inbox_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months":{"get":{"tags":["cbmodels","master"],"summary":"List Months","operationId":"list_months_api_v1_master_months_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}},"post":{"tags":["cbmodels","master"],"summary":"Add Month","description":"+Add a new master month with its clicks cohort.\n\nThe two per-month build parameters — the report month and the\nclicks-cohort ceiling — are all the operator supplies; the build\nderives the rest (window, click-credit horizon). Lands the month in\n``prep``. 409 if the month already exists (use the gates/cohort\nendpoints to edit an existing one).","operationId":"add_month_api_v1_master_months_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__AddMonthRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}":{"delete":{"tags":["cbmodels","master"],"summary":"Delete Month","description":"Remove a never-built ``prep`` month. 404 if absent, 409 if it has any\nhistory — see :meth:`StateMachine.delete_month` for the guard and why.","operationId":"delete_month_api_v1_master_months__month__delete","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}},"get":{"tags":["cbmodels","master"],"summary":"Get Month","operationId":"get_month_api_v1_master_months__month__get","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/cohort":{"post":{"tags":["cbmodels","master"],"summary":"Set Cohort","description":"Update the clicks cohort for an existing month (audited).","operationId":"set_cohort_api_v1_master_months__month__cohort_post","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CohortRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/dashboard":{"get":{"tags":["cbmodels","master"],"summary":"Get Dashboard Link","description":"Build Eric's tabbed CC dashboard, ship it to cbfiles, return the link.\n\nTabs reproduce the GROUND_ONLY exceptions panel (Exceptions) plus its\nfull backing reconciliation (QA Reconciliation), built from the same\nsource as the in-app QA panel. Delivered as a cbfiles shortlink — a\nclean ``files.campaignbrain.dev`` download that opens everywhere — the\nsame way the master xlsx is delivered. (Serving it through the auth-gated\n``/proxy/cbmodels`` path saved a 401 JSON body as the .xlsx for anyone\nwhose download navigation didn't carry the app's auth.)\n\nCached per month (keyed on the source xlsx mtime) so repeat opens don't\nre-upload. Returns ``{url, generated_at, cached}``.","operationId":"get_dashboard_link_api_v1_master_months__month__dashboard_get","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/gates/{gate}":{"post":{"tags":["cbmodels","master"],"summary":"Set Gate","operationId":"set_gate_api_v1_master_months__month__gates__gate__post","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"gate","in":"path","required":true,"schema":{"type":"string","title":"Gate"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__GateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/log":{"get":{"tags":["cbmodels","master"],"summary":"Tail the build log for a month's run","description":"Tail the build log for ``month``.\n\nA button-triggered build runs detached in its own systemd scope and writes\nto ``cbcc/logs/master-build-<YYYYMM>-<run8>.log``. Nothing exposed that:\n``master_status`` only knows about runs launched through the ungated\n``/cc/master/run`` (which has no frontend caller), so for the builds people\nactually run it reports ``idle`` while a 45-minute step is mid-flight.\nThat is how \"working\" and \"wedged\" come to look identical.\n\nThe path is fully derivable from month + run id, so this needs no state\ncolumn. Defaults to the month's in-flight run, else its most recent log.","operationId":"month_build_log_api_v1_master_months__month__log_get","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"tail","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Tail"}},{"name":"run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/qa":{"get":{"tags":["cbmodels","master"],"summary":"Get Qa Classification","description":"Auto-characterize GROUND_ONLY rows on the master QA tab.\n\nReads the canonical xlsx for ``month`` and classifies each\nGROUND_ONLY row into one of: p2p_by_design / not_sent /\nphantom_send / needs_review. Phantom verdicts require the\n``at_cid_in_iterable`` sniff check to have been run on this\nmonth (its payload provides the cid set).","operationId":"get_qa_classification_api_v1_master_months__month__qa_get","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/readiness":{"get":{"tags":["cbmodels","master"],"summary":"Get Readiness","description":"Pre-build readiness checklist for ``month`` (5 derivable signals).\n\nEncodes Brad's tribal-knowledge gate: Iterable month complete +\nAirtable fresh + at least one of CCMAIN_CC_COSTS/OTHER_INCOME has a\nrow for the month (blocking) + P2P + deployments-ready\n(non-blocking, informational). The cbloom UI renders this above the\nTrigger Build button; the trigger-build endpoint itself enforces\nthe same gate.","operationId":"get_readiness_api_v1_master_months__month__readiness_get","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/reopen":{"post":{"tags":["cbmodels","master"],"summary":"Reopen","operationId":"reopen_api_v1_master_months__month__reopen_post","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ReopenRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/reship":{"post":{"tags":["cbmodels","master"],"summary":"Reship","description":"Repoint a month to a corrected xlsx WITHOUT a rebuild.\n\nFor post-close corrections where the shipped numbers are already\nauthoritative (e.g. a manual List Owner fix taken from the source-of-truth\nreport) and re-running the whole build is wrong or blocked. Uploads the\nfile, bumps the version, repoints ``last_xlsx_url``, records ``reason`` in\nthe transition trail, and settles the month to ``closed`` (also resolving a\nmonth stuck in ``rerun``). Deliberately skips the review gate, sniff-test,\nSnowflake, and any build — that is the whole point.","operationId":"reship_api_v1_master_months__month__reship_post","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ReshipRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/send-back":{"post":{"tags":["cbmodels","master"],"summary":"Send Back","operationId":"send_back_api_v1_master_months__month__send_back_post","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__SendBackRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/ship":{"post":{"tags":["cbmodels","master"],"summary":"Ship","operationId":"ship_api_v1_master_months__month__ship_post","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ShipRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/summary":{"get":{"tags":["cbmodels","master"],"summary":"Get Summary","description":"Headline P&L for the month (revenue → CC cut → net profit), summed from\nthe master xlsx with parity to the Summary tab. 404 if no master xlsx yet.","operationId":"get_summary_api_v1_master_months__month__summary_get","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/trigger-build":{"post":{"tags":["cbmodels","master"],"summary":"Trigger Build","description":"Transition the month → building and fire the orchestrator.\n\nGated on the readiness checklist (see ``readiness.py``) — 8 checks:\n4 always-blocking, plus deployment_resolution_1to1 which blocks only\nwhen a duplicate is priced (cbmodels#197).\nBlocking FAILs return 422 with per-check details; override with\n``force=true`` + non-empty ``force_reason`` (both audited in the\ntransition trail).","operationId":"trigger_build_api_v1_master_months__month__trigger_build_post","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__TriggerBuildRequest","default":{"force":false}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/master/months/{month}/workbook":{"get":{"tags":["cbmodels","master"],"summary":"Get Workbook Link","description":"A shortlink to the month's CURRENT master workbook, shipped or not.\n\n``ship`` mints a link only when a month closes, so a month in ``review`` —\nthe state in which people most need to read the workbook — has none, and\nthe operator ends up mailing a file or a hand-minted URL. This gives the\nreview copy the same delivery the shipped copy gets: upload to cbfiles,\nsigned + shortened, 7 days.\n\nTarget path is ``cc-master/<YYYY>/cc-master-<YYYYMMDD>-beta.xlsx`` —\ndeliberately NOT the ``-with-qa-v<N>`` path ``ship`` writes, so a review\nlink can never be mistaken for, or overwrite, a shipped version. ``-beta``\nis also what the July 2026 review copy was hand-uploaded as on 2026-08-24,\nso the link already in Erick's inbox keeps resolving to the current file.\n\nCached per month on the xlsx mtime, like ``dashboard``: a re-populated\nworkbook re-uploads on the next open; an unchanged one does not.","operationId":"get_workbook_link_api_v1_master_months__month__workbook_get","parameters":[{"name":"month","in":"path","required":true,"schema":{"type":"string","title":"Month"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/r/install":{"post":{"tags":["cbmodels","r-scripts"],"summary":"Install R packages from CRAN","description":"Install one or more CRAN packages. Provide a comma-separated list of package names. Packages are installed to the site library and persist across runs. Returns what was installed, already present, and failed. Default timeout: 10 min (some packages compile C/Fortran).","operationId":"install_r_packages_api_v1_r_install_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__InstallPackagesRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__InstallPackagesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/r/run":{"post":{"tags":["cbmodels","r-scripts"],"summary":"Run an R script in a sandboxed subprocess","description":"Execute R source code with optional input data (SQL query or URL). The data is loaded as a `df` data.frame in the R environment. Any files the script writes (PNG, CSV, PDF) are uploaded to cbfiles and returned as short links. Pre-installed packages: arrow, data.table, tidyverse, survey, lme4, glmnet, ggplot2. Limits: 8 GB RAM, 4 BLAS threads, max 4 concurrent, timeout up to 1800s.","operationId":"run_r_script_api_v1_r_run_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__RunScriptRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__RunScriptResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/radio/datasoup-coverage":{"get":{"tags":["cbmodels","radio-coverage"],"summary":"Datasoup Coverage","description":"Radio stations covering a geography, ranked by listenable Geo% (DataSoup).\n\nMirrors the ``datasoup_radio_coverage`` MCP tool over REST for mesh callers.\nEach station row carries ``geo_pct`` (listenable Geo%), station/callsign,\nlicensee, rep, wattage, format, and a demographic %. On-demand (first call\ndrives the site) and cached per (state, geography).","operationId":"datasoup_coverage_api_v1_radio_datasoup_coverage_get","parameters":[{"name":"state","in":"query","required":true,"schema":{"type":"string","description":"US state name, e.g. 'michigan'","title":"State"},"description":"US state name, e.g. 'michigan'"},{"name":"geography","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional sub-geography, e.g. 'House District 42' or 'MI-HD-042'","title":"Geography"},"description":"Optional sub-geography, e.g. 'House District 42' or 'MI-HD-042'"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":0,"description":"0 = all stations, else top N","default":0,"title":"Limit"},"description":"0 = all stations, else top N"},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Re-drive the site, bypassing cache","default":false,"title":"Force"},"description":"Re-drive the site, bypassing cache"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Datasoup Coverage Api V1 Radio Datasoup Coverage Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/s3/sources":{"get":{"tags":["cbmodels","s3"],"summary":"List all configured S3 sources","description":"Returns every source defined in configs/s3_sources.yaml together with its current sync status from the state file. Each item also includes the federation `catalog_name` (prefixed `s3_`) so SQL queries via /api/v1/federation/query can address the same data — the two surfaces use different names by convention.","operationId":"list_sources_api_v1_s3_sources_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Sources Api V1 S3 Sources Get"}}}}}}},"/cbmodels/api/v1/s3/sources/{name}/browse":{"get":{"tags":["cbmodels","s3"],"summary":"Browse objects/prefixes under an S3 source","description":"List sub-prefixes (directories) and objects under a source. Append a *prefix* query param to descend into a sub-path.","operationId":"browse_source_api_v1_s3_sources__name__browse_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"prefix","in":"query","required":false,"schema":{"type":"string","default":"","title":"Prefix"}},{"name":"delimiter","in":"query","required":false,"schema":{"type":"string","default":"/","title":"Delimiter"}},{"name":"max_keys","in":"query","required":false,"schema":{"type":"integer","default":1000,"title":"Max Keys"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Browse Source Api V1 S3 Sources  Name  Browse Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/s3/sources/{name}/info":{"get":{"tags":["cbmodels","s3"],"summary":"Bucket summary stats for an S3 source","description":"Returns file count, total size, extension breakdown, and top-level prefixes for the source's configured prefix.","operationId":"source_info_api_v1_s3_sources__name__info_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Source Info Api V1 S3 Sources  Name  Info Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/s3/sources/{name}/status":{"get":{"tags":["cbmodels","s3"],"summary":"Current sync state for an S3 source","description":"Returns the last-sync timestamp, row count, and object count from s3_sync_state.json. Returns null sync_status if never synced.","operationId":"source_status_api_v1_s3_sources__name__status_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Source Status Api V1 S3 Sources  Name  Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/s3/sources/{name}/sync":{"post":{"tags":["cbmodels","s3"],"summary":"Trigger S3 source sync","description":"Downloads S3 objects and materialises them into DuckDB. Returns a newline-delimited JSON stream of progress events. Event types: start, progress, complete, error.","operationId":"sync_source_api_v1_s3_sources__name__sync_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"full","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Full"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/segment/analyze":{"post":{"tags":["cbmodels","segment"],"summary":"Analyze voter segment against baseline","description":"## Analyze a Voter Segment\n\nCompare a segment of voters (defined by State Voter IDs) against the full\nvoter baseline to identify statistically significant differences.\n\n### What This Endpoint Does\n\n1. **Resolves SVIDs** - Converts State Voter IDs to internal person records\n2. **Computes segment statistics** - Calculates mean, median, distributions for the segment\n3. **Compares to baseline** - Identifies deviations from the pre-computed baseline\n4. **Flags significance** - Marks columns where segment differs significantly\n\n### Use Cases\n\n- **Donor Analysis**: \"How do Trump donors in FL differ from the general population?\"\n- **Geographic Targeting**: \"What's unique about voters in Miami-Dade county?\"\n- **Behavioral Insights**: \"Do email clickers have different demographics?\"\n- **Campaign Optimization**: \"Which segments respond best to conservative messaging?\"\n\n### Response Interpretation\n\nThe AI should:\n1. **Start with the summary** - Report segment size and number of significant deviations\n2. **Highlight top deviations** - Focus on columns in `summary.top_deviations`\n3. **Explain categorical shifts** - Compare `segment_top_values` vs `baseline_top_values`\n4. **Note numeric differences** - Use `mean_deviation` to describe percentage differences\n5. **Flag data quality issues** - Report any patterns in the `patterns` array\n\n### Example Narrative\n\n```\n\"This segment of 823 voters represents 1% of the baseline population.\nThey show 8 significant deviations from the general population:\n\n- **County concentration**: 40% are in Miami-Dade (vs 15% baseline) - 2.7x overrepresentation\n- **Age**: Average age is 58.3 (vs 45.2 baseline) - 29% older than average\n- **Party affiliation**: 72% Republican (vs 45% baseline)\"\n```","operationId":"analyze_segment_api_v1_segment_analyze_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__SegmentAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful analysis","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__SegmentAnalysisResponse"},"example":{"request_id":"550e8400-e29b-41d4-a716-446655440000","analyzed_at":"2025-12-26T10:30:00Z","segment_svids_provided":1000,"segment_svids_matched":823,"summary":{"segment_size":823,"baseline_size":81000,"segment_percentage":1.02,"significant_deviations":8,"top_deviations":["county","age","party_affiliation"]},"columns":[],"query_time_ms":45.2,"analysis_time_ms":312.8}}}},"404":{"description":"No matching voters found for provided SVIDs","content":{"application/json":{"example":{"detail":"No matching persons found for provided SVIDs"}}}},"503":{"description":"Service unavailable - model or database not loaded","content":{"application/json":{"example":{"detail":"Model not loaded"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/usage":{"get":{"tags":["cbmodels","health"],"summary":"Get API usage statistics","description":"## API Usage Statistics\n\nReturns aggregated usage metrics from the metering database.\nUseful for monitoring, billing, and capacity planning.\n\n### Metrics Included\n\n- **Total requests** in the period\n- **By endpoint** - Request counts, average latency, error rates\n- **By tier** - Usage distribution across subscription tiers\n- **By tenant** - Top tenants by request count\n\n### Requirements\n\nRequires metering to be enabled (CBMODELS_METERING=true).","operationId":"get_usage_api_v1_usage_get","parameters":[{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Usage statistics summary","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/voters/api-count":{"post":{"tags":["cbmodels","voters"],"summary":"Query i360 API for voter counts (any US state)","description":"Query the national i360 database via CBWorkflow API.\nCovers all 50 states with affinity/propensity filtering.\n\nUse this for states without purchased local data.\nFor local Michigan data with rich filters, use POST /count instead.\n\n**Important:** Provide both min AND max for affinity/propensity — the API\nreturns zeros if only one side is sent.","operationId":"api_count_api_v1_voters_api_count_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ApiCountRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__ApiCountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/voters/batch":{"post":{"tags":["cbmodels","voters"],"summary":"Get multiple voters by SVID (Michigan only)","description":"Batch lookup of voters by their SVIDs.\n\nReturns full voter details for each SVID found.\nSVIDs not found are silently skipped.\n\n**Use cases:**\n- \"Get details for these 50 voters\"\n- \"Enrich this list with voter data\"","operationId":"get_voters_batch_api_v1_voters_batch_post","parameters":[{"name":"svids","in":"query","required":true,"schema":{"type":"array","items":{"type":"string"},"description":"List of SVIDs to look up","title":"Svids"},"description":"List of SVIDs to look up"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbmodels__VoterDetail"},"title":"Response Get Voters Batch Api V1 Voters Batch Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/voters/count":{"post":{"tags":["cbmodels","voters"],"summary":"Count voters matching filters","description":"Count voters with structured filters. Returns individuals, residences,\nphone counts (all, landline, mobile).\n\n**Jargon translations:**\n- \"2-4x4 GVH\" → `{\"column\": \"last_four_general_elections\", \"operator\": \">=\", \"value\": 2}`\n- Score \".6+\" → `{\"column\": \"<score>_score\", \"operator\": \">=\", \"value\": 60}` (scores are 0-100)","operationId":"count_voters_api_v1_voters_count_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CountQueryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CountQueryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/voters/count/report":{"post":{"tags":["cbmodels","voters"],"summary":"Count voters and build a shareable report","description":"Count voters matching filters and package results as a VoterCountReport.\nOptionally uploads JSON to cbfiles for sharing (requires CBFILES_API_KEY).\n\nReturns query plan (human-readable filter summary), results, and share URL.","operationId":"count_report_api_v1_voters_count_report_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__CountReportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__VoterCountReport"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/voters/dictionary":{"get":{"tags":["cbmodels","voters"],"summary":"Get data dictionary for LLM prompts","description":"Returns markdown data dictionary with jargon translations and column descriptions.","operationId":"get_dictionary_api_v1_voters_dictionary_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"michigan","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Get Dictionary Api V1 Voters Dictionary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/voters/filters":{"get":{"tags":["cbmodels","voters"],"summary":"Get available filter options (Michigan only)","description":"Get distinct values for filterable fields.\n\nReturns available options for:\n- Parties\n- Counties\n- Congressional districts\n- Genders\n\nUse this to populate filter dropdowns in the UI.","operationId":"get_filters_api_v1_voters_filters_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__FilterOptions"}}}}}}},"/cbmodels/api/v1/voters/schema":{"get":{"tags":["cbmodels","voters"],"summary":"Get auto-discovered schema","description":"Returns all columns classified by category with types and ranges.","operationId":"get_schema_api_v1_voters_schema_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"michigan","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Schema Api V1 Voters Schema Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/voters/search":{"post":{"tags":["cbmodels","voters"],"summary":"Search voters with filters (Michigan only)","description":"Search the voter database with various filters.\n\nSupports filtering by:\n- Name (partial match)\n- Location (city, county, ZIP, congressional district)\n- Demographics (party, gender)\n- Scores (turnout, trump support ranges)\n\n**Use cases:**\n- \"Find Republican women in Oakland County\"\n- \"Search for voters with high turnout scores\"\n- \"Find voters named Smith in ZIP 48104\"","operationId":"search_voters_api_v1_voters_search_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__VoterSearchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__VoterSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/voters/stats":{"get":{"tags":["cbmodels","voters"],"summary":"Get voter database statistics (Michigan only)","description":"Get aggregate statistics about the voter database.\n\nReturns:\n- Total voter count\n- Party and gender distributions\n- Top counties by population\n- Average scores\n\n**Use cases:**\n- \"How many voters are in the database?\"\n- \"What's the party breakdown?\"\n- \"Which counties have the most voters?\"","operationId":"get_stats_api_v1_voters_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__VoterStatsResponse"}}}}}}},"/cbmodels/api/v1/voters/tool-schema":{"get":{"tags":["cbmodels","voters"],"summary":"Get MCP tool definition","description":"Returns an MCP-compatible tool definition JSON for AI agent integration.","operationId":"get_tool_schema_api_v1_voters_tool_schema_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"michigan","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Tool Schema Api V1 Voters Tool Schema Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/v1/voters/{svid}":{"get":{"tags":["cbmodels","voters"],"summary":"Get voter by SVID (Michigan only)","description":"Get detailed voter information by State Voter ID (SVID).\n\nReturns all available fields including demographics, contact info,\npolitical scores, and voting history.\n\n**Use cases:**\n- \"Look up voter 7185869\"\n- \"Get details for this SVID\"\n- \"What's the voting history for this person?\"","operationId":"get_voter_api_v1_voters__svid__get","parameters":[{"name":"svid","in":"path","required":true,"schema":{"type":"string","title":"Svid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__VoterDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__HTTPValidationError"}}}}}}},"/cbmodels/api/version":{"get":{"tags":["cbmodels","health"],"summary":"Running build identity","description":"The git sha this PROCESS was started from (cbmodels#227).\n\nauto-deploy.sh compares this against ``git rev-parse HEAD`` after a\npull: equal means the running process already serves HEAD, so the\nrestart — a full cold catalog discovery with the port unbound — is\nskipped. On monroe the dev workspace and deploy target share a\ncheckout, so the pull itself is always a no-op and only the running\nprocess knows which commit it booted from.","operationId":"api_version_api_version_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Api Version Api Version Get"}}}}}}},"/cbmodels/health":{"get":{"tags":["cbmodels","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__sysforge__core__health__HealthResponse"}}}}}}},"/cbmodels/health/detailed":{"get":{"tags":["cbmodels","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__sysforge__core__health__HealthResponse"}}}}}}},"/cbmodels/prime":{"get":{"tags":["cbmodels","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__PrimeResponse"}}}}}}},"/cbmodels/status":{"get":{"tags":["cbmodels","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbmodels__StatusResponse"}}}}}}},"/cbnetwork/api/v1/fleet/status":{"get":{"tags":["cbnetwork","Health"],"summary":"Fleet Status","description":"Combined fleet overview — the main dashboard endpoint.","operationId":"fleet_status_api_v1_fleet_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Fleet Status Api V1 Fleet Status Get"}}}}}}},"/cbnetwork/api/v1/inventory":{"get":{"tags":["cbnetwork","Inventory"],"summary":"Get Inventory","description":"Full latest inventory snapshot.\n\nReturns an empty snapshot (populated=False) if no scan has run yet —\na missing snapshot is not a service failure, just a provisioning gap.","operationId":"get_inventory_api_v1_inventory_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Inventory Api V1 Inventory Get"}}}}}}},"/cbnetwork/api/v1/inventory/snapshots":{"get":{"tags":["cbnetwork","Inventory"],"summary":"List Snapshots","description":"List available snapshot timestamps.","operationId":"list_snapshots_api_v1_inventory_snapshots_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Response List Snapshots Api V1 Inventory Snapshots Get"}}}}}}},"/cbnetwork/api/v1/inventory/{ip}":{"get":{"tags":["cbnetwork","Inventory"],"summary":"Get Device","description":"Get a single device snapshot by IP.","operationId":"get_device_api_v1_inventory__ip__get","parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string","description":"Device IP address (e.g. 17.0.0.10)","title":"Ip"},"description":"Device IP address (e.g. 17.0.0.10)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Device Api V1 Inventory  Ip  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnetwork__HTTPValidationError"}}}}}}},"/cbnetwork/api/v1/printers":{"get":{"tags":["cbnetwork","Printers"],"summary":"Get Printers","description":"Latest cached printer scan.\n\nReturns an empty, ``populated=False`` payload if no scan has run yet — a\nmissing cache is a provisioning gap, not a service failure.","operationId":"get_printers_api_v1_printers_get","parameters":[{"name":"low","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Only printers with a supply below this percent.","title":"Low"},"description":"Only printers with a supply below this percent."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Printers Api V1 Printers Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnetwork__HTTPValidationError"}}}}}}},"/cbnetwork/api/v1/printers/scan":{"post":{"tags":["cbnetwork","Printers"],"summary":"Run Scan","description":"Run a live scan, cache it, and return the result (slow — seconds).","operationId":"run_scan_api_v1_printers_scan_post","parameters":[{"name":"cidr","in":"query","required":false,"schema":{"type":"string","description":"Subnet to port-sweep ('auto' = host's own /24).","default":"auto","title":"Cidr"},"description":"Subnet to port-sweep ('auto' = host's own /24)."},{"name":"mdns","in":"query","required":false,"schema":{"type":"boolean","description":"Run the mDNS/DNS-SD layer.","default":true,"title":"Mdns"},"description":"Run the mDNS/DNS-SD layer."},{"name":"sweep","in":"query","required":false,"schema":{"type":"boolean","description":"Run the TCP port-sweep layer.","default":true,"title":"Sweep"},"description":"Run the TCP port-sweep layer."},{"name":"snmp","in":"query","required":false,"schema":{"type":"boolean","description":"Run SNMP supply/page-count enrichment.","default":true,"title":"Snmp"},"description":"Run SNMP supply/page-count enrichment."},{"name":"mdns_timeout","in":"query","required":false,"schema":{"type":"number","description":"mDNS listen seconds.","default":4.0,"title":"Mdns Timeout"},"description":"mDNS listen seconds."},{"name":"sweep_timeout","in":"query","required":false,"schema":{"type":"number","description":"Per-connection TCP timeout.","default":1.0,"title":"Sweep Timeout"},"description":"Per-connection TCP timeout."},{"name":"snmp_timeout","in":"query","required":false,"schema":{"type":"number","description":"Per-host SNMP timeout.","default":2.0,"title":"Snmp Timeout"},"description":"Per-host SNMP timeout."},{"name":"snmp_community","in":"query","required":false,"schema":{"type":"string","description":"SNMP v2c community.","default":"public","title":"Snmp Community"},"description":"SNMP v2c community."},{"name":"concurrency","in":"query","required":false,"schema":{"type":"integer","description":"Max concurrent probes.","default":256,"title":"Concurrency"},"description":"Max concurrent probes."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Run Scan Api V1 Printers Scan Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnetwork__HTTPValidationError"}}}}}}},"/cbnetwork/api/v1/printers/{ip}":{"get":{"tags":["cbnetwork","Printers"],"summary":"Get Printer","description":"A single cached printer by IP.","operationId":"get_printer_api_v1_printers__ip__get","parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string","description":"Printer IP (e.g. 192.168.1.50)","title":"Ip"},"description":"Printer IP (e.g. 192.168.1.50)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Printer Api V1 Printers  Ip  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnetwork__HTTPValidationError"}}}}}}},"/cbnetwork/api/v1/topology":{"get":{"tags":["cbnetwork","Topology"],"summary":"Get Topology","description":"Registered device topology from the device registry.","operationId":"get_topology_api_v1_topology_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Topology Api V1 Topology Get"}}}}}}},"/cbnetwork/health":{"get":{"tags":["cbnetwork","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnetwork__HealthResponse"}}}}}}},"/cbnetwork/health/detailed":{"get":{"tags":["cbnetwork","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnetwork__HealthResponse"}}}}}}},"/cbnetwork/prime":{"get":{"tags":["cbnetwork","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnetwork__PrimeResponse"}}}}}}},"/cbnetwork/status":{"get":{"tags":["cbnetwork","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnetwork__StatusResponse"}}}}}}},"/cbnominate/api/v1/calendar":{"get":{"tags":["cbnominate","CBNOMINATE"],"summary":"Dated election events across built states","description":"Every dated entry, sorted, with its state attached.\n\nEntries the kit wrote in a form that will not parse are **excluded by\ndefault** and counted in ``undated`` — an undated artifact is not served, but\nsilently dropping one would hide an authoring bug, so the count is always\nreported even when the entries are not.","operationId":"get_calendar_api_v1_calendar_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Limit to one state (code or slug).","title":"State"},"description":"Limit to one state (code or slug)."},{"name":"undated","in":"query","required":false,"schema":{"type":"boolean","description":"Include entries whose date could not be parsed.","default":false,"title":"Undated"},"description":"Include entries whose date could not be parsed."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Calendar Api V1 Calendar Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__HTTPValidationError"}}}}}}},"/cbnominate/api/v1/kit":{"get":{"tags":["cbnominate","CBNOMINATE"],"summary":"Every kit document, verbatim, keyed by filename","description":"The whole data directory as authored — the harvest-consumer endpoint.\n\nThis is the API twin of the single-round-trip ssh read in\n``cbelections/scripts/import_skeletons.py``: one object keyed by filename,\ntemplates (``_blank.json``) included, so the importer's own skip-list keeps\ndoing its job and the swap from ssh to HTTP changes nothing downstream.\nConsumers wanting typed values should prefer ``/states`` — this endpoint\nexists for the one consumer that runs its own normalizer.","operationId":"get_kit_api_v1_kit_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Kit Api V1 Kit Get"}}}}}}},"/cbnominate/api/v1/states":{"get":{"tags":["cbnominate","CBNOMINATE"],"summary":"The coverage table over every built state","description":"One row per state the research kit holds.\n\n``built`` is the count of documents that exist, not of the 50 — the states\nthat have not been researched are absent rather than present-and-zero, so a\nconsumer cannot mistake an unwritten state for an empty one.","operationId":"list_states_api_v1_states_get","parameters":[{"name":"complete","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter to states whose ladder is fully counted.","title":"Complete"},"description":"Filter to states whose ladder is fully counted."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List States Api V1 States Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__HTTPValidationError"}}}}}}},"/cbnominate/api/v1/states/{state}":{"get":{"tags":["cbnominate","CBNOMINATE"],"summary":"One state's full typed skeleton","description":"The whole skeleton: ladder, profile, deltas, calendar, sources, sample.","operationId":"get_state_api_v1_states__state__get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","description":"State postal code (`FL`) or kit slug (`florida`).","title":"State"},"description":"State postal code (`FL`) or kit slug (`florida`)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get State Api V1 States  State  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__HTTPValidationError"}}}}}}},"/cbnominate/api/v1/states/{state}/counties":{"get":{"tags":["cbnominate","CBNOMINATE"],"summary":"One state's county rows","description":"The county rows the kit holds — a **sample** unless ``complete`` is true.\n\nMost states carry ten-odd illustrative rows against a sixty-plus-county\nregister. The total is reported beside the row count so the difference is\nimpossible to miss.","operationId":"get_counties_api_v1_states__state__counties_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","description":"State postal code (`FL`) or kit slug (`florida`).","title":"State"},"description":"State postal code (`FL`) or kit slug (`florida`)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Counties Api V1 States  State  Counties Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__HTTPValidationError"}}}}}}},"/cbnominate/api/v1/states/{state}/deltas":{"get":{"tags":["cbnominate","CBNOMINATE"],"summary":"One state's structural exceptions","description":"What makes this state different from the last one — the crawler-breakers.","operationId":"get_deltas_api_v1_states__state__deltas_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","description":"State postal code (`FL`) or kit slug (`florida`).","title":"State"},"description":"State postal code (`FL`) or kit slug (`florida`)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Deltas Api V1 States  State  Deltas Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__HTTPValidationError"}}}}}}},"/cbnominate/api/v1/states/{state}/ladder":{"get":{"tags":["cbnominate","CBNOMINATE"],"summary":"One state's seat ladder","description":"The tiers with typed counts, plus the expected-offices roll-up.\n\n``tiers_without_count`` travels with the sum on purpose: a total that quietly\nomits uncounted tiers reads as authoritative when it is not.","operationId":"get_ladder_api_v1_states__state__ladder_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","description":"State postal code (`FL`) or kit slug (`florida`).","title":"State"},"description":"State postal code (`FL`) or kit slug (`florida`)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Ladder Api V1 States  State  Ladder Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__HTTPValidationError"}}}}}}},"/cbnominate/api/v1/states/{state}/raw":{"get":{"tags":["cbnominate","CBNOMINATE"],"summary":"One kit document exactly as authored","description":"The unprocessed kit JSON — display strings and all.\n\nFor consumers that run their own normalization (``skeleton_kit.convert``)\nor need a field the typed view does not carry. Everything else should\nprefer ``/states/{state}``.","operationId":"get_state_raw_api_v1_states__state__raw_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","description":"State postal code (`FL`) or kit slug (`florida`).","title":"State"},"description":"State postal code (`FL`) or kit slug (`florida`)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get State Raw Api V1 States  State  Raw Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__HTTPValidationError"}}}}}}},"/cbnominate/api/v1/states/{state}/sources":{"get":{"tags":["cbnominate","CBNOMINATE"],"summary":"One state's receipts","description":"Every source chip behind the state's numbers.\n\n``reviewed`` is a **document-level** date; the kit carries no per-source read\ndate, so each source reports ``dated: false`` rather than borrowing that date\nand implying a precision the record does not have.","operationId":"get_sources_api_v1_states__state__sources_get","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","description":"State postal code (`FL`) or kit slug (`florida`).","title":"State"},"description":"State postal code (`FL`) or kit slug (`florida`)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Sources Api V1 States  State  Sources Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__HTTPValidationError"}}}}}}},"/cbnominate/api/v1/vocabulary":{"get":{"tags":["cbnominate","CBNOMINATE"],"summary":"The closed vocabularies consumers must use","description":"Domains a consumer would otherwise have to infer from whatever it saw.\n\n``delta_severities`` is authoritative (``nominate-ai-kit/data/README.md``);\n``count`` documents the ``{value, approximate, minimum, raw}`` shape every\nnumber on this API carries, including what ``value: null`` means.","operationId":"get_vocabulary_api_v1_vocabulary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Vocabulary Api V1 Vocabulary Get"}}}}}}},"/cbnominate/health":{"get":{"tags":["cbnominate","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__HealthResponse"}}}}}}},"/cbnominate/health/detailed":{"get":{"tags":["cbnominate","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__HealthResponse"}}}}}}},"/cbnominate/prime":{"get":{"tags":["cbnominate","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__PrimeResponse"}}}}}}},"/cbnominate/status":{"get":{"tags":["cbnominate","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnominate__StatusResponse"}}}}}}},"/cbnotify/api/v1/hello":{"get":{"tags":["cbnotify","CBNOTIFY"],"summary":"Hello world","description":"Trivial smoke endpoint that returns a fixed greeting. Useful for a quick liveness check or to confirm the `/api/v1` router is mounted. For real health/readiness use the `/health` family under the **Health** tag.","operationId":"hello_api_v1_hello_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbnotify/health":{"get":{"tags":["cbnotify","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__HealthResponse"}}}}}}},"/cbnotify/health/detailed":{"get":{"tags":["cbnotify","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__HealthResponse"}}}}}}},"/cbnotify/notify":{"post":{"tags":["cbnotify","notifications"],"summary":"Publish a notification","description":"Store a notification row and report whether anything will actually deliver it.\n\n**201** — a channel will carry this row.\n**202** — stored and queryable, but no channel applies; see ``delivery.outlook`` for whether that is worth waiting out (``queued``) or needs a config change (anything else).\n\nThe distinction matters: cbnotify returned a flat 201 for every notification for months while delivering none of them, so producers had no way to tell acceptance from delivery. If a caller would rather fail hard than branch on the status code, set ``CBNOTIFY_REFUSE_UNDELIVERABLE=1`` and an undeliverable publish becomes a **503** — off by default, because the stored row is worth keeping even when nothing sends it.\n\nProducer must have ``cbnotify.notify.write`` (env-gated). ``producer_email`` is captured from the ``X-User-Email`` header (set upstream by ``sysforge.core.client.caller_identity``), NOT from the request body.","operationId":"post_notify_notify_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__NotificationCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__NotificationOut"}}}},"202":{"description":"Stored, but no delivery channel applies to it. The row is queryable via GET /notify; nothing will send it."},"503":{"description":"Undeliverable and CBNOTIFY_REFUSE_UNDELIVERABLE is set — the row was NOT stored."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__HTTPValidationError"}}}}}},"get":{"tags":["cbnotify","notifications"],"summary":"List notifications with their real delivery outcome","description":"Consumer read over the notification store (cbnotify#2). Returns rows newest-first with a computed ``delivery_state`` — **not** a raw ``dispatched_at``, because the stale guard stamps that column on rows it deliberately never sent. Also reports whether the delivery channels are armed at all, which is usually the reason a backlog exists. Gated on ``cbnotify.notify.read`` (same env-gated rollout as the producer route). Filed from cbloom#300, where multi-day failures alerted nobody and there was no surface on which to notice.","operationId":"list_notifications_notify_get","parameters":[{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact channel match.","title":"Channel"},"description":"Exact channel match."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbnotify__DeliveryState"},{"type":"null"}],"description":"Filter to one delivery state.","title":"State"},"description":"Filter to one delivery state."},{"name":"priority","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact priority match.","title":"Priority"},"description":"Exact priority match."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Page size.","default":50,"title":"Limit"},"description":"Page size."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Rows to skip.","default":0,"title":"Offset"},"description":"Rows to skip."},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__NotificationPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__HTTPValidationError"}}}}}}},"/cbnotify/notify/delivery":{"get":{"tags":["cbnotify","notifications"],"summary":"Can cbnotify actually deliver right now?","description":"The honest counterpart to a 2xx on ``POST /notify``. Reports per-channel arming, the undelivered backlog, the last real hand-off, the last send failure, and when the dispatch tick last ran — so a caller can tell **accepted** from **will actually arrive** without reading cbnotify's environment.\n\nThis is the surface whose absence let a 102-failure outage go unnoticed for 48 hours: cbcron's alerting worked and fired correctly, and there was no way to see that delivery was the part that was shut. Unauthenticated on purpose — it exposes no notification content and no page number, only whether the spine works, and a health signal you need credentials to read is a health signal nobody checks.\n\nAlso surfaced as the ``delivery`` component on ``/health/detailed``.","operationId":"delivery_status_notify_delivery_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__DeliveryHealth"}}}}}}},"/cbnotify/notify/dispatch":{"post":{"tags":["cbnotify","notifications"],"summary":"Deliver pending notifications (cbcron tick)","description":"Hand undelivered notifications to cbemail as email and stamp ``dispatched_at``. Idempotent + retry-safe (a failed send stays pending for the next tick). No-op unless delivery is armed (``CBNOTIFY_DELIVERY_ENABLED=1`` + cbemail creds). Registered with cbcron — not a systemd timer.","operationId":"dispatch_notify_dispatch_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Dispatch Notify Dispatch Post"}}}}}}},"/cbnotify/notify/pending":{"get":{"tags":["cbnotify","notifications"],"summary":"Count undelivered notifications","description":"Observability for the delivery backlog (dispatched_at IS NULL).","operationId":"pending_notify_pending_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Pending Notify Pending Get"}}}}}}},"/cbnotify/notify/{notification_id}":{"delete":{"tags":["cbnotify","notifications"],"summary":"Delete a notification row","description":"Remove a notification by id — housekeeping for test/mistaken rows before delivery is armed. Gated on ``cbnotify.notify.write`` (same env-gate as the producer route). 404 if the id is unknown.","operationId":"delete_notification_notify__notification_id__delete","parameters":[{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","title":"Notification Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Delete Notification Notify  Notification Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__HTTPValidationError"}}}}}}},"/cbnotify/prime":{"get":{"tags":["cbnotify","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__PrimeResponse"}}}}}}},"/cbnotify/status":{"get":{"tags":["cbnotify","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbnotify__StatusResponse"}}}}}}},"/cbonboard/admin/demo-sessions":{"post":{"tags":["cbonboard","Admin"],"summary":"Create a demo_session (admin)","description":"Open a new demo session that mints rotating QR batches. Set the rotation interval, post-rotation grace window, and per-batch redemption cap. Requires admin authorization.","operationId":"create_demo_session_admin_demo_sessions_post","parameters":[{"name":"x-cb-auth-permission","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Auth-Permission"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__CreateDemoSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__DemoSession"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}},"get":{"tags":["cbonboard","Admin"],"summary":"List all demo sessions (active + inactive)","description":"Return every demo session, both active and deactivated, for the operator console. Requires admin authorization.","operationId":"list_demo_sessions_admin_demo_sessions_get","parameters":[{"name":"x-cb-auth-permission","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Auth-Permission"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbonboard__DemoSession"},"title":"Response List Demo Sessions Admin Demo Sessions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/admin/demo-sessions/{ds_id}":{"get":{"tags":["cbonboard","Admin"],"summary":"Get a demo session by id","description":"Fetch a single demo session by id. Returns 404 `demo_session_not_found` if no session matches. Requires admin authorization.","operationId":"get_demo_session_admin_demo_sessions__ds_id__get","parameters":[{"name":"ds_id","in":"path","required":true,"schema":{"type":"string","title":"Ds Id"}},{"name":"x-cb-auth-permission","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Auth-Permission"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__DemoSession"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}},"patch":{"tags":["cbonboard","Admin"],"summary":"Update a demo session (label, max_redemptions, expires_at)","description":"Partially update a demo session. Supply at least one of `label`, `max_redemptions`, or `expires_at`; omitted fields are left unchanged. Returns 400 `nothing_to_update` for an empty body and 404 `demo_session_not_found` if the session is missing. Requires admin authorization.","operationId":"patch_demo_session_admin_demo_sessions__ds_id__patch","parameters":[{"name":"ds_id","in":"path","required":true,"schema":{"type":"string","title":"Ds Id"}},{"name":"x-cb-auth-permission","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Auth-Permission"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__UpdateDemoSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__DemoSession"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}},"delete":{"tags":["cbonboard","Admin"],"summary":"Deactivate a demo session","description":"Soft-delete (deactivate) a demo session so it stops minting QR batches. Returns 204 on success and 404 `demo_session_not_found` if the session is missing. Requires admin authorization.","operationId":"delete_demo_session_admin_demo_sessions__ds_id__delete","parameters":[{"name":"ds_id","in":"path","required":true,"schema":{"type":"string","title":"Ds Id"}},{"name":"x-cb-auth-permission","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Auth-Permission"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/admin/invites":{"post":{"tags":["cbonboard","Admin"],"summary":"Mint durable per-person onboarding links","description":"Mint one **durable, single-use onboarding link per person** — the mail-merge\npath alongside the kiosk QR.\n\nEach link confers the demo session's `grant_groups`, exactly as a QR scan\ndoes, and drops the recipient into the same signup flow (name → email → phone\n→ password → SMS code → verified). The recipient's `email` is kept with the\ncode and prefilled on the form (editable), and the account is created under\nit with the password they choose — a real, vanilla cbauth user, not a\nsynthetic one (cbonboard#6). Nothing else is trusted from the list: they type\ntheir own name and phone, because the phone must be the handset holding the\nSMS code.\n\n**Single use, durably.** The cap is enforced from the redemption ledger rather\nthan the Redis gate (whose keys expire in 300s), so a forwarded link cannot\nboard a second person days later — the second opener gets\n`invite_already_used`.\n\n`ttl_hours` defaults to 72 so links mailed the night before still work on\nelection night.","operationId":"mint_invites_admin_invites_post","parameters":[{"name":"x-cb-auth-permission","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Auth-Permission"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__MintInvitesRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__InviteList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/admin/redemptions":{"get":{"tags":["cbonboard","Admin"],"summary":"List recent redemptions across one or all demo sessions","description":"Return the redemption audit ledger, newest first. Filter to a single session with `demo_session_id`; `limit` is clamped to 1-500 (default 50). Each row shows how far up the trust ladder that redemption climbed. Requires admin authorization.","operationId":"list_redemptions_admin_redemptions_get","parameters":[{"name":"demo_session_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Demo Session Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"x-cb-auth-permission","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Auth-Permission"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__RedemptionList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/health":{"get":{"tags":["cbonboard","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HealthResponse"}}}}}}},"/cbonboard/health/detailed":{"get":{"tags":["cbonboard","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HealthResponse"}}}}}}},"/cbonboard/onboard/invite/{code}":{"get":{"tags":["cbonboard","Onboarding"],"summary":"Resolve a short invite code to its token","description":"Exchange a short invite **code** for its full invite token.\n\nPublic on purpose — it is the first hop of a mailed link, before the recipient\nhas any credential. The code is a bearer of the invite exactly as the token is,\nso the same protections apply downstream: the token still carries the expiry\nand the single-use cap, and redemption is still enforced from the ledger. An\nunknown or retired code is a flat 404 with no detail, so the endpoint cannot be\nused to probe which codes exist.\n\nAlso returns the invite's **`email`** — the address it was minted for, which the\nform prefills and the account is created under (cbonboard#6) — and the\nsession's **`landing_url`** (+ `landing_label`), so the PWA\nholds the destination before it attempts redeem — a spent link never reaches\nredeem, and that page must still be able to say \"continue to the app\"\n(cbonboard#5). Short links are what get mailed (the kit sends `short_url`), so\nthis hop is the one every invitee makes; full `?t=` links skip it and learn the\ndestination at redeem as before.","operationId":"resolve_invite_onboard_invite__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__InviteResolve"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/onboard/redeem":{"post":{"tags":["cbonboard","Onboarding"],"summary":"Redeem a batch_token for a session_token","description":"Exchange a scanned QR **`batch_token`** for a short-lived Tier-0\n**`session_token`** — the first rung of the trust ladder.\n\n### Flow\n1. Validate the `batch_token` signature and slot (rejects expired/rotated tokens).\n2. Atomically claim a redemption slot in Redis — enforces the per-batch\n   `max_redemptions` cap and burns the token's `jti` against replay.\n3. Mint a 15-minute `session_token` bound to the batch and fingerprint.\n4. Record the redemption in the audit ledger and emit a `user_joined_batch`\n   event to the demo session's big screen.\n\n### Errors\n| Status | `code` | When |\n|--------|--------|------|\n| 401 | `batch_token_invalid` | token expired, rotated out, or malformed |\n| 429 | `batch_cap_reached` | the batch's redemption cap is full |\n| 502 | `audit_write_failed` | the redemption ledger write failed |","operationId":"redeem_onboard_redeem_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__RedeemRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__RedeemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/onboard/resend":{"post":{"tags":["cbonboard","Onboarding"],"summary":"Re-send the SMS verification code (rate-limited per phone)","description":"Re-issue the SMS verification code for a provisional user, **rate-limited per\nphone number**.\n\nRequires the provisional user's token as a **bearer** credential. Issues a\nfresh code (invalidating the prior one), sends it, and arms the resend cooldown.\n\n### Errors\n| Status | `code` | When |\n|--------|--------|------|\n| 401 | `user_token_missing` / `user_token_invalid` | bad bearer token |\n| 400 | `phone_unknown` | no phone on file for the user |\n| 429 | `resend_rate_limited` | a resend was requested too soon |","operationId":"resend_onboard_resend_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__ResendRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/onboard/signup":{"post":{"tags":["cbonboard","Onboarding"],"summary":"Create a provisional user (Tier 1) from a Tier 0 session","description":"Promote a Tier-0 `scanned` session to a Tier-1 **`provisional`** user.\n\nRequires the `session_token` from `/onboard/redeem` as a **bearer** credential.\n\n### Flow\n1. Verify the bearer `session_token`.\n2. Create the cbauth user (group `new_request`, elevated to the session's\n   `grant_groups` at verify — nothing more):\n   - **with an email** (the invite's, or one the person typed): a REAL,\n     vanilla user under that address with the `password` they chose\n     (cbonboard#6). `email_confirmed` is true when the address is the one the\n     link was mailed to — they arrived through it.\n   - **with none** (a kiosk scan that gave no address): a provisional,\n     password-less account under a synthetic `onboard+<hex>@noreply` address,\n     as before. The gate is the presence of an email, not the caller.\n3. Mint a user token pair and issue an SMS verification code to the phone.\n4. Emit `user_provisional` to the big screen and mark the redemption ledger.\n\n### Errors\n| Status | `code` | When |\n|--------|--------|------|\n| 401 | `session_token_invalid` | missing/expired/invalid bearer token |\n| 400 | `password_required` | an email is known but no `password` was sent |\n| 409 | `email_taken` | cbauth already has an account under this address — sign in instead |","operationId":"signup_onboard_signup_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__SignupRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/onboard/verify":{"post":{"tags":["cbonboard","Onboarding"],"summary":"Verify SMS code → elevate user to Tier 2","description":"Confirm the SMS verification code and elevate the user from Tier-1\n`provisional` to Tier-2 **`verified`**.\n\nRequires the provisional user's token as a **bearer** credential.\n\n### Flow\n1. Resolve the user from the bearer token.\n2. Validate the submitted code against the Redis-backed code store.\n3. Mint a short-lived elevation proof and call cbauth to swap groups —\n   `new_request` is removed and the user is elevated into the **demo\n   session's `grant_groups`** (default `verified` when the session did not\n   set them, or when no session resolves) — and mark the phone confirmed.\n4. Emit `user_verified` to the session's big screen and stamp the\n   redemption ledger.\n\n### Errors\n| Status | `code` | When |\n|--------|--------|------|\n| 401 | `user_token_missing` / `user_token_invalid` | bad bearer token |\n| 400 | `code_invalid` | wrong code |\n| 410 | `code_expired` | code expired or already used |\n| 429 | `code_too_many_attempts` | too many failed attempts |","operationId":"verify_onboard_verify_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__VerifyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/prime":{"get":{"tags":["cbonboard","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__PrimeResponse"}}}}}}},"/cbonboard/qr/live/{demo_session_id}":{"get":{"tags":["cbonboard","QR"],"summary":"Current batch_token + counters for cbloom to render","description":"Return the live QR rendering payload for a demo session's kiosk / big screen.\n\nCarries the **current rotating `batch_token`**, its expiry and rotation\ntimestamps, the scan URL, and the running redemption counter. cbloom polls this\nendpoint to draw and rotate the on-screen QR code.\n\nOpen under a subdomain-wildcard CORS policy (`*.campaignbrain.dev`) so any\nkiosk frontend can read it.","operationId":"qr_live_qr_live__demo_session_id__get","parameters":[{"name":"demo_session_id","in":"path","required":true,"schema":{"type":"string","title":"Demo Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__LiveQr"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/session/status":{"get":{"tags":["cbonboard","Onboarding"],"summary":"Current verification state for the bearer-authenticated user","description":"Return the current trust tier and verification state for the bearer user.\n\nA **polling fallback** for the signup PWA's verify page when the WebSocket is\nunavailable: the page polls this endpoint until `verified` flips true.\n\n### Errors\n| Status | `code` | When |\n|--------|--------|------|\n| 401 | `user_token_missing` / `user_token_invalid` | bad bearer token |","operationId":"session_status_session_status_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__SessionStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__HTTPValidationError"}}}}}}},"/cbonboard/status":{"get":{"tags":["cbonboard","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbonboard__StatusResponse"}}}}}}},"/cboverseer/api/v1/agents/dispatch":{"post":{"tags":["cboverseer","agents"],"summary":"Dispatch a monitoring agent","description":"Dispatch a monitoring agent to start collecting metrics.\n\n**Typical Usage:**\nUse this to start a new monitoring agent or restart one that failed.\nThe agent will connect to cbmesh and begin sending telemetry.\n\n**Options:**\n- `immediate`: If true, run a check immediately instead of waiting for interval\n- `check_interval`: Override the default check interval for this agent\n\n**Note:** Only one agent of each type can run at a time per host.","operationId":"dispatch_agent_api_v1_agents_dispatch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__AgentDispatchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__AgentInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/agents/status":{"get":{"tags":["cboverseer","agents"],"summary":"List all agents","description":"Get status of all monitoring agents.\n\n**Response Interpretation:**\n- `agents`: List of agent information objects\n- Each agent shows its type, host, status, and last check time\n- `metrics_collected`: Total metrics this agent has sent\n- `alerts_generated`: Total alerts this agent has triggered\n\n**Agent Status Values:**\n- `idle`: Agent is ready and waiting for next check interval\n- `running`: Agent is currently collecting metrics\n- `error`: Last check failed\n- `disconnected`: Agent hasn't reported recently","operationId":"list_agents_api_v1_agents_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Agents Api V1 Agents Status Get"}}}}}}},"/cboverseer/api/v1/agents/status/{agent_type}":{"get":{"tags":["cboverseer","agents"],"summary":"Get agent by type","description":"Get status of a specific agent type.\n\n**Agent Types:**\n- `system`: Memory, disk, CPU, load monitoring\n- `services`: Systemd units, health endpoints\n- `database`: PostgreSQL, DuckDB, SQLite connections\n- `network`: TOR, VPN, SWRM peers, banned IPs\n- `nginx`: Access logs, error logs, upstream status\n- `files`: cbfiles storage usage\n- `user`: User activity tracking","operationId":"get_agent_status_api_v1_agents_status__agent_type__get","parameters":[{"name":"agent_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/cboverseer__AgentType"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__AgentInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/agents/types":{"get":{"tags":["cboverseer","agents"],"summary":"List available agent types","description":"List all available monitoring agent types and their descriptions.","operationId":"list_agent_types_api_v1_agents_types_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Agent Types Api V1 Agents Types Get"}}}}}}},"/cboverseer/api/v1/agents/{agent_type}":{"delete":{"tags":["cboverseer","agents"],"summary":"Stop an agent","description":"Stop a running monitoring agent.\n\nThe agent will be gracefully stopped and removed from the active list.\nIt can be restarted later with POST /dispatch.","operationId":"stop_agent_api_v1_agents__agent_type__delete","parameters":[{"name":"agent_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/cboverseer__AgentType"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Stop Agent Api V1 Agents  Agent Type  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/alerts/config":{"get":{"tags":["cboverseer","alerts"],"summary":"Get alerting configuration","description":"Get current SMS alerting configuration.\n\nShows enabled status, configured phone numbers (masked),\nand rate limiting settings.","operationId":"get_alert_config_api_v1_alerts_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Alert Config Api V1 Alerts Config Get"}}}}}}},"/cboverseer/api/v1/alerts/config/phones":{"put":{"tags":["cboverseer","alerts"],"summary":"Update alert phone numbers","description":"Update the list of phone numbers to receive alerts.\n\n**Format:** Phone numbers should be in E.164 format (e.g., +15551234567)\n\n**Note:** This updates runtime config only. For persistent changes,\nupdate the environment variables or config file.","operationId":"update_phone_numbers_api_v1_alerts_config_phones_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__PhoneNumberUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Update Phone Numbers Api V1 Alerts Config Phones Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/alerts/health":{"get":{"tags":["cboverseer","alerts"],"summary":"Check cbsms availability","description":"Check if the cbsms gateway is available and healthy.\n\n**Response:**\n- `available`: Whether cbsms is responding\n- `sms_enabled`: Whether SMS alerts are enabled in config\n- `phone_numbers_configured`: Count of configured phone numbers","operationId":"check_sms_health_api_v1_alerts_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Check Sms Health Api V1 Alerts Health Get"}}}}}}},"/cboverseer/api/v1/alerts/incoming":{"get":{"tags":["cboverseer","alerts"],"summary":"Check for incoming SMS messages","description":"Poll cbsms for any incoming SMS responses.\n\nReturns list of recent incoming messages that may be\nresponses to alerts.","operationId":"check_incoming_messages_api_v1_alerts_incoming_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Check Incoming Messages Api V1 Alerts Incoming Get"}}}}}}},"/cboverseer/api/v1/alerts/response":{"post":{"tags":["cboverseer","alerts"],"summary":"Handle incoming SMS response","description":"Process an incoming SMS response to an alert.\n\n**Supported responses:**\n- `FIX` - Approve auto-remediation for the incident\n- `WAIT` - Hold off on auto-fix, will alert again if issue persists\n- Any other text - Forwarded to cbai for context-aware response\n\n**Note:** This endpoint is typically called by cbsms webhook,\nnot directly by users.","operationId":"handle_sms_response_api_v1_alerts_response_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__SMSResponse"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Handle Sms Response Api V1 Alerts Response Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/alerts/{incident_id}/resolution":{"post":{"tags":["cboverseer","alerts"],"summary":"Send resolution notification","description":"Send SMS notification that an incident has been resolved.\n\nThis notifies the person who received the original alert that\nthe issue has been fixed.","operationId":"send_resolution_alert_api_v1_alerts__incident_id__resolution_post","parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Send Resolution Alert Api V1 Alerts  Incident Id  Resolution Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/alerts/{incident_id}/send":{"post":{"tags":["cboverseer","alerts"],"summary":"Send SMS alert for incident","description":"Send an SMS alert for a specific incident.\n\n**What happens:**\n1. Retrieves the incident details\n2. Optionally runs AI diagnosis for context\n3. Queries cbscout for similar past incidents\n4. Formats and sends SMS to all configured phone numbers\n\n**Human-in-the-loop:**\nSMS includes options to reply:\n- `FIX` - Approve auto-remediation\n- `WAIT` - Hold off on auto-fix\n\n**Rate limiting:**\nDuplicate alerts for the same service/severity are rate-limited\nto prevent alert fatigue.","operationId":"send_incident_alert_api_v1_alerts__incident_id__send_post","parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Send Incident Alert Api V1 Alerts  Incident Id  Send Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/dependencies":{"get":{"tags":["cboverseer","dependencies"],"summary":"Get dependency map","description":"Get the service dependency graph.\n\n**Response Interpretation:**\n- `nodes`: List of services in the system\n- `edges`: List of dependency relationships\n- Each edge shows source -> target with dependency type\n\n**Dependency Types:**\n- `api_call`: Service makes API calls to target\n- `database`: Service connects to target database\n- `message_queue`: Service sends messages via queue\n\nThis data can be used to visualize service topology\nand understand the blast radius of outages.","operationId":"get_dependencies_api_v1_dependencies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Dependencies Api V1 Dependencies Get"}}}}}},"post":{"tags":["cboverseer","dependencies"],"summary":"Record a dependency","description":"Record a newly discovered dependency between services.\n\nDependencies can be discovered through:\n- Static analysis of code\n- Runtime API call observation\n- Configuration parsing","operationId":"record_dependency_api_v1_dependencies_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__ServiceDependency"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Record Dependency Api V1 Dependencies Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/dependencies/health":{"get":{"tags":["cboverseer","dependencies"],"summary":"Get dependency health","description":"Check the health status of service dependencies.\n\nReturns which dependencies are healthy vs unhealthy,\nhelping identify potential issues in the service mesh.","operationId":"get_dependency_health_api_v1_dependencies_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Dependency Health Api V1 Dependencies Health Get"}}}}}}},"/cboverseer/api/v1/dependencies/service/{service}":{"get":{"tags":["cboverseer","dependencies"],"summary":"Get dependencies for a service","description":"Get all dependencies for a specific service.\n\nReturns both:\n- `depends_on`: Services this service calls\n- `depended_by`: Services that call this service","operationId":"get_service_dependencies_api_v1_dependencies_service__service__get","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","title":"Service"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Service Dependencies Api V1 Dependencies Service  Service  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents":{"get":{"tags":["cboverseer","incidents"],"summary":"List incidents","description":"Retrieve incidents with optional filtering.\n\n**Response Interpretation:**\n- `incidents`: List of incident records\n- `total`: Total matching incidents\n- `open_count`: Number of currently open incidents\n\n**Filtering Options:**\n- `status`: Filter by incident status\n- `severity`: Filter by severity level\n- `service`: Filter by affected service\n- `limit`: Maximum incidents to return","operationId":"list_incidents_api_v1_incidents_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cboverseer__IncidentStatus"},{"type":"null"}],"title":"Status"}},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cboverseer__IncidentSeverity"},{"type":"null"}],"title":"Severity"}},{"name":"service","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Incidents Api V1 Incidents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}},"post":{"tags":["cboverseer","incidents"],"summary":"Create incident","description":"Create a new incident record.\n\n**Typical Usage:**\nUsually called automatically when monitoring detects an issue.\nCan also be used to manually report incidents.\n\nThe incident will be stored and available for AI diagnosis.","operationId":"create_incident_api_v1_incidents_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__IncidentCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__Incident"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents/remediation/actions":{"get":{"tags":["cboverseer","incidents"],"summary":"List available remediation actions","description":"Get list of all configured remediation actions with their settings.\n\nShows confidence requirements, cooldowns, and execution statistics.","operationId":"list_remediation_actions_api_v1_incidents_remediation_actions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Remediation Actions Api V1 Incidents Remediation Actions Get"}}}}}}},"/cboverseer/api/v1/incidents/remediation/history":{"get":{"tags":["cboverseer","incidents"],"summary":"Get remediation history","description":"Get history of executed remediation actions.\n\nCan be filtered by incident ID to see all actions taken for a specific incident.","operationId":"get_remediation_history_api_v1_incidents_remediation_history_get","parameters":[{"name":"incident_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incident Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Remediation History Api V1 Incidents Remediation History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents/service/{service}":{"get":{"tags":["cboverseer","incidents"],"summary":"Get incidents by service","description":"Get all incidents for a specific service.\n\nUseful for service-level dashboards and understanding\nthe reliability history of a particular service.","operationId":"get_incidents_by_service_api_v1_incidents_service__service__get","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","title":"Service"}},{"name":"include_resolved","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Resolved"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Incidents By Service Api V1 Incidents Service  Service  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents/service/{service}/knowledge":{"get":{"tags":["cboverseer","incidents"],"summary":"Get service knowledge from cbscout","description":"Get accumulated knowledge about a service from cbscout memory.\n\nReturns learned patterns, past incidents, and resolutions\nthat help with diagnosis and self-healing.\n\n**Use this to:**\n- Understand historical issues with a service\n- Find past resolutions that worked\n- Get context for current incidents","operationId":"get_service_knowledge_api_v1_incidents_service__service__knowledge_get","parameters":[{"name":"service","in":"path","required":true,"schema":{"type":"string","title":"Service"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Service Knowledge Api V1 Incidents Service  Service  Knowledge Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents/{incident_id}":{"get":{"tags":["cboverseer","incidents"],"summary":"Get incident details","description":"Get detailed information about a specific incident.\n\n**Response Interpretation:**\n- Full incident record including metrics snapshot\n- `similar_incident_ids`: Links to similar past incidents\n- `root_cause`: AI-determined cause if diagnosed\n- `diagnosis_confidence`: How confident the AI is (0-100)","operationId":"get_incident_api_v1_incidents__incident_id__get","parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__Incident"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents/{incident_id}/acknowledge":{"post":{"tags":["cboverseer","incidents"],"summary":"Acknowledge incident","description":"Acknowledge that an incident is being investigated.\n\nThis prevents duplicate alerts and signals to others that\nsomeone is working on the issue.","operationId":"acknowledge_incident_api_v1_incidents__incident_id__acknowledge_post","parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__Incident"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents/{incident_id}/close":{"post":{"tags":["cboverseer","incidents"],"summary":"Close incident without resolution","description":"Close an incident without marking it as resolved.\n\n**Use Cases:**\n- False positive alerts\n- Duplicate incidents\n- Issues that resolved themselves\n\nOptional reason can explain why it was closed.","operationId":"close_incident_api_v1_incidents__incident_id__close_post","parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}},{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__Incident"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents/{incident_id}/diagnose":{"post":{"tags":["cboverseer","incidents"],"summary":"Diagnose incident with AI","description":"Run AI-powered diagnosis on an incident.\n\n**What it does:**\n1. Analyzes the incident metrics and alerts\n2. Queries cbscout for similar past incidents\n3. Uses cbai to determine root cause\n4. Recommends immediate and long-term fixes\n5. Assesses confidence and auto-remediation eligibility\n\n**Response includes:**\n- `root_cause`: AI's assessment of what went wrong\n- `immediate_actions`: Steps to take now\n- `long_term_fixes`: Preventive measures\n- `confidence`: 0-100 confidence score\n- `recommended_action`: Auto-remediation action if applicable\n- `can_auto_fix`: Whether auto-fix is recommended (confidence >= 90%)","operationId":"diagnose_incident_api_v1_incidents__incident_id__diagnose_post","parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Diagnose Incident Api V1 Incidents  Incident Id  Diagnose Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents/{incident_id}/remediate":{"post":{"tags":["cboverseer","incidents"],"summary":"Execute remediation action","description":"Execute a self-healing remediation action for an incident.\n\n**Available Actions:**\n- `restart_service`: Restart a systemd service\n- `clear_cache`: Clear temporary files in /tmp/cb*\n- `rotate_logs`: Force log rotation\n- `kill_zombie`: Kill zombie processes\n- `nginx_reload`: Reload nginx after config test\n\n**Safety Controls:**\n- Confidence threshold must be met\n- Cooldown period between same actions\n- Execution logging and history\n- Dry-run mode available\n\n**Use dry_run=true to simulate without executing.**","operationId":"remediate_incident_api_v1_incidents__incident_id__remediate_post","parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}},{"name":"action","in":"query","required":true,"schema":{"$ref":"#/components/schemas/cboverseer__RemediationActionType"}},{"name":"confidence","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"default":90.0,"title":"Confidence"}},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Remediate Incident Api V1 Incidents  Incident Id  Remediate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents/{incident_id}/remediation/check":{"get":{"tags":["cboverseer","incidents"],"summary":"Check if remediation is possible","description":"Check if a remediation action can be executed for an incident.\n\nReturns whether the action is allowed and any blocking reasons\n(cooldown active, confidence too low, action disabled, etc.)","operationId":"check_remediation_api_v1_incidents__incident_id__remediation_check_get","parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}},{"name":"action","in":"query","required":true,"schema":{"$ref":"#/components/schemas/cboverseer__RemediationActionType"}},{"name":"confidence","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"default":90.0,"title":"Confidence"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Check Remediation Api V1 Incidents  Incident Id  Remediation Check Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/incidents/{incident_id}/resolve":{"post":{"tags":["cboverseer","incidents"],"summary":"Resolve incident","description":"Mark an incident as resolved and record the resolution.\n\n**Typical Usage:**\nCall this after an issue has been fixed to:\n1. Close the incident\n2. Record how it was fixed\n3. Store the resolution in cbscout for learning\n\n**Important:**\nThe resolution details help train the self-healing system.\nBe descriptive about what was done and what the root cause was.","operationId":"resolve_incident_api_v1_incidents__incident_id__resolve_post","parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__IncidentResolution"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__Incident"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/reports/{agent}":{"get":{"tags":["cboverseer","reports"],"summary":"List reports for an agent","description":"List available reports for a monitoring agent, newest first.\n\nEach entry includes filename, timestamp, status (OK/WARNING/CRITICAL), and file size.","operationId":"list_agent_reports_api_v1_reports__agent__get","parameters":[{"name":"agent","in":"path","required":true,"schema":{"type":"string","title":"Agent"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Agent Reports Api V1 Reports  Agent  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/reports/{agent}/latest":{"get":{"tags":["cboverseer","reports"],"summary":"Get the latest report","description":"Returns the most recent report for an agent.\n\nIncludes both metadata and the full markdown content.","operationId":"get_latest_report_api_v1_reports__agent__latest_get","parameters":[{"name":"agent","in":"path","required":true,"schema":{"type":"string","title":"Agent"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Latest Report Api V1 Reports  Agent  Latest Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/reports/{agent}/latest/raw":{"get":{"tags":["cboverseer","reports"],"summary":"Get the latest report as raw markdown","description":"Returns the raw markdown content of the latest report, suitable for direct rendering.","operationId":"get_latest_report_raw_api_v1_reports__agent__latest_raw_get","parameters":[{"name":"agent","in":"path","required":true,"schema":{"type":"string","title":"Agent"}}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/reports/{agent}/{filename}":{"get":{"tags":["cboverseer","reports"],"summary":"Get a specific report","description":"Returns a specific report by filename.\n\nThe filename must match the pattern `YYYYMMDD-HHMMSS-STATUS.md`.","operationId":"get_report_api_v1_reports__agent___filename__get","parameters":[{"name":"agent","in":"path","required":true,"schema":{"type":"string","title":"Agent"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Report Api V1 Reports  Agent   Filename  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/reports/{agent}/{filename}/raw":{"get":{"tags":["cboverseer","reports"],"summary":"Get a specific report as raw markdown","description":"Get a specific report as raw markdown.","operationId":"get_report_raw_api_v1_reports__agent___filename__raw_get","parameters":[{"name":"agent","in":"path","required":true,"schema":{"type":"string","title":"Agent"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/rulings":{"get":{"tags":["cboverseer","rulings"],"summary":"List operator rulings (the strike zone)","operationId":"list_rulings_api_v1_rulings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Rulings Api V1 Rulings Get"}}}}}},"post":{"tags":["cboverseer","rulings"],"summary":"Rule a fingerprint (suppress noise / watch)","operationId":"create_ruling_api_v1_rulings_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__RulingCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Ruling Api V1 Rulings Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/rulings/{fingerprint}":{"delete":{"tags":["cboverseer","rulings"],"summary":"Remove a ruling","operationId":"remove_ruling_api_v1_rulings__fingerprint__delete","parameters":[{"name":"fingerprint","in":"path","required":true,"schema":{"type":"string","title":"Fingerprint"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Remove Ruling Api V1 Rulings  Fingerprint  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/telemetry/alerts":{"get":{"tags":["cboverseer","telemetry"],"summary":"Get active alerts","description":"Retrieve currently active alerts across all agents.\n\n**Response Interpretation:**\n- Returns a dict of agent -> alerts\n- Empty dict means no active alerts\n- Alerts include severity, metric, value, and threshold","operationId":"get_active_alerts_api_v1_telemetry_alerts_get","parameters":[{"name":"agent","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/cboverseer__Alert"}},"title":"Response Get Active Alerts Api V1 Telemetry Alerts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/telemetry/ingest":{"post":{"tags":["cboverseer","telemetry"],"summary":"Ingest telemetry message","description":"Submit a telemetry message from an agent.\n\n**Typical Usage:**\nThis endpoint is called by monitoring agents to report their metrics.\nAgents send periodic updates with current measurements and any\nalerts triggered by threshold breaches.","operationId":"ingest_telemetry_api_v1_telemetry_ingest_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__TelemetryMessage"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Ingest Telemetry Api V1 Telemetry Ingest Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/telemetry/recent":{"get":{"tags":["cboverseer","telemetry"],"summary":"Get recent telemetry","description":"Retrieve recent telemetry messages from monitoring agents.\n\n**Response Interpretation:**\n- `messages`: List of telemetry messages, newest first\n- `summary`: Overview of current system status\n\nEach message contains metrics collected at a point in time plus any\nalerts that were triggered.","operationId":"get_recent_telemetry_api_v1_telemetry_recent_get","parameters":[{"name":"agent","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Recent Telemetry Api V1 Telemetry Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/api/v1/telemetry/summary":{"get":{"tags":["cboverseer","telemetry"],"summary":"Get metrics summary","description":"Get a high-level summary of system metrics.\n\n**Response Interpretation:**\n- `agents_reporting`: List of agents actively sending telemetry\n- `latest_metrics`: Most recent metrics from each agent\n- `active_alerts`: Count of current alerts","operationId":"get_summary_api_v1_telemetry_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Summary Api V1 Telemetry Summary Get"}}}}}}},"/cboverseer/health":{"get":{"tags":["cboverseer","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HealthResponse"}}}}}}},"/cboverseer/health/detailed":{"get":{"tags":["cboverseer","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HealthResponse"}}}}}}},"/cboverseer/prime":{"get":{"tags":["cboverseer","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__PrimeResponse"}}}}}}},"/cboverseer/status":{"get":{"tags":["cboverseer","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__StatusResponse"}}}}}}},"/cboverseer/webhooks/sms":{"post":{"tags":["cboverseer","webhooks"],"summary":"Receive incoming SMS from cbsms","description":"Webhook endpoint for incoming SMS messages from cbsms gateway.\n\nThis endpoint processes user replies to alerts and supports multi-turn\nconversational interactions for incident investigation and remediation.\n\n**Conversation Flow:**\n\n1. User receives alert SMS\n2. User replies with question or command\n3. System parses intent and executes safe commands\n4. System sends formatted response (max 160 chars)\n5. Conversation continues until resolved or timeout\n\n**Supported Commands:**\n\nSafe (immediate execution):\n- `status` - Check service status\n- `logs` - View recent logs\n- `errors` - View error logs\n- `health` - Check health endpoint\n- `disk`, `memory`, `uptime` - System status\n\nDangerous (require confirmation):\n- `restart` - Restart service (requires YES)\n- `stop` - Stop service (requires YES)\n\n**Example Exchanges:**\n\n```\nUser: \"what service?\"\nBot: \"cbscout-api: High 5xx errors (189)\"\n\nUser: \"check cbscout\"\nBot: \"cbscout-api: DOWN, mem 89%, last rstr 2hr\"\n\nUser: \"restart it\"\nBot: \"CONFIRM: Restart cbscout-api? Reply YES/NO\"\n\nUser: \"yes\"\nBot: \"Restarted. cbscout-api now OK.\"\n```\n\n**Note:** This endpoint is called by cbsms, not directly by users.","operationId":"handle_sms_webhook_webhooks_sms_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__SMSWebhookRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__SMSWebhookResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cboverseer__HTTPValidationError"}}}}}}},"/cboverseer/webhooks/sms/cleanup":{"post":{"tags":["cboverseer","webhooks"],"summary":"Cleanup stale conversations","description":"Close all stale conversations that have timed out.\n\nCalled periodically to clean up abandoned sessions.","operationId":"cleanup_conversations_webhooks_sms_cleanup_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Cleanup Conversations Webhooks Sms Cleanup Post"}}}}}}},"/cboverseer/webhooks/sms/conversations":{"get":{"tags":["cboverseer","webhooks"],"summary":"List active SMS conversations","description":"List all active SMS conversations for monitoring and debugging.\n\nReturns conversation statistics and active session info.","operationId":"list_conversations_webhooks_sms_conversations_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Conversations Webhooks Sms Conversations Get"}}}}}}},"/cbpayments/admin/sync":{"post":{"tags":["cbpayments","Sync"],"summary":"Trigger data sync","description":"Trigger incremental data sync from Azure SQL.\n\nAccepts admin JWT or CBAUTH_API_KEY via X-API-Key header.\nRuns the sync in a thread pool since pyodbc is blocking.","operationId":"trigger_sync_admin_sync_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Trigger Sync Admin Sync Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/accounthistory/{account_code}":{"get":{"tags":["cbpayments","Reporting"],"summary":"Get account donation history","description":"Export donation history for an account as CSV.","operationId":"get_account_history_api_1_accounthistory__account_code__get","parameters":[{"name":"account_code","in":"path","required":true,"schema":{"type":"string","title":"Account Code"}},{"name":"startdate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (YYYY-MM-DD)","title":"Startdate"},"description":"Start date (YYYY-MM-DD)"},{"name":"enddate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (YYYY-MM-DD)","title":"Enddate"},"description":"End date (YYYY-MM-DD)"},{"name":"apikey","in":"query","required":true,"schema":{"type":"string","description":"API key for the account","title":"Apikey"},"description":"API key for the account"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/accounts":{"get":{"tags":["cbpayments","Accounts"],"summary":"List accounts (discovery)","description":"Paginated list of accounts for discovery by admin or service consumers.","operationId":"list_accounts_api_1_accounts_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search account_code, committee_name, company_name","title":"Q"},"description":"Search account_code, committee_name, company_name"},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__AccountListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/accounttotals/{account_code}":{"get":{"tags":["cbpayments","Reporting"],"summary":"Get account donation totals","description":"Get aggregate donation totals for an account.","operationId":"get_account_totals_api_1_accounttotals__account_code__get","parameters":[{"name":"account_code","in":"path","required":true,"schema":{"type":"string","title":"Account Code"}},{"name":"startdate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (YYYY-MM-DD)","title":"Startdate"},"description":"Start date (YYYY-MM-DD)"},{"name":"enddate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (YYYY-MM-DD)","title":"Enddate"},"description":"End date (YYYY-MM-DD)"},{"name":"apikey","in":"query","required":true,"schema":{"type":"string","description":"API key for the account","title":"Apikey"},"description":"API key for the account"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/campaignhistory/{campaign_code}":{"get":{"tags":["cbpayments","Reporting"],"summary":"Get campaign donation history","description":"Export donation history for a campaign as CSV.","operationId":"get_campaign_history_api_1_campaignhistory__campaign_code__get","parameters":[{"name":"campaign_code","in":"path","required":true,"schema":{"type":"string","title":"Campaign Code"}},{"name":"startdate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (YYYY-MM-DD)","title":"Startdate"},"description":"Start date (YYYY-MM-DD)"},{"name":"enddate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (YYYY-MM-DD)","title":"Enddate"},"description":"End date (YYYY-MM-DD)"},{"name":"apikey","in":"query","required":true,"schema":{"type":"string","description":"API key for the campaign's account","title":"Apikey"},"description":"API key for the campaign's account"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/campaignimage/{campaign_code}":{"get":{"tags":["cbpayments","Images"],"summary":"Get campaign image","description":"Serve a campaign image (logo) as binary data.","operationId":"get_campaign_image_api_1_campaignimage__campaign_code__get","parameters":[{"name":"campaign_code","in":"path","required":true,"schema":{"type":"string","title":"Campaign Code"}},{"name":"image_type","in":"query","required":false,"schema":{"type":"string","default":"DonationPageLogo","title":"Image Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/campaignimage/{campaign_code}/{image_type}":{"get":{"tags":["cbpayments","Images"],"summary":"Get campaign image by type","description":"Serve a campaign image (logo) as binary data.","operationId":"get_campaign_image_api_1_campaignimage__campaign_code___image_type__get","parameters":[{"name":"campaign_code","in":"path","required":true,"schema":{"type":"string","title":"Campaign Code"}},{"name":"image_type","in":"path","required":true,"schema":{"type":"string","title":"Image Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/campaigninfo/{campaign_code}":{"get":{"tags":["cbpayments","Campaigns"],"summary":"Get campaign info","description":"Get campaign metadata for rendering a donation page.","operationId":"get_campaign_info_api_1_campaigninfo__campaign_code__get","parameters":[{"name":"campaign_code","in":"path","required":true,"schema":{"type":"string","title":"Campaign Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__CampaignInfoResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/campaignlist/{account_code}":{"get":{"tags":["cbpayments","Campaigns"],"summary":"List campaigns","description":"List active campaigns for an account.","operationId":"list_campaigns_api_1_campaignlist__account_code__get","parameters":[{"name":"account_code","in":"path","required":true,"schema":{"type":"string","title":"Account Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__CampaignListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/campaigns":{"get":{"tags":["cbpayments","Campaigns"],"summary":"List campaigns across accounts (discovery)","description":"Paginated cross-account campaign discovery.","operationId":"discover_campaigns_api_1_campaigns_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search campaign_code or campaign_name","title":"Q"},"description":"Search campaign_code or campaign_name"},{"name":"account_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Narrow to one account","title":"Account Code"},"description":"Narrow to one account"},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__CampaignDiscoveryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/campaigntotals/{campaign_code}":{"get":{"tags":["cbpayments","Reporting"],"summary":"Get campaign donation totals","description":"Get aggregate donation totals for a single campaign.","operationId":"get_campaign_totals_api_1_campaigntotals__campaign_code__get","parameters":[{"name":"campaign_code","in":"path","required":true,"schema":{"type":"string","title":"Campaign Code"}},{"name":"startdate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (YYYY-MM-DD)","title":"Startdate"},"description":"Start date (YYYY-MM-DD)"},{"name":"enddate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (YYYY-MM-DD)","title":"Enddate"},"description":"End date (YYYY-MM-DD)"},{"name":"apikey","in":"query","required":true,"schema":{"type":"string","description":"API key for the campaign's account","title":"Apikey"},"description":"API key for the campaign's account"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/donate/{campaign_code}":{"post":{"tags":["cbpayments","Donations"],"summary":"Process donation","description":"Submit a donation to a campaign.\n\nValidates input, calculates fees, charges the credit card via Authorize.Net,\nand stores the donation record. On gateway decline, the donation record is\ndeleted and result_code=6 is returned.","operationId":"donate_api_1_donate__campaign_code__post","parameters":[{"name":"campaign_code","in":"path","required":true,"schema":{"type":"string","title":"Campaign Code"}},{"name":"apikey","in":"query","required":true,"schema":{"type":"string","description":"API key for the campaign's account","title":"Apikey"},"description":"API key for the campaign's account"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__DonationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__DonationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/donors/{account_code}":{"get":{"tags":["cbpayments","Donors"],"summary":"List donors","description":"List distinct donors for an account (derived from donation records).\n\nRequires the account API key. `require_api_key` resolves `account_code` from\nthe path and raises 404 for an unknown account, 401 for a bad key.","operationId":"list_donors_api_1_donors__account_code__get","parameters":[{"name":"account_code","in":"path","required":true,"schema":{"type":"string","title":"Account Code"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max donors to return","default":100,"title":"Limit"},"description":"Max donors to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Rows to skip","default":0,"title":"Offset"},"description":"Rows to skip"},{"name":"apikey","in":"query","required":true,"schema":{"type":"string","description":"API key for the account","title":"Apikey"},"description":"API key for the account"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__DonorsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/api/1/feed/{account_code}":{"get":{"tags":["cbpayments","Feed"],"summary":"Get donation feed","description":"Get recent donations for an account, respecting feed availability settings.","operationId":"get_donation_feed_api_1_feed__account_code__get","parameters":[{"name":"account_code","in":"path","required":true,"schema":{"type":"string","title":"Account Code"}},{"name":"count","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Count"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__FeedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/auth/forgot-password":{"post":{"tags":["cbpayments","Auth"],"summary":"Request password reset","description":"Create a password reset token and send reset email. Always returns 200.","operationId":"forgot_password_auth_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__ForgotPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/auth/me":{"get":{"tags":["cbpayments","Auth"],"summary":"Get current user","description":"Return the currently authenticated user.","operationId":"me_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__User"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbpayments/auth/reset-password":{"post":{"tags":["cbpayments","Auth"],"summary":"Reset password with token","description":"Validate token, hash new password, update user, mark token used.","operationId":"reset_password_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/auth/token":{"post":{"tags":["cbpayments","Auth"],"summary":"Issue JWT token","description":"Authenticate with username/password and receive a JWT.","operationId":"login_auth_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbpayments__Body_login_auth_token_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__Token"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HTTPValidationError"}}}}}}},"/cbpayments/health":{"get":{"tags":["cbpayments","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HealthResponse"}}}}}}},"/cbpayments/health/detailed":{"get":{"tags":["cbpayments","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__HealthResponse"}}}}}}},"/cbpayments/prime":{"get":{"tags":["cbpayments","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__PrimeResponse"}}}}}}},"/cbpayments/status":{"get":{"tags":["cbpayments","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpayments__StatusResponse"}}}}}}},"/cbpersonnel/api/v1/employees":{"get":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Roster","operationId":"list_employees_api_v1_employees_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpersonnel__EmployeeOut"},"title":"Response List Employees Api V1 Employees Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/employees/{employee_id}":{"patch":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Edit the CB-owned employee fields","operationId":"patch_employee_api_v1_employees__employee_id__patch","parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__EmployeePatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__EmployeeOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/entities":{"get":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Allocation entities","operationId":"list_entities_api_v1_entities_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpersonnel__EntityOut"},"title":"Response List Entities Api V1 Entities Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/periods":{"get":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Pay periods","description":"Generated semi-monthly periods, annotated with what is stored.\n\nThe calendar is a convenience, not truth: Paychex owns the authoritative\nperiod list (with its `submitByDate`), and P4 reconciles against it.","operationId":"list_periods_api_v1_periods_get","parameters":[{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpersonnel__PeriodOut"},"title":"Response List Periods Api V1 Periods Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/periods/{key}/allocation":{"get":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Per-entity cost allocation","description":"The product. Per-entity gross, insurance, reimbursements and share.\n\nPercentages sum to exactly 100 and unallocated lines are an explicit\n`Unassigned` bucket rather than a silent omission.","operationId":"allocation_api_v1_periods__key__allocation_get","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__AllocationOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/periods/{key}/approve":{"post":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Freeze a period's lines","operationId":"approve_period_api_v1_periods__key__approve_post","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/periods/{key}/lines":{"get":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Lines in a period","operationId":"list_lines_api_v1_periods__key__lines_get","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpersonnel__LineOut"},"title":"Response List Lines Api V1 Periods  Key  Lines Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/periods/{key}/lines/add-all":{"post":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Create a line for every active employee","operationId":"add_all_api_v1_periods__key__lines_add_all_post","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpersonnel__LineOut"},"title":"Response Add All Api V1 Periods  Key  Lines Add All Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/periods/{key}/lines/{employee_id}":{"put":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Create or update one line","operationId":"upsert_line_api_v1_periods__key__lines__employee_id__put","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__LineIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__LineOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}},"delete":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Delete one line","operationId":"delete_line_api_v1_periods__key__lines__employee_id__delete","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/periods/{key}/lines/{employee_id}/allocations":{"get":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"How one line is split across entities","operationId":"list_line_allocations_api_v1_periods__key__lines__employee_id__allocations_get","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpersonnel__LineAllocationOut"},"title":"Response List Line Allocations Api V1 Periods  Key  Lines  Employee Id  Allocations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}},"put":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Set how one line is split across entities","description":"Replace a line's allocation outright (cbpersonnel#4).\n\nWhole-list replace rather than per-row edits, because the invariants are\nproperties of the SET — percents summing to 100, one remainder, flats within\nthe line — and a per-row PATCH would have to pass through invalid\nintermediate states to get from one valid allocation to another.\n\nAn empty list clears the split, and the line falls back to being charged\nwhole to its `entity_id`. That is a real operation, not a no-op: it is how a\nsplit gets undone.","operationId":"put_line_allocations_api_v1_periods__key__lines__employee_id__allocations_put","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpersonnel__LineAllocationIn"},"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__LineOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/periods/{key}/pay":{"post":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Mark a period paid and lock it","operationId":"pay_period_api_v1_periods__key__pay_post","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/api/v1/periods/{key}/reopen":{"post":{"tags":["cbpersonnel","CBPERSONNEL"],"summary":"Reopen a closed period (admin)","description":"Permitted, audited, and deliberately a separate permission.\n\nCurtis does not hold `cbpersonnel.admin.reopen` to start: reopening a paid\nperiod is how a closed month quietly changes after the fact.","operationId":"reopen_period_api_v1_periods__key__reopen_post","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"X-CB-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant"}},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"x-user-tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-edge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"x-user-kind","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Kind"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HTTPValidationError"}}}}}}},"/cbpersonnel/health":{"get":{"tags":["cbpersonnel","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HealthResponse"}}}}}}},"/cbpersonnel/health/detailed":{"get":{"tags":["cbpersonnel","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__HealthResponse"}}}}}}},"/cbpersonnel/prime":{"get":{"tags":["cbpersonnel","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__PrimeResponse"}}}}}}},"/cbpersonnel/status":{"get":{"tags":["cbpersonnel","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpersonnel__StatusResponse"}}}}}}},"/cbprint/api/v1/health":{"get":{"tags":["cbprint","CBPRINT"],"summary":"Fleet Health","description":"Roll-up: does any printer need hands right now?","operationId":"print_fleet_health","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Print Fleet Health"}}}}}}},"/cbprint/api/v1/jobs":{"get":{"tags":["cbprint","CBPRINT"],"summary":"List Jobs","description":"Jobs across queues; which ∈ all | not-completed | completed.","operationId":"jobs_list","parameters":[{"name":"queue","in":"query","required":false,"schema":{"type":"string","default":"","title":"Queue"}},{"name":"which","in":"query","required":false,"schema":{"type":"string","default":"all","title":"Which"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Jobs List"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__HTTPValidationError"}}}}}}},"/cbprint/api/v1/jobs/{job_id}/cancel":{"post":{"tags":["cbprint","CBPRINT"],"summary":"Cancel Job","operationId":"jobs_cancel","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","title":"Job Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Jobs Cancel"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__HTTPValidationError"}}}}}}},"/cbprint/api/v1/jobs/{job_id}/restart":{"post":{"tags":["cbprint","CBPRINT"],"summary":"Restart Job","operationId":"jobs_restart","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","title":"Job Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Jobs Restart"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__HTTPValidationError"}}}}}}},"/cbprint/api/v1/onboarding":{"get":{"tags":["cbprint","CBPRINT"],"summary":"Get Onboarding","description":"Per-printer connection payloads for setup surfaces.","operationId":"print_onboarding","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Print Onboarding"}}}}}}},"/cbprint/api/v1/printers":{"get":{"tags":["cbprint","CBPRINT"],"summary":"List Printers","description":"All queues with live device state, supplies, and real models.","operationId":"printers_list","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Printers List"}}}}}}},"/cbprint/api/v1/printers/{queue}":{"get":{"tags":["cbprint","CBPRINT"],"summary":"Get Printer","description":"One queue, full detail.","operationId":"printers_get","parameters":[{"name":"queue","in":"path","required":true,"schema":{"type":"string","title":"Queue"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Printers Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__HTTPValidationError"}}}}}},"patch":{"tags":["cbprint","CBPRINT"],"summary":"Patch Printer","description":"Apply CUPS config changes; returns the fresh snapshot so the UI\nrenders what actually landed, not what it asked for.","operationId":"printers_patch","parameters":[{"name":"queue","in":"path","required":true,"schema":{"type":"string","title":"Queue"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__PrinterPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Printers Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__HTTPValidationError"}}}}}}},"/cbprint/api/v1/printers/{queue}/enable":{"post":{"tags":["cbprint","CBPRINT"],"summary":"Enable Printer","description":"Resume a paused queue and start accepting jobs.","operationId":"printers_enable","parameters":[{"name":"queue","in":"path","required":true,"schema":{"type":"string","title":"Queue"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Printers Enable"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__HTTPValidationError"}}}}}}},"/cbprint/api/v1/printers/{queue}/test":{"post":{"tags":["cbprint","CBPRINT"],"summary":"Test Page","description":"Send the stock CUPS test page.","operationId":"printers_test_page","parameters":[{"name":"queue","in":"path","required":true,"schema":{"type":"string","title":"Queue"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Id"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-User-Groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}},{"name":"X-User-Tenant","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Tenant"}},{"name":"X-CB-User-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-User-Id"}},{"name":"X-CB-Tenant-ID","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}},{"name":"cbauth_token","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbauth Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Printers Test Page"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__HTTPValidationError"}}}}}}},"/cbprint/health":{"get":{"tags":["cbprint","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__HealthResponse"}}}}}}},"/cbprint/health/detailed":{"get":{"tags":["cbprint","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__HealthResponse"}}}}}}},"/cbprint/prime":{"get":{"tags":["cbprint","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__PrimeResponse"}}}}}}},"/cbprint/status":{"get":{"tags":["cbprint","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbprint__StatusResponse"}}}}}}},"/cbpublish/api/v1/artifacts":{"get":{"tags":["cbpublish","Artifacts"],"summary":"List artifacts","description":"List non-deleted artifacts, optionally scoped to a tenant.","operationId":"list_artifacts_api_v1_artifacts_get","parameters":[{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__ArtifactList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}},"post":{"tags":["cbpublish","Artifacts"],"summary":"Ingest an artifact","description":"Ingest a structured/semistructured artifact (inline content).","operationId":"ingest_artifact_api_v1_artifacts_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__ArtifactInput"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__Artifact"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}}},"/cbpublish/api/v1/artifacts/{artifact_id}":{"get":{"tags":["cbpublish","Artifacts"],"summary":"Get an artifact","description":"Fetch one artifact by id, raw content included; 404 if absent or deleted.","operationId":"get_artifact_api_v1_artifacts__artifact_id__get","parameters":[{"name":"artifact_id","in":"path","required":true,"schema":{"type":"integer","title":"Artifact Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__Artifact"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}},"delete":{"tags":["cbpublish","Artifacts"],"summary":"Soft-delete an artifact","description":"Soft-delete an artifact (flag flip; rows are never hard-deleted).","operationId":"delete_artifact_api_v1_artifacts__artifact_id__delete","parameters":[{"name":"artifact_id","in":"path","required":true,"schema":{"type":"integer","title":"Artifact Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}}},"/cbpublish/api/v1/artifacts/{artifact_id}/normalized":{"get":{"tags":["cbpublish","Artifacts"],"summary":"Normalize an artifact to its canonical IR","description":"Normalize an artifact to the canonical IR (title/text/data/sections).","operationId":"normalized_api_v1_artifacts__artifact_id__normalized_get","parameters":[{"name":"artifact_id","in":"path","required":true,"schema":{"type":"integer","title":"Artifact Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__NormalizedArtifact"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}}},"/cbpublish/api/v1/documents":{"post":{"tags":["cbpublish","Documents"],"summary":"Assemble + render a full document (PDF/HTML)","description":"Assemble a publication (cover/TOC/index/citations) and render PDF/HTML.","operationId":"create_document_api_v1_documents_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__DocumentRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__DocumentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}}},"/cbpublish/api/v1/publish":{"post":{"tags":["cbpublish","Publish"],"summary":"Publish a work product","description":"Run the pipeline: fetch artifacts, normalize, render the template, persist.","operationId":"publish_api_v1_publish_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__PublishRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__WorkProduct"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}}},"/cbpublish/api/v1/templates":{"get":{"tags":["cbpublish","Templates"],"summary":"List templates","description":"List a tenant's templates unioned with the shared `default` templates.","operationId":"list_templates_api_v1_templates_get","parameters":[{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__TemplateList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}},"post":{"tags":["cbpublish","Templates"],"summary":"Create a template","description":"Create a tenant-scoped template; 409 if the slug already exists.","operationId":"create_template_api_v1_templates_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__TemplateInput"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__Template"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}}},"/cbpublish/api/v1/templates/{template_id}":{"get":{"tags":["cbpublish","Templates"],"summary":"Get a template","description":"Fetch one template by id, Jinja2 source included; 404 if absent or deleted.","operationId":"get_template_api_v1_templates__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"integer","title":"Template Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__Template"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}},"put":{"tags":["cbpublish","Templates"],"summary":"Update a template","description":"Update a template's fields in place (full replace); 404 if absent.","operationId":"update_template_api_v1_templates__template_id__put","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"integer","title":"Template Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__TemplateInput"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__Template"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}},"delete":{"tags":["cbpublish","Templates"],"summary":"Soft-delete a template","description":"Soft-delete a template (flag flip; rows are never hard-deleted).","operationId":"delete_template_api_v1_templates__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"integer","title":"Template Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}}},"/cbpublish/api/v1/workproducts":{"get":{"tags":["cbpublish","Publish"],"summary":"List work products","description":"List work products (metadata only — content is fetched per id).","operationId":"list_work_products_api_v1_workproducts_get","parameters":[{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__WorkProductList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}}},"/cbpublish/api/v1/workproducts/{wp_id}":{"get":{"tags":["cbpublish","Publish"],"summary":"Get a work product","description":"Fetch a work product including its rendered content.","operationId":"get_work_product_api_v1_workproducts__wp_id__get","parameters":[{"name":"wp_id","in":"path","required":true,"schema":{"type":"integer","title":"Wp Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__WorkProduct"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}},"delete":{"tags":["cbpublish","Publish"],"summary":"Soft-delete a work product","description":"Soft-delete a work product (flag flip; rows are never hard-deleted).","operationId":"delete_work_product_api_v1_workproducts__wp_id__delete","parameters":[{"name":"wp_id","in":"path","required":true,"schema":{"type":"integer","title":"Wp Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}}},"/cbpublish/api/v1/workproducts/{wp_id}/raw":{"get":{"tags":["cbpublish","Publish"],"summary":"Get a work product's rendered body (raw)","description":"Serve the rendered body under its native media type (PDF decoded inline).","operationId":"get_work_product_raw_api_v1_workproducts__wp_id__raw_get","parameters":[{"name":"wp_id","in":"path","required":true,"schema":{"type":"integer","title":"Wp Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HTTPValidationError"}}}}}}},"/cbpublish/health":{"get":{"tags":["cbpublish","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HealthResponse"}}}}}}},"/cbpublish/health/detailed":{"get":{"tags":["cbpublish","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__HealthResponse"}}}}}}},"/cbpublish/prime":{"get":{"tags":["cbpublish","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__PrimeResponse"}}}}}}},"/cbpublish/status":{"get":{"tags":["cbpublish","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpublish__StatusResponse"}}}}}}},"/cbpulse/api/admin/employees/manual":{"post":{"tags":["cbpulse","Admin"],"summary":"Create a manual (non-Homebase) employee (sysop only)","description":"Inserts an ``EmployeeORM`` row with ``source='manual'`` and a server-generated ``external_id``. The Homebase consolidator never touches manual rows — they're the explicit escape hatch for owners, 1099 contractors, and any other employee who legitimately isn't in the Homebase roster. Closes cbpulse#409.","operationId":"create_manual_employee_api_admin_employees_manual_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ManualEmployeeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ManualEmployeeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/admin/employees/merge":{"post":{"tags":["cbpulse","Admin"],"summary":"Merge a duplicate employee into a canonical employee","description":"Atomically repoints every employee_id FK (writeups, incidents, onboarding, overtime, compliance_documents, discipline_steps, payroll_packages, timesheet_exceptions) from duplicate → canonical, then marks the duplicate row status='duplicate' (soft-delete). Also rewrites loose employee references in action_logs.target_id and hr_audit_log.entity_id. Sysop-only. cbpulse#356 Phase B.","operationId":"merge_employees_api_admin_employees_merge_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MergeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MergeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/admin/employees/merge-candidates":{"get":{"tags":["cbpulse","Admin"],"summary":"List employee merge candidates","description":"Re-derives collision groups (same normalized name within a business) from live data. Sysop-only. cbpulse#356 Phase B.","operationId":"list_merge_candidates_api_admin_employees_merge_candidates_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MergeCandidatesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/admin/employees/{employee_id}":{"patch":{"tags":["cbpulse","Admin"],"summary":"Edit employee identity fields (sysop only)","description":"Partial update of an ``EmployeeORM`` row. Intended for ``manual`` rows where Homebase isn't authoritative — manual fields here won't be blown away by the next sync because the consolidator never iterates non-staging rows. On ``homebase``/``clover`` rows the edit still applies but a subsequent sync will overwrite mapped fields. Closes cbpulse#409.","operationId":"patch_employee_api_admin_employees__employee_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EmployeePatchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ManualEmployeeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/admin/employees/{employee_id}/exclude":{"post":{"tags":["cbpulse","Admin"],"summary":"Mark a spurious row as not-an-employee (sysop only)","description":"Rules the row non-staff (cbpulse#520 — e.g. a Clover vendor/role contact synthesized into an employee): sets ``status='not_employee'`` + ``is_active=False`` and (by default) clears the email. The status is sync-authoritative — source syncs never re-activate it — and the row leaves rosters, summaries, and merge-candidate scans. Refuses rows that own HR data (writeups/onboarding/compliance/…): those are real people — merge or terminate instead.","operationId":"exclude_employee_api_admin_employees__employee_id__exclude_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ExcludeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ExcludeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/admin/employees/{employee_id}/last-clock-in":{"get":{"tags":["cbpulse","Admin"],"summary":"Latest Homebase clock-in for an employee (Separation wizard prefill)","description":"Returns the employee's most-recent ``shifts`` row (max ``shift_date`` scoped to their ``business_id``) so the #462 Separation wizard can prefill ``last_day_worked``. Degrades gracefully: no shifts on file yields NULL fields rather than a 404, since some separations are verbal resignations with no labor record. cbpulse#468.","operationId":"get_last_clock_in_api_admin_employees__employee_id__last_clock_in_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__LastClockInResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/admin/employees/{employee_id}/mark-manual":{"post":{"tags":["cbpulse","Admin"],"summary":"Flip an Airtable-source employee to source='manual' (sysop only)","description":"Used by the #410 reconciliation script and the cbloom HR Queue UI to drain ``source='airtable'`` rows that have no Homebase counterpart (owners / contractors). Preserves ``external_id`` and all FK references so writeups / onboarding / compliance documents keep resolving. Refuses when the row is already ``source='manual'`` or any source other than ``airtable``. Closes cbpulse#409.","operationId":"mark_employee_manual_api_admin_employees__employee_id__mark_manual_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MarkManualResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/admin/employees/{employee_id}/rehire":{"post":{"tags":["cbpulse","Admin"],"summary":"Re-hire a terminated or inactive employee (sysop only)","description":"Symmetric to ``POST /admin/employees/{id}/terminate`` — flips an employee from ``terminated`` or ``inactive`` back to ``active`` while writing an ``hr_rehires`` row that captures the re-hire date, notes, and manager attribution. Refuses when the employee is already active, is a soft-deleted duplicate, or when the new re-hire date precedes the prior termination date. Closes cbpulse#404.","operationId":"rehire_employee_api_admin_employees__employee_id__rehire_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RehireRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RehireResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/admin/employees/{employee_id}/terminate":{"post":{"tags":["cbpulse","Admin"],"summary":"Terminate an employee with full defensibility trail (sysop only)","description":"Sets ``employees.status='terminated'``, ``termination_date``, and ``is_active=False`` while inserting a ``hr_terminations`` row that captures the 24 V.I.C. § 76 statutory ground, manager attribution, stated reason, and prior writeups cited. Writes the dual audit trail via ``log_action``. Resolves cbpulse#378.","operationId":"terminate_employee_api_admin_employees__employee_id__terminate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TerminateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TerminateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/admin/employees/{employee_id}/terminate/send-notice":{"post":{"tags":["cbpulse","Admin"],"summary":"Send the BoldSign termination acknowledgement notice (sysop only)","description":"Dispatches the property-specific BoldSign *Notice of Termination* (receipt-acknowledgement only — the internal reason is NEVER sent to the employee, cbpulse#461) for an already-recorded termination and stamps the envelope id on the ``hr_terminations`` row. The cbpulse-side termination record is created first via ``POST /admin/employees/{id}/terminate``; this is the separate notice-send step. Template ids are config-driven (``CBPULSE_TERMINATION_TEMPLATE_<SLUG>`` env override → ``TERMINATION_TEMPLATES`` fallback, cbpulse#482): a property whose template isn't provisioned yet returns **422** with a clear 'not provisioned' message and the recorded termination is left intact for retry — never a 500. Idempotent: a row that already carries an envelope returns 200 + ``X-Idempotent-Replay``.","operationId":"send_termination_notice_route_api_admin_employees__employee_id__terminate_send_notice_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TerminationNoticeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/admin/writeups/{writeup_id}/force-cancel":{"post":{"tags":["cbpulse","Admin"],"summary":"Force-cancel a writeup in any status (sysop only)","description":"Sets ``status='cancelled'`` regardless of the writeup's current state. Use for cleaning up test cruft or recovering from inconsistent rows (e.g. ``status='sent'`` with no envelope). The manager-scoped ``/writeups/{id}/cancel`` endpoint only handles ``{draft, approved}``; this one handles everything else. Resolves cbpulse#373.","operationId":"force_cancel_writeup_api_admin_writeups__writeup_id__force_cancel_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ForceCancelRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ForceCancelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/alerts":{"get":{"tags":["cbpulse","Alerts"],"summary":"List alerts","description":"Return alerts visible to the current user with optional filters for status, severity, business, and blocking flag. Results are RLS-filtered and ordered by creation date descending.","operationId":"list_alerts_api_alerts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity"}},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"is_blocking","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Blocking"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__AlertResponse"},"title":"Response List Alerts Api Alerts Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to requested business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/alerts/blocking":{"get":{"tags":["cbpulse","Alerts"],"summary":"List active blocking alerts","description":"Return all unresolved blocking alerts visible to the current user. Blocking alerts prevent certain workflows until resolved — use this endpoint to check for blockers before proceeding with operations.","operationId":"list_blocking_alerts_api_alerts_blocking_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__AlertResponse"},"type":"array","title":"Response List Blocking Alerts Api Alerts Blocking Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/alerts/{alert_id}":{"get":{"tags":["cbpulse","Alerts"],"summary":"Get alert by ID","description":"Retrieve a single alert by its ID. Returns 404 if not found, 403 if the user does not have access to the alert's business.","operationId":"get_alert_api_alerts__alert_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AlertResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this alert's business","content":{"application/json":{"example":{"detail":"No access to this alert"}}}},"404":{"description":"Alert not found","content":{"application/json":{"example":{"detail":"Alert not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/alerts/{alert_id}/acknowledge":{"post":{"tags":["cbpulse","Alerts"],"summary":"Acknowledge an alert","description":"Transition an alert from NEW to ACKNOWLEDGED. Records the acknowledging user and timestamp. Requires manager+ role. Returns 400 if the alert is not in NEW status.","operationId":"acknowledge_alert_api_alerts__alert_id__acknowledge_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AlertResponse"}}}},"400":{"description":"Alert not in acknowledgeable status","content":{"application/json":{"example":{"detail":"Cannot acknowledge alert in status 'resolved'"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to alert","content":{"application/json":{"example":{"detail":"No access to this alert"}}}},"404":{"description":"Alert not found","content":{"application/json":{"example":{"detail":"Alert not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/alerts/{alert_id}/assign":{"post":{"tags":["cbpulse","Alerts"],"summary":"Assign an alert","description":"Assign an alert to a specific user and transition it to ASSIGNED status. Cannot assign a RESOLVED alert. Requires manager+ role.","operationId":"assign_alert_api_alerts__alert_id__assign_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__alerts__AssignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AlertResponse"}}}},"400":{"description":"Cannot assign a resolved alert","content":{"application/json":{"example":{"detail":"Cannot assign a resolved alert"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to alert","content":{"application/json":{"example":{"detail":"No access to this alert"}}}},"404":{"description":"Alert not found","content":{"application/json":{"example":{"detail":"Alert not found"}}}},"422":{"description":"Validation error in request body","content":{"application/json":{"example":{"detail":[{"loc":["body","assigned_to"],"msg":"Field required","type":"missing"}]}}}}}}},"/cbpulse/api/alerts/{alert_id}/resolve":{"post":{"tags":["cbpulse","Alerts"],"summary":"Resolve an alert","description":"Transition an alert to RESOLVED status with optional resolution notes. Cannot resolve an already-resolved alert. Requires manager+ role.","operationId":"resolve_alert_api_alerts__alert_id__resolve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__alerts__ResolveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AlertResponse"}}}},"400":{"description":"Alert is already resolved","content":{"application/json":{"example":{"detail":"Alert is already resolved"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to alert","content":{"application/json":{"example":{"detail":"No access to this alert"}}}},"404":{"description":"Alert not found","content":{"application/json":{"example":{"detail":"Alert not found"}}}},"422":{"description":"Validation error in request body","content":{"application/json":{"example":{"detail":[{"loc":["body","notes"],"msg":"Input should be a valid string","type":"string_type"}]}}}}}}},"/cbpulse/api/auth/forgot-password":{"post":{"tags":["cbpulse","Auth"],"summary":"Request password reset","description":"Generate a password reset token for the given email. The token is logged server-side (email delivery is deferred). Returns 200 regardless of whether the email exists to avoid user enumeration.","operationId":"forgot_password_api_auth_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ForgotPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation error (e.g. malformed email address)"}}}},"/cbpulse/api/auth/me":{"get":{"tags":["cbpulse","Auth"],"summary":"Get current user profile","description":"Return the authenticated user's profile including their role (sysop, admin, manager, employee) and the list of business IDs they have access to.","operationId":"me_api_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MeResponse"}}}},"401":{"description":"Missing or invalid authentication token","content":{"application/json":{"example":{"detail":"Invalid or expired token"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/auth/refresh":{"post":{"tags":["cbpulse","Auth"],"summary":"Refresh access token","description":"Exchange a valid refresh token for a new access token and refresh token pair. Use this to maintain a session without re-authenticating. Returns 401 if the refresh token is invalid or expired.","operationId":"refresh_api_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__auth__RefreshRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TokenResponse"}}}},"401":{"description":"Invalid, expired, or wrong-type refresh token","content":{"application/json":{"example":{"detail":"Invalid or expired refresh token"}}}},"422":{"description":"Validation error (e.g. missing refresh_token field)"}}}},"/cbpulse/api/auth/reset-password":{"post":{"tags":["cbpulse","Auth"],"summary":"Complete password reset","description":"Validate the reset token and set a new password. Tokens are single-use and expire after 1 hour.","operationId":"reset_password_api_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid, expired, or already-used reset token","content":{"application/json":{"example":{"detail":"Invalid or expired reset token"}}}},"422":{"description":"Validation error (e.g. password shorter than 8 characters)"}}}},"/cbpulse/api/auth/token":{"post":{"tags":["cbpulse","Auth"],"summary":"Login with email and password","description":"Authenticate with email and password credentials. Returns a JWT access token and a refresh token. The access token should be passed as a Bearer token in the Authorization header for subsequent requests.","operationId":"login_api_auth_token_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TokenResponse"}}}},"401":{"description":"Invalid credentials","content":{"application/json":{"example":{"detail":"Invalid email or password"}}}},"403":{"description":"Account deactivated","content":{"application/json":{"example":{"detail":"Account is deactivated"}}}},"422":{"description":"Validation error (e.g. malformed email or missing fields)"}}}},"/cbpulse/api/brand-registry":{"get":{"tags":["cbpulse","Brand Registry"],"summary":"List brand registry","description":"List all brands in the CB Loom registry. Optionally filter to active brands only. Manager role or above required.\n\n### Use This Endpoint When...\n- Enumerating which brands the planner / Vista-push can target\n- Building a brand picker in the marketing UI\n\n### Response Interpretation for LLMs\n- Each row carries the Vista ids + workflow gid needed to push content\n- `active: false` brands should be skipped by the planner","operationId":"list_brand_registry_api_brand_registry_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"If set, filter to brands with this active flag","title":"Active"},"description":"If set, filter to brands with this active flag"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__BrandRegistryResponse"},"title":"Response List Brand Registry Api Brand Registry Get"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least manager role"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/brand-registry/{slug}":{"get":{"tags":["cbpulse","Brand Registry"],"summary":"Get a brand registry record","description":"Retrieve the full registry record for one brand by slug — including every Vista id (IG/FB/GBus), the profile-group id, and the workflow gid needed to perform a Vista push. Manager role or above required.","operationId":"get_brand_registry_api_brand_registry__slug__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BrandRegistryResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least manager role"},"404":{"description":"No brand with this slug"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"put":{"tags":["cbpulse","Brand Registry"],"summary":"Upsert a brand registry record","description":"Create or update a brand registry record by slug. Creates if none exists (201), otherwise updates only the provided fields (200). Admin role required.","operationId":"upsert_brand_registry_api_brand_registry__slug__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BrandRegistryUpsert"}}}},"responses":{"200":{"description":"Brand updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BrandRegistryResponse"}}}},"201":{"description":"Brand created"},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least admin role"},"422":{"description":"Validation error"}}},"delete":{"tags":["cbpulse","Brand Registry"],"summary":"Delete a brand registry record","description":"Permanently delete a brand registry record by slug. Admin role required. Returns 204 on success, 404 if no such brand.","operationId":"delete_brand_registry_api_brand_registry__slug__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"204":{"description":"Brand deleted"},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least admin role"},"404":{"description":"No brand with this slug"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/brands/{business_id}":{"get":{"tags":["cbpulse","Brands"],"summary":"Get brand profile","description":"Retrieve the brand profile for a business. Each business has at most one brand profile (singleton). Returns the full voice, visual, and audience configuration.\n\n### Use This Endpoint When...\n- Loading brand settings for the marketing dashboard\n- Checking if a brand profile exists before applying voice\n- Displaying brand identity configuration to managers\n\n### Response Interpretation for LLMs\n- `voice_tone` + `voice_personality` define the AI rewriting style\n- `dos_and_donts` contains guardrails for content generation\n- `smart_snippets` are reusable text blocks keyed by purpose\n- `is_active: false` means voice transformation is paused","operationId":"get_brand_profile_api_brands__business_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BrandProfileResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Requires at least manager role"}}}},"404":{"description":"No brand profile for this business","content":{"application/json":{"example":{"detail":"Brand profile not found for this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"put":{"tags":["cbpulse","Brands"],"summary":"Upsert brand profile","description":"Create or update the brand profile for a business. Since profiles are 1:1 with businesses, this uses PUT semantics — creates if none exists, updates otherwise. Only provided fields are updated on an existing profile.\n\n### Use This Endpoint When...\n- Setting up brand identity for a new business\n- Updating voice tone, colors, hashtags, or audience targeting\n- Activating or deactivating brand voice transformation\n\n### Response Interpretation for LLMs\n- Returns 201 on create, 200 on update\n- All fields are optional — omitted fields are left unchanged on update\n- `is_active: false` pauses voice transformation without deleting the profile","operationId":"upsert_brand_profile_api_brands__business_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BrandProfileUpsert"}}}},"responses":{"200":{"description":"Profile updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BrandProfileResponse"}}}},"201":{"description":"Profile created"},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Requires at least admin role"}}}},"422":{"description":"Validation error"}}},"delete":{"tags":["cbpulse","Brands"],"summary":"Delete brand profile","description":"Permanently delete the brand profile for a business. The profile can be recreated via PUT. Existing voice edit records are preserved.\n\n### Use This Endpoint When...\n- Removing brand configuration for a business\n- Starting fresh with a new brand identity\n\n### Response Interpretation for LLMs\n- Returns 204 No Content on success\n- Returns 404 if no profile exists to delete","operationId":"delete_brand_profile_api_brands__business_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Requires at least admin role"}}}},"404":{"description":"No brand profile to delete","content":{"application/json":{"example":{"detail":"Brand profile not found for this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/brands/{business_id}/apply-voice":{"post":{"tags":["cbpulse","Brands"],"summary":"Apply brand voice to text","description":"Transform input text to match the business's brand voice using AI. Loads the brand profile, constructs voice guidelines, sends to CBAI for rewriting, and records the transformation as a voice edit.\n\n### Use This Endpoint When...\n- Rewriting social media posts in brand voice\n- Transforming email copy to match brand tone\n- Applying brand personality to listing descriptions\n- Any content needs brand-consistent language\n\n### Response Interpretation for LLMs\n- `transformed_text` is the AI-rewritten version\n- `edit_id` links to the recorded voice edit for audit\n- `warning` is set if CBAI was unavailable (original text returned)\n- The transformation preserves factual content but adjusts tone and style","operationId":"apply_brand_voice_api_brands__business_id__apply_voice_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ApplyVoiceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ApplyVoiceResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Requires at least manager role"}}}},"404":{"description":"No brand profile for this business","content":{"application/json":{"example":{"detail":"Brand profile not found for this business"}}}},"422":{"description":"Validation error"}}}},"/cbpulse/api/brands/{business_id}/voice-edits":{"get":{"tags":["cbpulse","Brands"],"summary":"List voice edits","description":"Paginated list of brand voice transformations for a business. Each record shows the original text, the AI-transformed version, source content type, and who triggered it.\n\n### Use This Endpoint When...\n- Reviewing past brand voice transformations\n- Auditing AI-generated content changes\n- Filtering edits by content type (social_post, email, etc.)\n\n### Response Interpretation for LLMs\n- `edits` is ordered by most recent first\n- `total` is the full count (before pagination)\n- `source_type` identifies what kind of content was transformed\n- `edit_field` shows which specific field was rewritten","operationId":"list_voice_edits_api_brands__business_id__voice_edits_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"source_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by source content type (e.g. social_post, email)","title":"Source Type"},"description":"Filter by source content type (e.g. social_post, email)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max records to return","default":20,"title":"Limit"},"description":"Max records to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of records to skip","default":0,"title":"Offset"},"description":"Number of records to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__VoiceEditListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Requires at least manager role"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/brands/{slug}/events":{"get":{"tags":["cbpulse","Brand Events"],"summary":"List expanded brand-event occurrences in a date range","description":"Return every brand-event occurrence for one brand between `start` and `end` (inclusive). Recurring events are EXPANDED — one entry per occurrence day, computed in AST. Both proposed and confirmed events are returned by default (each carries its `status`); pass `status` to narrow. Manager role or above required.\n\n### Response Interpretation for LLMs\n- These are the KNOWN events. A day with NO occurrence has NO confirmed event — do NOT invent one; ASK before assuming.\n- `status: proposed` means the event is a candidate to confirm, not a fact.","operationId":"list_brand_events_api_brands__slug__events_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"start","in":"query","required":true,"schema":{"type":"string","format":"date","description":"Inclusive range start (YYYY-MM-DD)","title":"Start"},"description":"Inclusive range start (YYYY-MM-DD)"},{"name":"end","in":"query","required":true,"schema":{"type":"string","format":"date","description":"Inclusive range end (YYYY-MM-DD)","title":"End"},"description":"Inclusive range end (YYYY-MM-DD)"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to proposed | confirmed (default: both)","title":"Status"},"description":"Filter to proposed | confirmed (default: both)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__EventOccurrenceResponse"},"title":"Response List Brand Events Api Brands  Slug  Events Get"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least manager role"},"404":{"description":"No brand with this slug"},"422":{"description":"Invalid date range or status"}}},"post":{"tags":["cbpulse","Brand Events"],"summary":"Create a brand event (one-off or recurring)","description":"Create a one-off (`event_date`) or recurring (`rrule` + `start_date`) brand event in `proposed` or `confirmed` state. The two shapes are mutually exclusive. Manager role or above required.","operationId":"create_brand_event_api_brands__slug__events_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BrandEventCreate"}}}},"responses":{"201":{"description":"Event created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BrandEventResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least manager role"},"404":{"description":"No brand with this slug"},"422":{"description":"Invalid event shape or status"}}}},"/cbpulse/api/brands/{slug}/events/{event_id}":{"patch":{"tags":["cbpulse","Brand Events"],"summary":"Update a brand event (incl. proposed -> confirmed)","description":"Update fields on a brand event, including flipping `confirmation_status` from `proposed` to `confirmed`. Only provided fields change. The resulting row must still be a valid one-off-XOR-recurring shape. Manager role or above required.","operationId":"update_brand_event_api_brands__slug__events__event_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BrandEventUpdate"}}}},"responses":{"200":{"description":"Event updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BrandEventResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least manager role"},"404":{"description":"No such brand or event"},"422":{"description":"Invalid resulting shape or status"}}},"delete":{"tags":["cbpulse","Brand Events"],"summary":"Delete a brand event","description":"Permanently delete a brand event. Admin role required. Returns 204 on success, 404 if no such brand/event.","operationId":"delete_brand_event_api_brands__slug__events__event_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"204":{"description":"Event deleted"},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least admin role"},"404":{"description":"No such brand or event"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/businesses":{"get":{"tags":["cbpulse","Businesses"],"summary":"List businesses","description":"Return all active businesses the current user has access to, ordered by name. Results are RLS-filtered — managers and employees only see their assigned businesses; admins and sysops see all.","operationId":"list_businesses_api_businesses_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__src__api__routes__businesses__BusinessResponse"},"type":"array","title":"Response List Businesses Api Businesses Get"}}}},"401":{"description":"Missing or invalid authentication token","content":{"application/json":{"example":{"detail":"Invalid or expired token"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/businesses/{business_id}":{"get":{"tags":["cbpulse","Businesses"],"summary":"Get business by ID","description":"Retrieve a single business entity by its ID. Returns 403 if the user does not have access to this business, 404 if the business does not exist.","operationId":"get_business_api_businesses__business_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__businesses__BusinessResponse"}}}},"401":{"description":"Missing or invalid authentication token","content":{"application/json":{"example":{"detail":"Invalid or expired token"}}}},"403":{"description":"User does not have access to this business","content":{"application/json":{"example":{"detail":"You do not have access to this business"}}}},"404":{"description":"Business not found","content":{"application/json":{"example":{"detail":"Business not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/businesses/{business_id}/housekeeping":{"get":{"tags":["cbpulse","Reservations"],"summary":"Housekeeping status","description":"Current housekeeping status for all rooms at a property. Shows cleaning condition, occupancy, assigned housekeeper, and front desk status.","operationId":"get_housekeeping_api_businesses__business_id__housekeeping_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"condition","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by condition (clean, dirty, inspected)","title":"Condition"},"description":"Filter by condition (clean, dirty, inspected)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__HousekeepingResponse"},"title":"Response Get Housekeeping Api Businesses  Business Id  Housekeeping Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/businesses/{business_id}/occupancy":{"get":{"tags":["cbpulse","Reservations"],"summary":"Hotel occupancy metrics","description":"Latest hotel performance snapshot including occupancy rate, ADR, RevPAR, and booking pace. Returns the most recent HotelPerformance record.","operationId":"get_occupancy_api_businesses__business_id__occupancy_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"report_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Specific date (YYYY-MM-DD), defaults to latest","title":"Report Date"},"description":"Specific date (YYYY-MM-DD), defaults to latest"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OccupancyResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"404":{"description":"No occupancy data available","content":{"application/json":{"example":{"detail":"No occupancy data available for this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/businesses/{business_id}/occupancy/history":{"get":{"tags":["cbpulse","Reservations"],"summary":"Occupancy history","description":"Historical hotel performance metrics over a date range. Returns daily snapshots ordered by date descending.","operationId":"occupancy_history_api_businesses__business_id__occupancy_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (YYYY-MM-DD)","title":"Start Date"},"description":"Start date (YYYY-MM-DD)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (YYYY-MM-DD)","title":"End Date"},"description":"End date (YYYY-MM-DD)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":90,"description":"Max records to return","default":30,"title":"Limit"},"description":"Max records to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__OccupancyResponse"},"title":"Response Occupancy History Api Businesses  Business Id  Occupancy History Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/businesses/{business_id}/reservations":{"get":{"tags":["cbpulse","Reservations"],"summary":"List reservations","description":"Paginated list of reservations for a business with optional status and date filters. Ordered by arrival date descending.","operationId":"list_reservations_api_businesses__business_id__reservations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by source (cloudbeds, webrezpro)","title":"Source"},"description":"Filter by source (cloudbeds, webrezpro)"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Arrival date on or after (YYYY-MM-DD)","title":"Start Date"},"description":"Arrival date on or after (YYYY-MM-DD)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Arrival date on or before (YYYY-MM-DD)","title":"End Date"},"description":"Arrival date on or before (YYYY-MM-DD)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"description":"Max results to return","default":50,"title":"Limit"},"description":"Max results to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip","default":0,"title":"Offset"},"description":"Number of results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ReservationResponse"},"title":"Response List Reservations Api Businesses  Business Id  Reservations Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/businesses/{business_id}/reservations/upcoming":{"get":{"tags":["cbpulse","Reservations"],"summary":"Upcoming arrivals","description":"Confirmed reservations arriving within the next N days (default 7). Useful for front desk ops view.","operationId":"upcoming_reservations_api_businesses__business_id__reservations_upcoming_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"description":"Look-ahead window in days","default":7,"title":"Days"},"description":"Look-ahead window in days"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ReservationResponse"},"title":"Response Upcoming Reservations Api Businesses  Business Id  Reservations Upcoming Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/businesses/{business_id}/sales/items":{"get":{"tags":["cbpulse","Sales"],"summary":"Get item-level sales data","description":"Return aggregated item-level sales from synced Clover orders for a business and date range. Supports category filtering and optional prior-period comparison (WoW). Used by Report #2 (Business Ops Brief) and Report #6 (Weekly Sales Intelligence).","operationId":"get_item_sales_api_businesses__business_id__sales_items_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"Period start (YYYY-MM-DD)","title":"Start Date"},"description":"Period start (YYYY-MM-DD)"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"Period end (YYYY-MM-DD)","title":"End Date"},"description":"Period end (YYYY-MM-DD)"},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by item category","title":"Category"},"description":"Filter by item category"},{"name":"aggregation","in":"query","required":false,"schema":{"type":"string","description":"Aggregation level: daily, weekly, monthly","default":"daily","title":"Aggregation"},"description":"Aggregation level: daily, weekly, monthly"},{"name":"include_prior_period","in":"query","required":false,"schema":{"type":"boolean","description":"Include prior-period comparison data","default":false,"title":"Include Prior Period"},"description":"Include prior-period comparison data"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ItemSalesResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/campaigns":{"get":{"tags":["cbpulse","Campaigns"],"summary":"List campaign proposals","description":"Paginated list of campaign proposals with optional status and business filters. Results are RLS-filtered — users only see proposals for their assigned businesses.\n\n### Use This Endpoint When...\n- Displaying the campaign pipeline on the marketing dashboard\n- Filtering campaigns by status (draft, approved, rejected)\n- Viewing all proposals for a specific business\n\n### Response Interpretation for LLMs\n- `proposals` ordered by most recent first\n- `total` is the full count before pagination\n- Each proposal includes nested `channels` with content","operationId":"list_campaigns_api_campaigns_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status (draft, review, approved, rejected, etc.)","title":"Status"},"description":"Filter by status (draft, review, approved, rejected, etc.)"},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by business ID","title":"Business Id"},"description":"Filter by business ID"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max records to return","default":20,"title":"Limit"},"description":"Max records to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of records to skip","default":0,"title":"Offset"},"description":"Number of records to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CampaignListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/campaigns/generate":{"post":{"tags":["cbpulse","Campaigns"],"summary":"Generate AI campaign proposal","description":"Generate a new campaign proposal with AI-powered content for multiple channels. Optionally triggered by a marketing signal. Content is generated using the business's brand profile voice guidelines via CBAI.\n\n### Use This Endpoint When...\n- Creating a new marketing campaign from a signal or manually\n- Generating multi-channel content (email, social, SMS) in one call\n- Responding to a marketing opportunity detected by the signal engine\n\n### Response Interpretation for LLMs\n- `status` starts as `draft` — requires admin approval before sending\n- `channels` contains per-channel content (subject, body, CTA)\n- `metadata_json.warning` is set if AI was unavailable (placeholder content)\n- `metadata_json.suggested_asset_ids` lists recent assets to attach","operationId":"generate_campaign_api_campaigns_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GenerateCampaignRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CampaignProposalResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Manager role required","content":{"application/json":{"example":{"detail":"Requires at least manager role"}}}},"422":{"description":"Validation error"}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/campaigns/{proposal_id}":{"get":{"tags":["cbpulse","Campaigns"],"summary":"Get campaign proposal","description":"Retrieve a single campaign proposal by ID with all channel content.\n\n### Use This Endpoint When...\n- Viewing full campaign details and channel content\n- Loading a proposal for editing or approval review\n\n### Response Interpretation for LLMs\n- `channels` contains per-channel AI-generated or edited content\n- `metadata_json.generation_count` shows how many times content was generated\n- `status` indicates current lifecycle stage","operationId":"get_campaign_api_campaigns__proposal_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CampaignProposalResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Access denied","content":{"application/json":{"example":{"detail":"Access denied to this business"}}}},"404":{"description":"Proposal not found","content":{"application/json":{"example":{"detail":"Campaign proposal not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/campaigns/{proposal_id}/approve":{"post":{"tags":["cbpulse","Campaigns"],"summary":"Approve campaign proposal","description":"Approve a draft or review campaign proposal. Sets status to `approved`, records the approver, and marks all channel content as approved.\n\n### Use This Endpoint When...\n- An admin has reviewed and approved the campaign content\n- Moving a campaign from draft/review to ready-to-schedule\n\n### Response Interpretation for LLMs\n- `status` changes to `approved`\n- `approved_by` and `approved_at` are populated\n- All channel `is_approved` flags are set to `true`","operationId":"approve_campaign_api_campaigns__proposal_id__approve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CampaignProposalResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Requires at least admin role"}}}},"404":{"description":"Proposal not found","content":{"application/json":{"example":{"detail":"Campaign proposal not found"}}}},"409":{"description":"Invalid status transition","content":{"application/json":{"example":{"detail":"Can only approve proposals in draft or review status"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/campaigns/{proposal_id}/channels/{channel}/edit":{"patch":{"tags":["cbpulse","Campaigns"],"summary":"Edit channel content","description":"Manually edit content for a specific channel within a campaign proposal. Each changed field is recorded as a BrandVoiceEdit for audit trail. Only proposals in draft or review status can be edited.\n\n### Use This Endpoint When...\n- Fine-tuning AI-generated content before approval\n- Updating subject lines, body copy, or CTAs for a specific channel\n- Making manual brand voice adjustments\n\n### Response Interpretation for LLMs\n- Returns the updated channel content\n- Each changed field creates a voice edit audit record\n- Only provided fields are updated (PATCH semantics)","operationId":"edit_channel_content_api_campaigns__proposal_id__channels__channel__edit_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EditChannelContentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CampaignChannelResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Manager role required","content":{"application/json":{"example":{"detail":"Requires at least manager role"}}}},"404":{"description":"Proposal or channel not found","content":{"application/json":{"example":{"detail":"Channel not found on this proposal"}}}},"409":{"description":"Invalid status for editing","content":{"application/json":{"example":{"detail":"Can only edit proposals in draft or review status"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/campaigns/{proposal_id}/regenerate":{"post":{"tags":["cbpulse","Campaigns"],"summary":"Regenerate campaign content","description":"Regenerate AI content for an existing proposal. Refreshes all channel content using the current brand profile. If the proposal was rejected, resets status to draft and clears rejection fields.\n\n### Use This Endpoint When...\n- AI-generated content needs a fresh take\n- A rejected proposal needs new content after feedback\n- Brand profile was updated and content should reflect changes\n\n### Response Interpretation for LLMs\n- `metadata_json.generation_count` is incremented\n- All channel content is refreshed with new AI output\n- If previously rejected, `status` resets to `draft`","operationId":"regenerate_campaign_api_campaigns__proposal_id__regenerate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CampaignProposalResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Manager role required","content":{"application/json":{"example":{"detail":"Requires at least manager role"}}}},"404":{"description":"Proposal not found","content":{"application/json":{"example":{"detail":"Campaign proposal not found"}}}},"409":{"description":"Invalid status for regeneration","content":{"application/json":{"example":{"detail":"Can only regenerate proposals in draft or rejected status"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/campaigns/{proposal_id}/reject":{"post":{"tags":["cbpulse","Campaigns"],"summary":"Reject campaign proposal","description":"Reject a draft or review campaign proposal with a reason. The proposal can be regenerated after rejection.\n\n### Use This Endpoint When...\n- Content doesn't meet brand standards or strategic goals\n- Sending feedback to the creator about needed changes\n\n### Response Interpretation for LLMs\n- `status` changes to `rejected`\n- `rejected_by`, `rejected_at`, and `rejected_reason` are populated\n- Use `/regenerate` to create new content and reset to draft","operationId":"reject_campaign_api_campaigns__proposal_id__reject_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RejectCampaignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CampaignProposalResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Requires at least admin role"}}}},"404":{"description":"Proposal not found","content":{"application/json":{"example":{"detail":"Campaign proposal not found"}}}},"409":{"description":"Invalid status transition","content":{"application/json":{"example":{"detail":"Can only reject proposals in draft or review status"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/commissions/configs":{"get":{"tags":["cbpulse","Commissions"],"summary":"List commission configs","description":"Return all commission split configurations, newest first.","operationId":"list_configs_api_commissions_configs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__CommissionConfigResponse"},"type":"array","title":"Response List Configs Api Commissions Configs Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["cbpulse","Commissions"],"summary":"Create commission config","description":"Create a new commission split rule between a source and destination business.","operationId":"create_config_api_commissions_configs_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CommissionConfigCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CommissionConfigResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/commissions/configs/{config_id}":{"get":{"tags":["cbpulse","Commissions"],"summary":"Get commission config","description":"Return a single commission split configuration by ID.","operationId":"get_config_api_commissions_configs__config_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"string","title":"Config Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CommissionConfigResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"put":{"tags":["cbpulse","Commissions"],"summary":"Update commission config","description":"Partially update a commission config. Only provided fields are changed.","operationId":"update_config_api_commissions_configs__config_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"config_id","in":"path","required":true,"schema":{"type":"string","title":"Config Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CommissionConfigUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CommissionConfigResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/commissions/splits":{"get":{"tags":["cbpulse","Commissions"],"summary":"List commission splits","description":"Return computed commission split audit records. Filterable by date range and business.","operationId":"list_splits_api_commissions_splits_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__CommissionSplitResponse"},"title":"Response List Splits Api Commissions Splits Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/compliance/analyze":{"post":{"tags":["cbpulse","HR"],"summary":"Run compliance analysis for a violation","description":"Deterministic matrix — same logic as the Claude HR skill. Returns handbook citation (current-version policy for the business + violation type), VI §76 mapping, matrix position, recommended severity, defensibility score, alternatives with pros/cons, and documentation gaps. Set create_draft=true to persist a draft writeup for progressive PATCH by the caller.","operationId":"compliance_analyze_api_compliance_analyze_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ComplianceAnalyzeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ComplianceAnalyzeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/compliance/handbook/{business_id}":{"get":{"tags":["cbpulse","HR"],"summary":"Handbook policies for a business","description":"Returns current-version policies. Pass ?version=N for a specific historical version (useful when reviewing a signed writeup's original citation post-revision). Response includes ``source_pdf_uuid`` (cbfiles object name, nullable) so the caller can render a 'open source PDF' link via the cbfiles proxy (cbpulse#434).","operationId":"handbook_lookup_api_compliance_handbook__business_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"version","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"title":"Version"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/compliance/items":{"get":{"tags":["cbpulse","Entity Compliance"],"summary":"List compliance items","description":"List compliance items with derived status, filterable by subject / type / status.","operationId":"list_compliance_items_api_compliance_items_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"subject_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Type"}},{"name":"subject_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Id"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by DERIVED status","title":"Status"},"description":"Filter by DERIVED status"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ComplianceItemResponse"},"title":"Response List Compliance Items Api Compliance Items Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Entity Compliance"],"summary":"Create a compliance item","description":"Create a renewable obligation on an entity or vehicle. Manager+. Audited.","operationId":"create_compliance_item_api_compliance_items_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ComplianceItemCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ComplianceItemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/compliance/items/{item_id}":{"patch":{"tags":["cbpulse","Entity Compliance"],"summary":"Update a compliance item","description":"Patch dates / status / owner / document_url on an item. Manager+. Audited.","operationId":"update_compliance_item_api_compliance_items__item_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ComplianceItemUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ComplianceItemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/compliance/standard-documents":{"get":{"tags":["cbpulse","HR"],"summary":"Shared standard employee agreements (reference shelf)","description":"Returns the portfolio-wide standard agreements — No Harassment Policy, Arbitration Agreement, Employee Standards of Conduct. These are identical across all entities (only the header company name differs), so each is stored once in the shared cbpulse-handbooks bucket under ``standard-docs/`` and referenced globally rather than per business (cbpulse#445). Each ``source_pdf_uuid`` resolves against the bucket the same way a handbook UUID does (cbpulse#434).","operationId":"standard_documents_lookup_api_compliance_standard_documents_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array","title":"Response Standard Documents Lookup Api Compliance Standard Documents Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/compliance/status":{"get":{"tags":["cbpulse","Entity Compliance"],"summary":"Compliance status rollup (or single-subject detail)","description":"The red/amber/green/missing view. Returns every compliance-tracked subject (entities + vehicles) with its items and derived status, plus portfolio and per-island roll-up counts. Status derivation is server-side.\n\n**Single-subject mode (cbpulse#512 §6a):** pass `?subject_id={id}` to get a single **bare `SubjectStatusResponse`** (not the rollup envelope) for the detail view — includes `edc_program` and `recurring_by_cadence` so the dashboard can read `edc_program.*` and `recurring_by_cadence.<cadence>` by dotted path. 404 if the id matches no entity or vehicle.","operationId":"compliance_status_rollup_api_compliance_status_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"island","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Island"}},{"name":"subject_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"entity | vehicle","title":"Subject Type"},"description":"entity | vehicle"},{"name":"subject_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single-subject detail (§6a): return one bare SubjectStatusResponse for this entity/vehicle id instead of the rollup.","title":"Subject Id"},"description":"Single-subject detail (§6a): return one bare SubjectStatusResponse for this entity/vehicle id instead of the rollup."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbpulse__SubjectStatusResponse"},{"$ref":"#/components/schemas/cbpulse__StatusRollupResponse"}],"title":"Response Compliance Status Rollup Api Compliance Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/content-queue/batch":{"post":{"tags":["cbpulse","Content Queue"],"summary":"Create Batch","description":"Claude writes a full batch of posts.","operationId":"create_batch_api_content_queue_batch_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BatchCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BatchCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/content-queue/batch/{batch_id}":{"get":{"tags":["cbpulse","Content Queue"],"summary":"Get Batch","description":"Get all posts in a batch.","operationId":"get_batch_api_content_queue_batch__batch_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string","title":"Batch Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BatchDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/content-queue/batch/{batch_id}/approve-all":{"post":{"tags":["cbpulse","Content Queue"],"summary":"Approve All","description":"Bulk approve all draft posts in a batch.","operationId":"approve_all_api_content_queue_batch__batch_id__approve_all_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string","title":"Batch Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/content-queue/batches":{"get":{"tags":["cbpulse","Content Queue"],"summary":"List Batches","description":"List batches with summary stats.","operationId":"list_batches_api_content_queue_batches_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__BatchSummary"},"title":"Response List Batches Api Content Queue Batches Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/content-queue/push":{"post":{"tags":["cbpulse","Content Queue"],"summary":"Push Approved","description":"Push all approved posts to Vista Social.","operationId":"push_approved_api_content_queue_push_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/content-queue/{post_id}":{"patch":{"tags":["cbpulse","Content Queue"],"summary":"Update Post","description":"Edit a post or change its status (approve/reject).","operationId":"update_post_api_content_queue__post_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","title":"Post Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PostUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__content_queue__PostResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/content-queue/{post_id}/media-url":{"get":{"tags":["cbpulse","Content Queue"],"summary":"Get Media Url","description":"Re-sign a cbfiles URL from media_path.","operationId":"get_media_url_api_content_queue__post_id__media_url_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/documents":{"get":{"tags":["cbpulse","Documents"],"summary":"List documents","description":"Return documents with optional filters for business, type, source, status, and EDC condition.","operationId":"list_documents_api_documents_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"document_type_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Type Code"}},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},{"name":"lifecycle_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lifecycle Status"}},{"name":"needs_review","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Needs Review"}},{"name":"edc_condition_number","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Edc Condition Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__DocumentResponse"},"title":"Response List Documents Api Documents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Documents"],"summary":"Create document record","description":"Create a document metadata record. Validates document_type_code FK if provided.","operationId":"create_document_api_documents_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DocumentCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DocumentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/documents/expiring":{"get":{"tags":["cbpulse","Documents"],"summary":"List expiring documents","description":"Return documents grouped by urgency tier based on expiration date within the lookahead window.","operationId":"list_expiring_documents_api_documents_expiring_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days_ahead","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":90,"title":"Days Ahead"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DocumentExpiringResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/documents/needs-review":{"get":{"tags":["cbpulse","Documents"],"summary":"List documents needing review","description":"Return all documents flagged for manual review, newest first.","operationId":"list_needs_review_api_documents_needs_review_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__DocumentResponse"},"type":"array","title":"Response List Needs Review Api Documents Needs Review Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/documents/search":{"get":{"tags":["cbpulse","Documents"],"summary":"Search documents","description":"Full-text search on title and extracted text via LIKE. Returns up to 50 results.","operationId":"search_documents_api_documents_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__DocumentResponse"},"title":"Response Search Documents Api Documents Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/documents/{doc_id}":{"get":{"tags":["cbpulse","Documents"],"summary":"Get document detail","description":"Return a single document with full metadata including extracted text.","operationId":"get_document_api_documents__doc_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DocumentDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"patch":{"tags":["cbpulse","Documents"],"summary":"Update document metadata","description":"Partially update a document record. Only provided fields are changed; changes are audit-logged.","operationId":"update_document_api_documents__doc_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DocumentUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DocumentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/edc/annual-package/{year}":{"get":{"tags":["cbpulse","EDC Compliance"],"summary":"Get annual compliance package","description":"Assemble the annual EDC package showing each condition's proof status, coverage percentage, and missing items.","operationId":"annual_package_api_edc_annual_package__year__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"year","in":"path","required":true,"schema":{"type":"integer","title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AnnualPackageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/edc/calendar":{"get":{"tags":["cbpulse","EDC Compliance"],"summary":"Get filing calendar","description":"Return upcoming filing deadlines within the lookahead window, with urgency color coding.","operationId":"filing_calendar_api_edc_calendar_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days_ahead","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":90,"title":"Days Ahead"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__CalendarEntry"},"title":"Response Filing Calendar Api Edc Calendar Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/edc/conditions":{"get":{"tags":["cbpulse","EDC Compliance"],"summary":"Get conditions status board","description":"Return all EDC conditions with proof status (green/yellow/red) for the given year.","operationId":"conditions_status_board_api_edc_conditions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ConditionStatus"},"title":"Response Conditions Status Board Api Edc Conditions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/edc/conditions/{condition_number}/proof":{"post":{"tags":["cbpulse","EDC Compliance"],"summary":"Attach proof to condition","description":"Link document IDs as proof for a condition and year. Creates a filing if none exists.","operationId":"attach_proof_api_edc_conditions__condition_number__proof_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"condition_number","in":"path","required":true,"schema":{"type":"integer","title":"Condition Number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AttachProofRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Attach Proof Api Edc Conditions  Condition Number  Proof Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/edc/filings":{"get":{"tags":["cbpulse","EDC Compliance"],"summary":"List EDC filings","description":"Return filings ordered by due date. Filterable by obligation, status, period, or overdue-only.","operationId":"list_filings_api_edc_filings_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"obligation_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Obligation Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period"}},{"name":"overdue_only","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Overdue Only"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__FilingResponse"},"title":"Response List Filings Api Edc Filings Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","EDC Compliance"],"summary":"Create filing record","description":"Create a new filing record for an obligation. Validates the obligation_id FK.","operationId":"create_filing_api_edc_filings_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FilingCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FilingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/edc/filings/{filing_id}":{"patch":{"tags":["cbpulse","EDC Compliance"],"summary":"Update filing record","description":"Partially update a filing's status, proof documents, or notes. Changes are audit-logged.","operationId":"update_filing_api_edc_filings__filing_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"filing_id","in":"path","required":true,"schema":{"type":"string","title":"Filing Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FilingUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FilingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/edc/obligations":{"get":{"tags":["cbpulse","EDC Compliance"],"summary":"List EDC obligations","description":"Return all EDC obligations ordered by condition number. Filterable by type, category, or condition number.","operationId":"list_obligations_api_edc_obligations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"obligation_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Obligation Type"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"condition_number","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Condition Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ObligationResponse"},"title":"Response List Obligations Api Edc Obligations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/edc/obligations/{obligation_id}":{"get":{"tags":["cbpulse","EDC Compliance"],"summary":"Get obligation detail","description":"Return a single obligation with its full filing history.","operationId":"get_obligation_api_edc_obligations__obligation_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"obligation_id","in":"path","required":true,"schema":{"type":"string","title":"Obligation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ObligationDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees":{"get":{"tags":["cbpulse","Employees"],"summary":"List employees","description":"Paginated employee directory with optional filters for business, department, active status, and employment type. RLS-filtered.","operationId":"list_employees_api_employees_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"department","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"}},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},{"name":"employment_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employment Type"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name or email","title":"Search"},"description":"Search by name or email"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__EmployeeResponse"},"title":"Response List Employees Api Employees Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/former":{"get":{"tags":["cbpulse","HR"],"summary":"Former employees (terminated + inactive)","description":"Browseable list of non-active employees for HR / legal lookup. Joins ``hr_terminations`` for the statutory-ground and decided-by metadata when present. Sort: termination_date DESC. Closes cbpulse#406.","operationId":"list_former_employees_api_employees_former_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"terminated | inactive | unset (both)","title":"Status"},"description":"terminated | inactive | unset (both)"},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date — only show after this","title":"Since"},"description":"ISO date — only show after this"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__FormerEmployeeRow"},"title":"Response List Former Employees Api Employees Former Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/headcount-history":{"get":{"tags":["cbpulse","Employees"],"summary":"Headcount history","description":"Active employee headcount per business over time. Supports configurable lookback and interval (daily, weekly, monthly).","operationId":"get_headcount_history_api_employees_headcount_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Lookback period in days","default":90,"title":"Days"},"description":"Lookback period in days"},{"name":"interval","in":"query","required":false,"schema":{"$ref":"#/components/schemas/cbpulse__HeadcountInterval","description":"Snapshot interval","default":"weekly"},"description":"Snapshot interval"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HeadcountHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/overtime-alerts":{"get":{"tags":["cbpulse","Employees"],"summary":"Get overtime and staffing alerts","description":"Detect overtime threshold breaches and multi-location conflicts based on shift data in the current week. Requires manager+ role.","operationId":"get_overtime_alerts_api_employees_overtime_alerts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"threshold_hours","in":"query","required":false,"schema":{"type":"number","description":"Weekly overtime threshold","default":40.0,"title":"Threshold Hours"},"description":"Weekly overtime threshold"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__OvertimeAlertResponse"},"title":"Response Get Overtime Alerts Api Employees Overtime Alerts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/summary":{"get":{"tags":["cbpulse","HR"],"summary":"Batched per-employee summary for preset filters (cbpulse#407)","description":"Returns one row per employee with the rollups cbloom's Employee File preset chips need: onboarding signed-yes/no, compliance valid/expired/missing counts, open + draft writeup counts, last writeup date. Four group-by queries against the source tables (no per-employee fan-out). p95 < 1s on ~250 employees.","operationId":"employees_summary_api_employees_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","description":"Include status != 'active' employees","default":false,"title":"Include Inactive"},"description":"Include status != 'active' employees"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__EmployeeSummaryRow"},"title":"Response Employees Summary Api Employees Summary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/{employee_id}":{"get":{"tags":["cbpulse","Employees"],"summary":"Get employee detail","description":"Retrieve employee with compliance documents.","operationId":"get_employee_api_employees__employee_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EmployeeDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/{employee_id}/compliance":{"get":{"tags":["cbpulse","Employees"],"summary":"Get compliance documents","description":"List all compliance documents for an employee.","operationId":"get_compliance_api_employees__employee_id__compliance_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ComplianceDocResponse"},"title":"Response Get Compliance Api Employees  Employee Id  Compliance Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"put":{"tags":["cbpulse","Employees"],"summary":"Add or update compliance document","description":"Add a new compliance document or update existing by type. Requires manager+ role.","operationId":"upsert_compliance_api_employees__employee_id__compliance_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ComplianceUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ComplianceDocResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/{employee_id}/compliance-documents/{doc_id}/document-url":{"get":{"tags":["cbpulse","HR","HR"],"summary":"Resolve a compliance document to a viewable PDF URL (cbpulse#493)","description":"Mints a fresh PDF URL for a compliance document. cbesig/DocuSign-backed rows resolve via the archive presigned URL (source=archive) or, when the archive has no download_url, the live cbesig stream (source=live, probed for retrievability). Non-cbesig stored paths return source=legacy. Un-retrievable docs return 404 so the UI degrades to a graceful toast rather than a broken link. RequireManager + business scope.","operationId":"get_compliance_document_url_api_employees__employee_id__compliance_documents__doc_id__document_url_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}},{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ComplianceDocumentURLResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/{employee_id}/compliance-documents/{doc_id}/document.pdf":{"get":{"tags":["cbpulse","HR","HR"],"summary":"Stream a compliance document's PDF (live cbesig fallback)","operationId":"stream_compliance_document_api_employees__employee_id__compliance_documents__doc_id__document_pdf_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}},{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"PDF stream","content":{"application/pdf":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/{employee_id}/contact":{"patch":{"tags":["cbpulse","Employees"],"summary":"Set/correct an employee's email and phone (manager+)","description":"Manager-scoped, sync-safe edit of an employee's **email** and **phone** (cbpulse#489). A Homebase profile transferred between orgs frequently drops the email, so the employee lands with none and onboarding send fails. Unlike the sysop ``PATCH /api/admin/employees/{id}``, the fields set here are recorded in ``manual_fields`` so the Homebase/Clover consolidator will **not** overwrite them on the next sync — the manager's correction persists. Business-scoped via ``can_access_business``; audit-logged.","operationId":"patch_employee_contact_api_employees__employee_id__contact_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ContactPatchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EmployeeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/{employee_id}/discipline":{"get":{"tags":["cbpulse","HR"],"summary":"Get discipline history","description":"Cross-business progressive discipline timeline for an employee.","operationId":"get_discipline_history_api_employees__employee_id__discipline_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__DisciplineStepResponse"},"title":"Response Get Discipline History Api Employees  Employee Id  Discipline Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","HR"],"summary":"Record discipline step","description":"Add a progressive discipline step. Enforces ladder sequence — cannot skip steps without override justification. Requires manager+ role.","operationId":"add_discipline_step_api_employees__employee_id__discipline_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CreateWriteUpRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DisciplineStepResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/{employee_id}/file":{"get":{"tags":["cbpulse","HR"],"summary":"Employee File — full HR record in one call","description":"Powers the HR Loom Employee File tab. Aggregates onboarding, compliance docs, discipline history, OT requests, and a lightweight risk assessment derived from prior writeups.","operationId":"employee_file_api_employees__employee_id__file_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/{employee_id}/recommended-action":{"get":{"tags":["cbpulse","HR"],"summary":"AI recommended disciplinary action","description":"AI recommends appropriate action based on discipline ladder position and incident history. Requires manager+ role.","operationId":"get_recommended_action_api_employees__employee_id__recommended_action_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RecommendedActionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/employees/{employee_id}/termination-risk":{"get":{"tags":["cbpulse","HR"],"summary":"AI termination risk assessment","description":"AI reviews employee file against WDA §76 requirements across 5 dimensions. Returns risk score with explanations. Requires admin+ role.","operationId":"get_termination_risk_api_employees__employee_id__termination_risk_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TerminationRiskResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/entities":{"get":{"tags":["cbpulse","Entities"],"summary":"List entities","description":"Return all legal entities. Filterable by island, type, active status, and EDC holder flag.","operationId":"list_entities_api_entities_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"island","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Island"}},{"name":"entity_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Type"}},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},{"name":"is_edc_holder","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Edc Holder"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__EntityResponse"},"title":"Response List Entities Api Entities Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/entities/resolver/pending":{"get":{"tags":["cbpulse","Entity Resolver"],"summary":"List pending entity resolutions","description":"Returns the triage queue Luke walks end-of-day. Filter by status (default 'pending') and paginate via limit/offset. Oldest first so nothing ages out silently.","operationId":"list_pending_api_entities_resolver_pending_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"pending","title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PendingListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/entities/resolver/pending/{pending_id}/resolve":{"post":{"tags":["cbpulse","Entity Resolver"],"summary":"Resolve a pending row (match / create / ignore)","description":"Luke's three-action entry point. Bumps alias_version once after the write so other workers rehydrate on their next 1s poll. The body's `action` field picks one of:\n\n- **match**: attach an alias to an existing entity. The same   string becomes a live matcher target for future ingests, and   any other pending rows whose payload contains it are auto-  resolved in the same transaction.\n- **create**: spin up a new entity + optionally an alias.\n- **ignore**: add a deny_list pattern.","operationId":"resolve_pending_api_entities_resolver_pending__pending_id__resolve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"pending_id","in":"path","required":true,"schema":{"type":"integer","title":"Pending Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ResolveResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/entities/resolver/search":{"get":{"tags":["cbpulse","Entity Resolver"],"summary":"Search entities (for the resolver picker)","description":"Substring match on legal_name, dba_name, and — for fuzzy matcher-style lookup — normalized aliases. Returns up to `limit` results; caller should paginate if UX needs it.","operationId":"search_entities_api_entities_resolver_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200,"title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__EntitySearchResult"},"title":"Response Search Entities Api Entities Resolver Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/entities/trade-names":{"get":{"tags":["cbpulse","Entities"],"summary":"List trade names","description":"Return all trade names sorted by expiration date, soonest first.","operationId":"list_trade_names_api_entities_trade_names_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__TradeNameResponse"},"type":"array","title":"Response List Trade Names Api Entities Trade Names Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/entities/{entity_id}":{"get":{"tags":["cbpulse","Entities"],"summary":"Get entity detail","description":"Return a single entity with its trade names and document count.","operationId":"get_entity_api_entities__entity_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EntityDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/entities/{entity_id}/attributes":{"patch":{"tags":["cbpulse","Entity Compliance"],"summary":"Update entity compliance attributes","description":"Edit EIN, date_formed (stored in entity_meta), island, legal_name, and the primary address (entity_addresses). Manager+. Audited.","operationId":"update_entity_attributes_api_entities__entity_id__attributes_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EntityAttributesUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Entity Attributes Api Entities  Entity Id  Attributes Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/events":{"get":{"tags":["cbpulse","Events"],"summary":"List event cadences","description":"Paginated list of event cadences with optional filters. Results are RLS-filtered to the user's assigned businesses.\n\n### Use This Endpoint When...\n- Displaying the events calendar on the marketing dashboard\n- Filtering events by status (draft, active, completed) or business\n- Viewing upcoming events that have auto-generated campaigns\n\n### Response Interpretation for LLMs\n- `events` ordered by event_date descending (most recent first)\n- `total` is the full count before pagination\n- `campaign_proposal_id` links to the auto-generated campaign if present","operationId":"list_events_api_events_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by business ID","title":"Business Id"},"description":"Filter by business ID"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active flag","title":"Is Active"},"description":"Filter by active flag"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max records to return","default":20,"title":"Limit"},"description":"Max records to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of records to skip","default":0,"title":"Offset"},"description":"Number of records to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EventListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Events"],"summary":"Create event cadence with auto-generated campaign","description":"Create a new event cadence and automatically generate an AI campaign proposal linked to it. The campaign objective is built from the event name, date, and description.\n\n### Use This Endpoint When...\n- Planning a holiday party, seasonal promotion, or special event\n- Creating an event that should auto-trigger marketing content\n- Setting up a multi-step cadence (e.g. teaser → reminder → day-of)\n\n### Response Interpretation for LLMs\n- `campaign_proposal_id` will be populated with the auto-generated campaign\n- The linked campaign starts in `draft` status and needs approval\n- `steps` define the marketing cadence timeline relative to event_date","operationId":"create_event_api_events_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EventCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EventCadenceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/events/{event_id}":{"patch":{"tags":["cbpulse","Events"],"summary":"Update event cadence","description":"Update event fields with PATCH semantics (only provided fields change). If `regenerate_campaign` is true and key fields (name, description, event_date) changed, the linked campaign content is regenerated via AI.\n\n### Use This Endpoint When...\n- Changing event date, name, or description\n- Activating/deactivating an event\n- Updating cadence steps\n- Refreshing campaign content after event details change\n\n### Response Interpretation for LLMs\n- Only provided fields are updated (PATCH semantics)\n- `regenerate_campaign` triggers AI content refresh for the linked campaign\n- Campaign regeneration only happens if key fields actually changed","operationId":"update_event_api_events__event_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EventUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EventCadenceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/events/{event_id}/generate-campaign":{"post":{"tags":["cbpulse","Events"],"summary":"Generate or regenerate campaign for event","description":"Generate a new campaign proposal for this event, or regenerate content if one already exists. Links the campaign to the event.\n\n### Use This Endpoint When...\n- An event was created without a campaign and needs one\n- The existing campaign content needs a fresh AI generation\n- Brand profile was updated and campaign should reflect changes\n\n### Response Interpretation for LLMs\n- `campaign_proposal_id` will be set to the new/regenerated campaign\n- New campaigns start in `draft` status\n- Regenerated campaigns increment `metadata_json.generation_count`","operationId":"generate_event_campaign_api_events__event_id__generate_campaign_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EventCadenceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/export/{report_type}":{"get":{"tags":["cbpulse","Export"],"summary":"Export financial report","description":"Download a financial report as CSV or PDF.\n\n**Report types:** `pnl`, `payables`, `deadlines`, `flash-report`\n\n**Formats:** `csv`, `pdf`\n\n### Parameters by report type\n- **pnl**: `start_date` (required), `end_date` (required), `business_id` (optional — omit for portfolio)\n- **payables**: no extra params\n- **deadlines**: no extra params\n- **flash-report**: `date` (default today), `period` (day/wtd/ptd, default day)","operationId":"export_report_api_export__report_type__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"report_type","in":"path","required":true,"schema":{"type":"string","title":"Report Type"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","description":"Export format: csv or pdf","default":"csv","title":"Format"},"description":"Export format: csv or pdf"},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Business ID (pnl only — omit for portfolio)","title":"Business Id"},"description":"Business ID (pnl only — omit for portfolio)"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date YYYY-MM-DD (required for pnl)","title":"Start Date"},"description":"Start date YYYY-MM-DD (required for pnl)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date YYYY-MM-DD (required for pnl)","title":"End Date"},"description":"End date YYYY-MM-DD (required for pnl)"},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Report date YYYY-MM-DD (flash-report, default today)","title":"Date"},"description":"Report date YYYY-MM-DD (flash-report, default today)"},{"name":"period","in":"query","required":false,"schema":{"type":"string","description":"Aggregation period: day, wtd, ptd (flash-report)","default":"day","title":"Period"},"description":"Aggregation period: day, wtd, ptd (flash-report)"},{"name":"report_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"EDC report ID (edc-report only)","title":"Report Id"},"description":"EDC report ID (edc-report only)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"EDC report not found","content":{"application/json":{"example":{"detail":"EDC report not found"}}}},"422":{"description":"Invalid report type, format, or missing required parameters","content":{"application/json":{"example":{"detail":"start_date and end_date are required for pnl export"}}}}}}},"/cbpulse/api/fb/auth":{"post":{"tags":["cbpulse","F&B Auth"],"summary":"PIN login","operationId":"pin_login_api_fb_auth_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PinLoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBUserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/auth/logout":{"post":{"tags":["cbpulse","F&B Auth"],"summary":"Logout","operationId":"logout_api_fb_auth_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbpulse/api/fb/auth/me":{"get":{"tags":["cbpulse","F&B Auth"],"summary":"Current FB user","operationId":"me_api_fb_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBUserResponse"}}}}}}},"/cbpulse/api/fb/budgets":{"get":{"tags":["cbpulse","F&B Budgets"],"summary":"List budgets","operationId":"list_budgets_api_fb_budgets_get","parameters":[{"name":"businessId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Businessid"}},{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Month"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__BudgetResponse"},"title":"Response List Budgets Api Fb Budgets Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"put":{"tags":["cbpulse","F&B Budgets"],"summary":"Create or update budget","operationId":"upsert_budget_api_fb_budgets_put","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BudgetUpsertRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BudgetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/budgets/auto-run":{"post":{"tags":["cbpulse","F&B Budgets"],"summary":"Auto-set monthly F&B budgets from prior-month Clover actuals (#499)","description":"Set each active F&B business's *month* budget to prior-month\nSUM(fb_revenue) x target_cogs_pct. Manual budgets are preserved.\n\nLocalhost-only / CBCron-triggered (the cron POSTs this on the 1st of each\nmonth). No user-auth dep — matches the other cron endpoints\n(``/api/system/tick``, ``/api/reports/render-and-send``); the public path is\ngated by nginx, cron hits 127.0.0.1 directly. Idempotent + manual-override-\nsafe, so a re-trigger never clobbers a manual budget.","operationId":"auto_run_budgets_api_fb_budgets_auto_run_post","parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Target month YYYY-MM (defaults to current month)","title":"Month"},"description":"Target month YYYY-MM (defaults to current month)"},{"name":"targetCogsPct","in":"query","required":false,"schema":{"type":"number","description":"COGS target ratio (default 0.35)","default":0.35,"title":"Targetcogspct"},"description":"COGS target ratio (default 0.35)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/businesses":{"get":{"tags":["cbpulse","F&B Reference"],"summary":"List F&B businesses","operationId":"list_businesses_api_fb_businesses_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__src__api__routes__fb_reference__BusinessResponse"},"type":"array","title":"Response List Businesses Api Fb Businesses Get"}}}}}}},"/cbpulse/api/fb/dashboard/{business_id}":{"get":{"tags":["cbpulse","F&B Dashboard"],"summary":"Dashboard data","operationId":"get_dashboard_api_fb_dashboard__business_id__get","parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Month"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/graphs":{"get":{"tags":["cbpulse","F&B Graphs"],"summary":"List available graph reports","description":"Returns the catalog of pre-made graph reports with their IDs and descriptions.","operationId":"list_reports_api_fb_graphs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbpulse/api/fb/graphs/budget-pacing":{"get":{"tags":["cbpulse","F&B Graphs"],"summary":"Budget pacing — cumulative spend vs target","operationId":"budget_pacing_api_fb_graphs_budget_pacing_get","parameters":[{"name":"businessId","in":"query","required":true,"schema":{"type":"string","title":"Businessid"}},{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Month"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/graphs/portfolio-overview":{"get":{"tags":["cbpulse","F&B Graphs"],"summary":"All businesses — spend vs budget snapshot","operationId":"portfolio_overview_api_fb_graphs_portfolio_overview_get","parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Month"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/graphs/spend-by-category":{"get":{"tags":["cbpulse","F&B Graphs"],"summary":"Food vs Drink spend breakdown","operationId":"spend_by_category_api_fb_graphs_spend_by_category_get","parameters":[{"name":"businessId","in":"query","required":true,"schema":{"type":"string","title":"Businessid"}},{"name":"months","in":"query","required":false,"schema":{"type":"integer","maximum":24,"minimum":1,"default":6,"title":"Months"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/graphs/spend-by-vendor":{"get":{"tags":["cbpulse","F&B Graphs"],"summary":"Top vendors by spend","operationId":"spend_by_vendor_api_fb_graphs_spend_by_vendor_get","parameters":[{"name":"businessId","in":"query","required":true,"schema":{"type":"string","title":"Businessid"}},{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Month"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":25,"minimum":1,"default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/graphs/spend-vs-budget":{"get":{"tags":["cbpulse","F&B Graphs"],"summary":"Spend vs Budget over time","operationId":"spend_vs_budget_api_fb_graphs_spend_vs_budget_get","parameters":[{"name":"businessId","in":"query","required":true,"schema":{"type":"string","title":"Businessid"}},{"name":"months","in":"query","required":false,"schema":{"type":"integer","maximum":24,"minimum":1,"default":6,"title":"Months"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/invoices":{"get":{"tags":["cbpulse","F&B Invoices"],"summary":"List pending F&B invoices for the caller's businesses","description":"The review lane: pending invoices tagged fb_food / fb_drink / fb_other, scoped to the F&B user's businesses (executives see all). Newest invoice date first. This is what the cbloom F&B app renders instead of the full 1,294-row recon pile.","operationId":"list_fb_invoices_api_fb_invoices_get","parameters":[{"name":"businessId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Businessid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__FBInvoiceSummary"},"title":"Response List Fb Invoices Api Fb Invoices Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/invoices/recategorize":{"post":{"tags":["cbpulse","F&B Invoices"],"summary":"Recategorize the pending 'general' pile against the current vendor list","description":"cbpulse#500/#332 — one-time (re-runnable) backfill. Re-runs the F&B categorizer over pending invoices currently tagged 'general' and UPGRADES any that now match a curated vendor (incl. aliases) into the F&B lane. Upgrade-only: never downgrades an already-F&B invoice, so it can't yank anything out of the lane. dry_run=true by default — returns the plan + a sample without writing. Run it after adding vendors via PATCH/POST /api/fb/vendors. Operator/executive only.","operationId":"recategorize_general_api_fb_invoices_recategorize_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Dry Run"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20000,"default":5000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RecategorizeReport"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/invoices/unmapped-vendors":{"get":{"tags":["cbpulse","F&B Invoices"],"summary":"Rank pending 'general' invoice vendors NOT yet in the F&B list","description":"cbpulse#332 — CJ's curation worklist, in-app. Buckets pending 'general' invoices by normalized vendor name, keeps only those that still don't match any curated vendor, and ranks by frequency so the highest-volume missing vendors surface first. Add the real F&B ones via POST /api/fb/vendors, then POST /recategorize to pull their invoices in.","operationId":"unmapped_vendors_api_fb_invoices_unmapped_vendors_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__UnmappedVendor"},"title":"Response Unmapped Vendors Api Fb Invoices Unmapped Vendors Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/invoices/{invoice_id}":{"patch":{"tags":["cbpulse","F&B Invoices"],"summary":"Correct a mis-extracted / mis-routed F&B invoice","description":"Fix vendor, amount, owning business, or category on a pending invoice. Setting purchase_category to 'general' removes it from the F&B lane (prefer POST /not-fb for that intent).","operationId":"correct_fb_invoice_api_fb_invoices__invoice_id__patch","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBInvoiceCorrect"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBInvoiceSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"get":{"tags":["cbpulse","F&B Invoices"],"summary":"Get a single F&B invoice by id (detail: line items + source doc)","description":"Detail fetch for the cbloom F&B invoice queue's per-invoice view (cbpulse#510/#513). Topline fields PLUS the parsed line items, a signed URL to the source document (+ page images), and OCR confidence — so a manager can verify the extraction against the actual invoice. Same business scoping as the list (executives see all, others only their businesses); 404 if not found or out of scope. Read-only. Source-doc signing is best-effort — a cbfiles hiccup yields null URLs, never a 500.","operationId":"get_fb_invoice_api_fb_invoices__invoice_id__get","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBInvoiceDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/invoices/{invoice_id}/approve":{"post":{"tags":["cbpulse","F&B Invoices"],"summary":"Approve an F&B invoice — creates and links an fb_order","description":"Approving a pending F&B invoice creates an fb_order (the spend record) and links it via invoices.fb_order_id, so the amount shows up against the F&B dashboard + budget. 400 if not pending or not an F&B invoice.","operationId":"approve_fb_invoice_api_fb_invoices__invoice_id__approve_post","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Book the order even when another copy of this purchase has already been approved (cbpulse#585). Use only after confirming the two are genuinely separate purchases.","default":false,"title":"Force"},"description":"Book the order even when another copy of this purchase has already been approved (cbpulse#585). Use only after confirming the two are genuinely separate purchases."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBApproveResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/invoices/{invoice_id}/not-fb":{"post":{"tags":["cbpulse","F&B Invoices"],"summary":"Mark an invoice 'not F&B' — drops it from the lane without rejecting","description":"Flips purchase_category back to 'general' so it leaves Luke's F&B lane but remains a valid pending invoice in the main recon app (NOT a rejection). Use when categorization over-tagged a non-F&B vendor.","operationId":"mark_not_fb_api_fb_invoices__invoice_id__not_fb_post","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBInvoiceSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/invoices/{invoice_id}/reject":{"post":{"tags":["cbpulse","F&B Invoices"],"summary":"Reject a pending F&B invoice with a reason","operationId":"reject_fb_invoice_api_fb_invoices__invoice_id__reject_post","parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBInvoiceReject"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBInvoiceSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/orders":{"get":{"tags":["cbpulse","F&B Orders"],"summary":"List orders","operationId":"list_orders_api_fb_orders_get","parameters":[{"name":"businessId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Businessid"}},{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Month"}},{"name":"vendor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__OrderSummaryResponse"},"title":"Response List Orders Api Fb Orders Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","F&B Orders"],"summary":"Create order","operationId":"create_order_api_fb_orders_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__fb_orders__CreateOrderRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OrderCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/orders/{order_id}":{"get":{"tags":["cbpulse","F&B Orders"],"summary":"Get order","operationId":"get_order_api_fb_orders__order_id__get","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OrderDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"put":{"tags":["cbpulse","F&B Orders"],"summary":"Update order","operationId":"update_order_api_fb_orders__order_id__put","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UpdateOrderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OrderDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"delete":{"tags":["cbpulse","F&B Orders"],"summary":"Void order","operationId":"delete_order_api_fb_orders__order_id__delete","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OrderDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/statements":{"get":{"tags":["cbpulse","F&B Statements"],"summary":"F&B statements queue","operationId":"list_fb_statements_api_fb_statements_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"pending","title":"Status"}},{"name":"businessId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Businessid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__FBStatementSummary"},"title":"Response List Fb Statements Api Fb Statements Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/statements/{statement_id}":{"get":{"tags":["cbpulse","F&B Statements"],"summary":"Get Fb Statement","operationId":"get_fb_statement_api_fb_statements__statement_id__get","parameters":[{"name":"statement_id","in":"path","required":true,"schema":{"type":"string","title":"Statement Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBStatementDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/statements/{statement_id}/approve":{"post":{"tags":["cbpulse","F&B Statements"],"summary":"Approve a statement — books one fb_order per referenced invoice","operationId":"approve_fb_statement_api_fb_statements__statement_id__approve_post","parameters":[{"name":"statement_id","in":"path","required":true,"schema":{"type":"string","title":"Statement Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FBStatementApproveResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/statements/{statement_id}/reject":{"post":{"tags":["cbpulse","F&B Statements"],"summary":"Reject a statement; voids any booked orders","operationId":"reject_fb_statement_api_fb_statements__statement_id__reject_post","parameters":[{"name":"statement_id","in":"path","required":true,"schema":{"type":"string","title":"Statement Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__fb_statements__RejectBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/vendors":{"get":{"tags":["cbpulse","F&B Reference"],"summary":"List F&B vendors","operationId":"list_vendors_api_fb_vendors_get","parameters":[{"name":"businessId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Businessid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__VendorResponse"},"title":"Response List Vendors Api Fb Vendors Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","F&B Reference"],"summary":"Add an F&B vendor (cbpulse#332 — widen categorization coverage)","description":"Create a curated F&B vendor so invoices from it auto-tag into Luke's lane. category is food / drink / other; aliases are alternate printed names the categorizer should also match. Operator/executive only.","operationId":"create_vendor_api_fb_vendors_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__VendorCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__VendorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/fb/vendors/{vendor_id}":{"patch":{"tags":["cbpulse","F&B Reference"],"summary":"Edit / deactivate an F&B vendor or its aliases","description":"Update name, category, aliases, or active flag. Setting active=false removes the vendor from categorization without deleting history. Operator/executive only.","operationId":"update_vendor_api_fb_vendors__vendor_id__patch","parameters":[{"name":"vendor_id","in":"path","required":true,"schema":{"type":"integer","title":"Vendor Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__VendorUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__VendorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/finance/deadlines":{"get":{"tags":["cbpulse","Finance"],"summary":"Get upcoming deadlines","description":"Active financial deadlines grouped by urgency: overdue, this week, this month.","operationId":"deadlines_api_finance_deadlines_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days_ahead","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Look-ahead window in days","default":30,"title":"Days Ahead"},"description":"Look-ahead window in days"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DeadlinesResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Finance"],"summary":"Create financial deadline","description":"Create a new recurring or one-time financial deadline for a business.","operationId":"create_deadline_api_finance_deadlines_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DeadlineCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DeadlineItem"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Invalid category or recurrence type","content":{"application/json":{"example":{"detail":"category must be one of: edc, insurance, license, reporting, tax, utility"}}}}}}},"/cbpulse/api/finance/deadlines/{deadline_id}/complete":{"post":{"tags":["cbpulse","Finance"],"summary":"Mark deadline completed","description":"Mark a deadline as completed. Recurring deadlines auto-advance to next due date.","operationId":"complete_deadline_api_finance_deadlines__deadline_id__complete_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"deadline_id","in":"path","required":true,"schema":{"type":"string","title":"Deadline Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Deadline not found","content":{"application/json":{"example":{"detail":"Deadline not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/finance/edc/obligations":{"get":{"tags":["cbpulse","Finance"],"summary":"List EDC compliance obligations","description":"Returns all active EDC deadlines for a business with the status of their latest generated report (draft, approved, or null if none).","operationId":"edc_obligations_api_finance_edc_obligations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":true,"schema":{"type":"string","description":"Business ID","title":"Business Id"},"description":"Business ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EdcObligationsResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/finance/edc/obligations/{deadline_id}/approve":{"post":{"tags":["cbpulse","Finance"],"summary":"Approve EDC report","description":"Approves the latest draft EDC report for the given deadline. Optionally accepts manual_data to merge before approval. Advances the linked deadline to its next due date.","operationId":"edc_approve_api_finance_edc_obligations__deadline_id__approve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"deadline_id","in":"path","required":true,"schema":{"type":"string","title":"Deadline Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EdcApproveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"No draft report found","content":{"application/json":{"example":{"detail":"No draft report found for this deadline"}}}},"422":{"description":"Approval validation error","content":{"application/json":{"example":{"detail":"Report is already approved"}}}}}}},"/cbpulse/api/finance/edc/obligations/{deadline_id}/draft":{"get":{"tags":["cbpulse","Finance"],"summary":"Generate EDC draft report","description":"Auto-generates a draft EDC report for the given deadline, pre-filled from shift and financial data. Idempotent — re-calling updates the draft.","operationId":"edc_draft_api_finance_edc_obligations__deadline_id__draft_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"deadline_id","in":"path","required":true,"schema":{"type":"string","title":"Deadline Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EdcDraftReport"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Invalid deadline configuration","content":{"application/json":{"example":{"detail":"Deadline is not an EDC obligation"}}}}}}},"/cbpulse/api/finance/exceptions":{"get":{"tags":["cbpulse","Finance"],"summary":"Get finance exceptions","description":"Consolidated feed of all finance items requiring attention: overdue invoices, upcoming due dates, overdue deadlines, and pending approvals.","operationId":"finance_exceptions_api_finance_exceptions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__FinanceException"},"type":"array","title":"Response Finance Exceptions Api Finance Exceptions Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/finance/payables":{"get":{"tags":["cbpulse","Finance"],"summary":"Get payables queue","description":"Pending and approved invoices with due dates, bucketed into overdue, due soon (≤7 days), and future. Includes totals and counts.","operationId":"payables_api_finance_payables_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PayablesResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/finance/pnl":{"get":{"tags":["cbpulse","Finance"],"summary":"Get P&L statement","description":"Rolling profit & loss statement. Provide business_id for a single business or omit for portfolio-wide aggregation.","operationId":"pnl_api_finance_pnl_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"Start date (YYYY-MM-DD)","title":"Start Date"},"description":"Start date (YYYY-MM-DD)"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"End date (YYYY-MM-DD)","title":"End Date"},"description":"End date (YYYY-MM-DD)"},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single business ID (omit for portfolio)","title":"Business Id"},"description":"Single business ID (omit for portfolio)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/finance/variances":{"get":{"tags":["cbpulse","Finance"],"summary":"Get variance report","description":"Per-business variance report comparing today's metrics against 30-day rolling averages. Flags metrics that deviate beyond configured thresholds.","operationId":"variances_api_finance_variances_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"date","in":"query","required":false,"schema":{"type":"string","description":"Report date (YYYY-MM-DD). Defaults to today.","title":"Date"},"description":"Report date (YYYY-MM-DD). Defaults to today."},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single business by ID","title":"Business Id"},"description":"Filter to a single business by ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__VarianceReport"},"title":"Response Variances Api Finance Variances Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/financials/business/{business_id}":{"get":{"tags":["cbpulse","Financials"],"summary":"Get latest business financials","description":"Return the most recent financial snapshot for a specific business. Includes revenue breakdown, cost metrics, occupancy KPIs, and health score. RLS-enforced.","operationId":"get_business_financials_api_financials_business__business_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FinancialsResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"404":{"description":"No financials found","content":{"application/json":{"example":{"detail":"No financials found for this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/financials/business/{business_id}/history":{"get":{"tags":["cbpulse","Financials"],"summary":"Get business financial history","description":"Return financial history for a business, ordered by date descending. Supports date range filtering and configurable limit (max 90 records). RLS-enforced.","operationId":"get_business_financials_history_api_financials_business__business_id__history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":90,"default":30,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__FinancialsResponse"},"title":"Response Get Business Financials History Api Financials Business  Business Id  History Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/financials/business/{business_id}/record":{"post":{"tags":["cbpulse","Financials"],"summary":"Record manual financials","description":"Manually upsert a financial record for a business and reporting date. Automatically computes the business health score after recording. Requires admin+ role.","operationId":"record_financials_api_financials_business__business_id__record_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FinancialsRecordRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FinancialsResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Business not found","content":{"application/json":{"example":{"detail":"Business not found"}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","report_date"],"msg":"Field required","type":"missing"}]}}}}}}},"/cbpulse/api/financials/business/{business_id}/regenerate":{"post":{"tags":["cbpulse","Financials"],"summary":"Regenerate financials from integration data","description":"Re-aggregate a business's daily financials from raw integration data (Clover transactions + Homebase shifts + hotel performance). Specify a single date or a date range. Useful when sync issues caused an incorrect daily row. Requires admin+ role.","operationId":"regenerate_financials_api_financials_business__business_id__regenerate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single date to regenerate (YYYY-MM-DD)","title":"Date"},"description":"Single date to regenerate (YYYY-MM-DD)"},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date for range regeneration","title":"Start"},"description":"Start date for range regeneration"},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date for range regeneration","title":"End"},"description":"End date for range regeneration"},{"name":"preserve_enrichment","in":"query","required":false,"schema":{"type":"boolean","description":"When true, run the full daily-tick sequence per date (aggregate → rollups → commissions → health) instead of aggregate-only. Re-derives rollup + commission enrichment so a backfill (e.g. the cbpulse#501 AST re-bucketing) doesn't strip commission_revenue/expense from historical rows. Safe because apply_commissions/run_rollups derive from data.","default":false,"title":"Preserve Enrichment"},"description":"When true, run the full daily-tick sequence per date (aggregate → rollups → commissions → health) instead of aggregate-only. Re-derives rollup + commission enrichment so a backfill (e.g. the cbpulse#501 AST re-bucketing) doesn't strip commission_revenue/expense from historical rows. Safe because apply_commissions/run_rollups derive from data."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__FinancialsResponse"},"title":"Response Regenerate Financials Api Financials Business  Business Id  Regenerate Post"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Business not found","content":{"application/json":{"example":{"detail":"Business not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/financials/portfolio":{"get":{"tags":["cbpulse","Financials"],"summary":"Get portfolio financial overview","description":"Return aggregated financials across all active businesses: total revenue, labor cost, COGS, and average health score. Includes per-business latest snapshots. Requires admin+ role.","operationId":"portfolio_overview_api_financials_portfolio_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PortfolioResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/financials/reconciliation":{"get":{"tags":["cbpulse","Financials"],"summary":"Rows whose financial invariants are broken (cbpulse#576 Phase 4)","description":"Every `business_financials` row that fails a reconciliation invariant. **Reports, never repairs** — a guard that silently corrected would hide the writer it exists to expose.\n\nThe damage this catches has happened three times (#567, #564, and the 352-row / $5.3M accumulation repaired in #576), always the same shape: a writer mutates a component after `upsert_financials` has derived the dependent columns, and nothing notices.\n\nInvariants, each of which held with ZERO violations across all 4,495 daily rows after the #576 repair — so a hit here is a real regression, not pre-existing noise:\n\n- `derived` — `gross_profit == total_revenue - labor_cost - cogs`. Was violated on 350 rows before the repair.\n- `commission_source` — where `commission_expense > 0`, `total_revenue == sum(components) - commission_expense`. Read 3x on The Saint's June block.\n- `commission_dest` — on a pure commission ledger row (commission_revenue > 0, every component zero), `total_revenue == commission_revenue`. Read **73x** on CRYAJY, worth $5,178,405.58 of phantom revenue.\n\nDeliberately NOT asserted: `total_revenue == sum(components)` on ordinary rows. It looks like the obvious fourth invariant and is false in production — 57 daily rows violate it because `aggregate_from_integrations` clamps `other_revenue` to `max(total - fb - spa, 0)`. Asserting it would fire on day one and train everyone to ignore this endpoint.","operationId":"reconciliation_drift_api_financials_reconciliation_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional business filter","title":"Business Id"},"description":"Optional business filter"},{"name":"report_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional YYYY-MM-DD filter","title":"Report Date"},"description":"Optional YYYY-MM-DD filter"},{"name":"period","in":"query","required":false,"schema":{"type":"string","description":"Period to check","default":"daily","title":"Period"},"description":"Period to check"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/guests":{"get":{"tags":["cbpulse","Guests"],"summary":"List guest profiles","description":"Return guest profiles visible to the current user with optional filters for business_id, search (name/email), spend_tier, tag, and consent_email. Results are RLS-filtered and ordered by updated_at descending. Supports limit/offset pagination.","operationId":"list_guests_api_guests_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search name or email (LIKE)","title":"Search"},"description":"Search name or email (LIKE)"},{"name":"spend_tier","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Spend Tier"}},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tag (must contain)","title":"Tag"},"description":"Filter by tag (must contain)"},{"name":"consent_email","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Consent Email"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GuestListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to requested business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/guests/segments":{"get":{"tags":["cbpulse","Guests"],"summary":"List guest segments","description":"Return all active guest segments visible to the current user. Optionally filter by business_id. Results are RLS-filtered.","operationId":"list_segments_api_guests_segments_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__SegmentResponse"},"title":"Response List Segments Api Guests Segments Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Guests"],"summary":"Create a guest segment","description":"Create a new guest segment with filter criteria. The segment is immediately evaluated to compute the estimated_size. Requires manager+ role.","operationId":"create_segment_api_guests_segments_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CreateSegmentRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SegmentResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"409":{"description":"Segment name already exists for this business","content":{"application/json":{"example":{"detail":"Segment 'High-Value Returners' already exists"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/guests/segments/{segment_id}/sync-mailchimp":{"post":{"tags":["cbpulse","Guests"],"summary":"Sync segment to Mailchimp","description":"Push the guest segment to a Mailchimp audience list. This endpoint is a stub — Mailchimp integration is tracked in issue #48.","operationId":"sync_to_mailchimp_api_guests_segments__segment_id__sync_mailchimp_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"segment_id","in":"path","required":true,"schema":{"type":"string","title":"Segment Id"}}],"responses":{"501":{"description":"Not yet implemented","content":{"application/json":{"schema":{},"example":{"detail":"Mailchimp integration not yet available"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/guests/{guest_id}":{"get":{"tags":["cbpulse","Guests"],"summary":"Get guest profile by ID","description":"Retrieve a single guest profile by its ID. Returns 404 if not found, 403 if the user does not have access to the guest's business.","operationId":"get_guest_api_guests__guest_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"guest_id","in":"path","required":true,"schema":{"type":"string","title":"Guest Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GuestProfileResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this guest's business","content":{"application/json":{"example":{"detail":"No access to this guest"}}}},"404":{"description":"Guest not found","content":{"application/json":{"example":{"detail":"Guest not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/guests/{guest_id}/consent":{"patch":{"tags":["cbpulse","Guests"],"summary":"Update guest consent","description":"Update marketing consent flags on a guest profile. Only provided fields are changed. Requires manager+ role.","operationId":"update_consent_api_guests__guest_id__consent_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"guest_id","in":"path","required":true,"schema":{"type":"string","title":"Guest Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UpdateConsentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GuestProfileResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to guest","content":{"application/json":{"example":{"detail":"No access to this guest"}}}},"404":{"description":"Guest not found","content":{"application/json":{"example":{"detail":"Guest not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/guests/{guest_id}/tags":{"patch":{"tags":["cbpulse","Guests"],"summary":"Update guest tags","description":"Add or remove tags on a guest profile. Existing tags not mentioned in 'remove' are preserved. Requires manager+ role.","operationId":"update_tags_api_guests__guest_id__tags_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"guest_id","in":"path","required":true,"schema":{"type":"string","title":"Guest Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UpdateTagsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GuestProfileResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to guest","content":{"application/json":{"example":{"detail":"No access to this guest"}}}},"404":{"description":"Guest not found","content":{"application/json":{"example":{"detail":"Guest not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/agreements/{agreement_id}":{"patch":{"tags":["cbpulse","HR","HR"],"summary":"Partially update an agreement (manager only)","operationId":"update_agreement_api_hr_agreements__agreement_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AgreementUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EmployeeAgreement"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"delete":{"tags":["cbpulse","HR","HR"],"summary":"Delete an agreement + its documents (manager only)","description":"Remove an agreement (cbpulse#494) — for a bogus/duplicate/wrong-employee row\nthat PATCH can't fix. Hard-deletes the row + every attached document, with a\nbest-effort cbfiles object cleanup; the deletion is audit-logged. 404/403 via\nthe shared business-scope loader.","operationId":"delete_agreement_api_hr_agreements__agreement_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/agreements/{agreement_id}/documents":{"post":{"tags":["cbpulse","HR","HR"],"summary":"Attach an original/executed document; replaces the same role","operationId":"attach_agreement_document_api_hr_agreements__agreement_id__documents_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbpulse__Body_attach_agreement_document_api_hr_agreements__agreement_id__documents_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AgreementDocument"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"get":{"tags":["cbpulse","HR","HR"],"summary":"List an agreement's documents, newest first, each signed","operationId":"list_agreement_documents_api_hr_agreements__agreement_id__documents_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__AgreementDocument"},"title":"Response List Agreement Documents Api Hr Agreements  Agreement Id  Documents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/agreements/{agreement_id}/documents/{doc_id}":{"delete":{"tags":["cbpulse","HR","HR"],"summary":"Detach an agreement document; row + best-effort cbfiles delete","operationId":"delete_agreement_document_api_hr_agreements__agreement_id__documents__doc_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}},{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/alerts":{"get":{"tags":["cbpulse","HR"],"summary":"List HR lifecycle alerts","description":"Surfaces Homebase ↔ cbpulse divergence alerts queued by the employee-sync consolidator (cbpulse#405): ``rehire_candidate`` (Homebase active vs cbpulse terminated) and ``informal_departure`` (sync marked someone inactive but no formal termination on file). Default returns ``status='open'`` only — pass ``status=resolved`` to see the audit history.","operationId":"list_hr_alerts_api_hr_alerts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"open | confirmed | dismissed","default":"open","title":"Status"},"description":"open | confirmed | dismissed"},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/alerts/{alert_id}/resolve":{"post":{"tags":["cbpulse","HR"],"summary":"Resolve an HR lifecycle alert","description":"Marks an open alert ``confirmed`` (manager acted on it — e.g. called the #404 rehire endpoint or the #378 termination endpoint) or ``dismissed`` (manager judged Homebase wrong / no action needed). Idempotent on already-resolved alerts — returns the existing row without overwriting the resolver / timestamp.","operationId":"resolve_hr_alert_api_hr_alerts__alert_id__resolve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HRAlertResolveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HRAlertResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/dashboard":{"get":{"tags":["cbpulse","HR"],"summary":"Portfolio HR overview (CJ executive view)","description":"Aggregates across every business the caller can access. Non-admin users see only their assigned properties.","operationId":"hr_dashboard_portfolio_api_hr_dashboard_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/hr/dashboard/{business_id}":{"get":{"tags":["cbpulse","HR"],"summary":"Property HR summary (GM view)","description":"Same aggregates as the portfolio view but scoped to one business. Recent activity filtered to this business only.","operationId":"hr_dashboard_property_api_hr_dashboard__business_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/employees/{employee_id}/agreements":{"post":{"tags":["cbpulse","HR","HR"],"summary":"Create an employment agreement for an employee (manager only)","operationId":"create_agreement_api_hr_employees__employee_id__agreements_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AgreementCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EmployeeAgreement"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"get":{"tags":["cbpulse","HR","HR"],"summary":"List an employee's agreements, newest first, with signed docs","operationId":"list_agreements_api_hr_employees__employee_id__agreements_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__EmployeeAgreement"},"title":"Response List Agreements Api Hr Employees  Employee Id  Agreements Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/employees/{employee_id}/documents":{"post":{"tags":["cbpulse","HR","HR"],"summary":"Attach a general document to an employee file (manager only)","description":"Multipart (``file`` + ``category`` + optional ``label``) → cbpulse-hr bucket → row. For non-write-up docs (incident reports, medical notes, acknowledgments). category: incident_report | medical | acknowledgment | disciplinary_note | other.","operationId":"attach_employee_document_api_hr_employees__employee_id__documents_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbpulse__Body_attach_employee_document_api_hr_employees__employee_id__documents_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EmployeeDocument"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"get":{"tags":["cbpulse","HR","HR"],"summary":"List an employee's general documents, newest first, each signed","operationId":"list_employee_documents_api_hr_employees__employee_id__documents_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__EmployeeDocument"},"title":"Response List Employee Documents Api Hr Employees  Employee Id  Documents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/employees/{employee_id}/documents/{doc_id}":{"delete":{"tags":["cbpulse","HR","HR"],"summary":"Detach an employee document; row + best-effort cbfiles delete","operationId":"delete_employee_document_api_hr_employees__employee_id__documents__doc_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}},{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/handbook/ask":{"post":{"tags":["cbpulse","HR"],"summary":"Grounded handbook Q&A (cbai over hr_handbook_policies)","description":"Answer a manager's plain-English question about an org's employee handbook, grounded ONLY in that org's current handbook sections, with citations. Whole-handbook-in-context via cbai (each org is ~6 sections) — no vector DB. Returns ``covered=false`` (200) with a graceful message when the org has no handbook on file or when the question is not addressed by the handbook. Returns 502 if cbai is unreachable. Closes cbpulse#511.","operationId":"handbook_ask_api_hr_handbook_ask_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HandbookAskRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HandbookAskResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/hr/maintenance/backfill-doc-keys":{"post":{"tags":["cbpulse","HR","HR"],"summary":"Backfill legacy HR doc file_ids to full object keys (sysop; cbpulse#496)","description":"One-off repair for documents uploaded before the #496 fix. Legacy rows stored a bare-uuid file_id and rebuilt the cbpulse-hr key from content_type — which the resignation-letter mirror hard-coded to PDF, 404'ing jpeg/png letters. Sweeps the three HR doc tables, asks cbfiles what object actually exists under each bare uuid, and rewrites file_id to the full <uuid>.<ext> key (+ the real content_type). Idempotent. **dry_run=true by default** — returns the plan without writing.","operationId":"backfill_hr_doc_keys_api_hr_maintenance_backfill_doc_keys_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Dry Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DocKeyBackfillReport"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/separations/letter":{"post":{"tags":["cbpulse","HR","HR"],"summary":"Upload a resignation/separation letter to cbfiles (manager only)","description":"Standalone multipart upload — no draft separation row required. The manager uploads the letter (PDF, JPEG, or PNG, max 10 MB) first, then passes the returned ``file_id`` as ``resignation_letter_file_id`` when calling POST /api/admin/employees/{id}/terminate with separation_type='resignation'. Stored in the cbfiles HR bucket.","operationId":"upload_separation_letter_api_hr_separations_letter_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbpulse__Body_upload_separation_letter_api_hr_separations_letter_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SeparationLetterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/hr/separations/{employee_id}/documents":{"post":{"tags":["cbpulse","HR","HR"],"summary":"Attach a supporting document to a separation (manager only)","description":"Multipart (``file`` + optional ``label``) → cbpulse-hr bucket → row. Retroactive; links to the most recent termination when one exists.","operationId":"attach_separation_document_api_hr_separations__employee_id__documents_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbpulse__Body_attach_separation_document_api_hr_separations__employee_id__documents_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SeparationDocument"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"get":{"tags":["cbpulse","HR","HR"],"summary":"List a separation's documents, newest first, each with a signed url","operationId":"list_separation_documents_api_hr_separations__employee_id__documents_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__SeparationDocument"},"title":"Response List Separation Documents Api Hr Separations  Employee Id  Documents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/separations/{employee_id}/documents/{doc_id}":{"delete":{"tags":["cbpulse","HR","HR"],"summary":"Detach a document; removes the row + best-effort cbfiles delete","operationId":"delete_separation_document_api_hr_separations__employee_id__documents__doc_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"employee_id","in":"path","required":true,"schema":{"type":"string","title":"Employee Id"}},{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/status-report":{"get":{"tags":["cbpulse","HR"],"summary":"HR Queue — pending items + overdue across all entities","description":"Single call returns the 5 sections of the HR Queue:\n- overdue_signatures (writeups sent > {overdue_hours}h ago, still unsigned)\n- pending_signatures (writeups sent, awaiting signature)\n- new_onboarding (status='pending', not yet sent)\n- ot_awaiting (overtime status in draft/sent — needs manager action)\n- expiring_compliance (placeholder; cbpulse#317 wires the real query)","operationId":"status_report_api_hr_status_report_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional property scope","title":"Business Id"},"description":"Optional property scope"},{"name":"overdue_hours","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"default":48,"title":"Overdue Hours"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__StatusReport"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/trends":{"get":{"tags":["cbpulse","HR"],"summary":"Monthly team-health trend series (cbpulse#516)","description":"Per-property monthly trends for turnover, discipline, and overtime. Omit `business_id` for the portfolio (all accessible properties); pass it for a single property. Returns `months`+1 buckets, index 0 = the same calendar month `months` prior (seasonal anchor), last index = the current in-progress month. `null` = no data for that month (distinct from a real `0`).","operationId":"hr_trends_api_hr_trends_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Single property; omit for the portfolio view","title":"Business Id"},"description":"Single property; omit for the portfolio view"},{"name":"months","in":"query","required":false,"schema":{"type":"integer","maximum":36,"minimum":1,"description":"Trailing window; response has months+1 buckets","default":12,"title":"Months"},"description":"Trailing window; response has months+1 buckets"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/hr/writeup/narrate":{"post":{"tags":["cbpulse","HR"],"summary":"Generate voice-bible compliant writeup prose","description":"Takes the manager's raw notes plus the structured fields (employee_name, severity, violation_category) and returns incident_description + corrective_action ready to drop into a writeup draft. Output is third-person manager voice — no em dashes, no legal citations, no banned phrases. Validation result is included so the wizard can flag attempts that failed despite retries (caller decides whether to block the send, fall back to a template, or surface the error to the manager). Closes cbpulse#364.","operationId":"narrate_writeup_api_hr_writeup_narrate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteupNarrateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteupNarrateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/inbox":{"get":{"tags":["cbpulse","Inbox"],"summary":"List inbox conversations","description":"Paginated list of inbox conversations with optional filters for channel, status, business, and assignee. Ordered by last_message_at descending. RLS-filtered by user's business access.","operationId":"list_conversations_api_inbox_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"assigned_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ConversationResponse"},"title":"Response List Conversations Api Inbox Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/inbox/gmail/connect":{"post":{"tags":["cbpulse","Inbox"],"summary":"Initiate Gmail OAuth connection","description":"Start the Gmail OAuth flow to connect a business email account to the unified inbox. Not yet implemented.","operationId":"connect_gmail_api_inbox_gmail_connect_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"501":{"description":"Not implemented","content":{"application/json":{"example":{"detail":"Gmail integration not yet implemented"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/inbox/gmail/disconnect":{"post":{"tags":["cbpulse","Inbox"],"summary":"Disconnect Gmail account","description":"Remove the Gmail OAuth connection for a business. Not yet implemented.","operationId":"disconnect_gmail_api_inbox_gmail_disconnect_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"501":{"description":"Not implemented","content":{"application/json":{"example":{"detail":"Gmail integration not yet implemented"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/inbox/gmail/status":{"get":{"tags":["cbpulse","Inbox"],"summary":"Gmail connection status","description":"Check the health of the Gmail integration connection. Not yet implemented.","operationId":"gmail_status_api_inbox_gmail_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"501":{"description":"Not implemented","content":{"application/json":{"example":{"detail":"Gmail integration not yet implemented"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/inbox/gmail/sync":{"post":{"tags":["cbpulse","Inbox"],"summary":"Manual Gmail sync trigger","description":"Trigger a manual sync of Gmail messages into the unified inbox. Not yet implemented.","operationId":"sync_gmail_api_inbox_gmail_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"501":{"description":"Not implemented","content":{"application/json":{"example":{"detail":"Gmail integration not yet implemented"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/inbox/{conversation_id}":{"get":{"tags":["cbpulse","Inbox"],"summary":"Get conversation with messages","description":"Retrieve a single conversation with all messages eagerly loaded. Messages are sorted by created_at ascending.","operationId":"get_conversation_api_inbox__conversation_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ConversationDetailResponse"}}}},"403":{"description":"No access to this conversation","content":{"application/json":{"example":{"detail":"No access to this conversation"}}}},"404":{"description":"Conversation not found","content":{"application/json":{"example":{"detail":"Conversation not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/inbox/{conversation_id}/ai-suggestion":{"post":{"tags":["cbpulse","Inbox"],"summary":"Get AI reply suggestion","description":"Generate an AI-powered reply suggestion for the conversation. Uses knowledge base articles and brand voice guidelines as context. Falls back gracefully if CBAI is unavailable. Requires manager+ role.","operationId":"get_ai_suggestion_api_inbox__conversation_id__ai_suggestion_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AISuggestionResponse"}}}},"404":{"description":"Conversation not found","content":{"application/json":{"example":{"detail":"Conversation not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/inbox/{conversation_id}/assign":{"post":{"tags":["cbpulse","Inbox"],"summary":"Assign conversation to agent","description":"Assign a conversation to a staff member. Cannot assign closed conversations. Creates an audit log entry. Requires manager+ role.","operationId":"assign_conversation_api_inbox__conversation_id__assign_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AssignAgentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ConversationResponse"}}}},"400":{"description":"Cannot assign closed conversation","content":{"application/json":{"example":{"detail":"Cannot assign a closed conversation"}}}},"404":{"description":"Conversation not found","content":{"application/json":{"example":{"detail":"Conversation not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/inbox/{conversation_id}/reply":{"post":{"tags":["cbpulse","Inbox"],"summary":"Reply to a conversation","description":"Create an outbound message in the conversation. Updates conversation counters and last_message_at. Channel dispatch is stubbed for future implementation. Requires manager+ role.","operationId":"reply_to_conversation_api_inbox__conversation_id__reply_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ReplyRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MessageResponse"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Requires at least manager role"}}}},"404":{"description":"Conversation not found","content":{"application/json":{"example":{"detail":"Conversation not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/inbox/{conversation_id}/status":{"post":{"tags":["cbpulse","Inbox"],"summary":"Update conversation status","description":"Update conversation status to open, snoozed, or closed. Snoozed status requires snoozed_until datetime. Opening clears snoozed_until. Creates an audit log entry. Requires manager+ role.","operationId":"update_status_api_inbox__conversation_id__status_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UpdateStatusRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ConversationResponse"}}}},"400":{"description":"Invalid status transition","content":{"application/json":{"example":{"detail":"snoozed_until is required when status is snoozed"}}}},"404":{"description":"Conversation not found","content":{"application/json":{"example":{"detail":"Conversation not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/incidents":{"get":{"tags":["cbpulse","HR"],"summary":"List incidents","description":"Paginated incident list with filters. RLS-filtered by business access.","operationId":"list_incidents_api_incidents_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"employee_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employee Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__IncidentResponse"},"title":"Response List Incidents Api Incidents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","HR"],"summary":"Create incident","description":"Log a new HR incident. Requires manager+ role.","operationId":"create_incident_api_incidents_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CreateIncidentRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__IncidentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/incidents/{incident_id}":{"patch":{"tags":["cbpulse","HR"],"summary":"Update incident","description":"Update incident status, severity, statement, or resolution. Requires manager+ role.","operationId":"update_incident_api_incidents__incident_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UpdateIncidentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__IncidentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/incidents/{incident_id}/escalate":{"post":{"tags":["cbpulse","HR"],"summary":"Escalate an incident into a draft write-up","description":"Creates a writeup linked to the incident (incident_id FK) pre-filled from the incident record. Returns the new writeup so the caller can open the wizard at step 2 (compliance preview) without re-gathering details.","operationId":"escalate_incident_api_incidents__incident_id__escalate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"incident_id","in":"path","required":true,"schema":{"type":"string","title":"Incident Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EscalateIncidentRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteUpResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/integrations/status":{"get":{"tags":["cbpulse","Integrations"],"summary":"Get integration status","description":"Returns the status of all configured external integrations across businesses.","operationId":"integration_status_api_integrations_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__IntegrationStatusResponse"},"type":"array","title":"Response Integration Status Api Integrations Status Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/integrations/{source}/sync":{"post":{"tags":["cbpulse","Integrations"],"summary":"Trigger manual sync","description":"Manually trigger a sync for the specified integration source.","operationId":"manual_sync_api_integrations__source__sync_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source","in":"path","required":true,"schema":{"type":"string","title":"Source"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SyncTriggerResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Sysop role required","content":{"application/json":{"example":{"detail":"Sysop role required"}}}},"404":{"description":"Unknown integration source","content":{"application/json":{"example":{"detail":"Unknown integration source: invalid_source"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices":{"get":{"tags":["cbpulse","Invoices"],"summary":"List invoices","description":"Return invoices with optional filters for status, business, vendor, and date range. Supports pagination via limit/offset. RLS-filtered for admin+ role.","operationId":"list_invoices_api_invoices_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"vendor_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Name"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__InvoiceResponse"},"title":"Response List Invoices Api Invoices Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Invoices"],"summary":"Create an invoice","description":"Manually create an invoice record for a vendor. Sets status to 'pending' for approval. Creates an audit log entry. Requires admin+ role.","operationId":"create_invoice_api_invoices_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CreateInvoiceRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InvoiceResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","vendor_name"],"msg":"Field required","type":"missing"}]}}}}}}},"/cbpulse/api/invoices/attribution-drift":{"get":{"tags":["cbpulse","Invoice Audit"],"summary":"Invoices the silent first-active fallback misfiled (cbpulse#565)","description":"**Reports, never moves.** Correcting these rows relocates real spend between property dashboards and historical F&B budget pacing, so the numbers get read before anything changes. Same posture as cbradio's `GET /stations/network-drift`.\n\n`_resolve_business_id` fell back to the first active business whenever the recipient-domain hint failed to resolve, so an unattributable document was not left unattributed — it was filed against an arbitrary *real* property. Fixed forward in #563; these rows predate it.\n\nTwo rules this endpoint applies, both taken from the pipeline rather than invented here:\n\n- **A resolving recipient domain is authoritative** and the extractor is not. So the 869 rows where extraction says `lovango` and the row is stored as `maho-crossroads` are **not** move candidates: 868 of them resolve via `lovecityfresh.com`, and the stored value is the stronger signal.\n- **The historical fallback target is The Fred**, not today's first-active. #563 added `ORDER BY slug`, which moves the live answer to `cryajy`, so re-deriving with today's rule would classify zero historical rows.\n\n**Read `spend_*` and not the raw totals.** 83% of the headline dollars are `rejected` invoices, which reach no dashboard and no budget — and $1,048,827 of that is one Alfreds Landscaping invoice duplicated 146 times. Groups are ranked by real money for the same reason; by raw dollars the largest group is 94% rejected paperwork.","operationId":"attribution_drift_api_invoices_attribution_drift_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"include_moves","in":"query","required":false,"schema":{"type":"boolean","description":"Include the per-invoice proposed moves (large)","default":false,"title":"Include Moves"},"description":"Include the per-invoice proposed moves (large)"},{"name":"spend_only","in":"query","required":false,"schema":{"type":"boolean","description":"Exclude rejected invoices from the monthly deltas","default":true,"title":"Spend Only"},"description":"Exclude rejected invoices from the monthly deltas"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/audit":{"get":{"tags":["cbpulse","Invoice Audit"],"summary":"Paginated invoice list for audit","description":"Full invoice list with pagination metadata, source data, and match info. Filterable by status, business, vendor, confidence.","operationId":"list_invoices_audit_api_invoices_audit_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"vendor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor"}},{"name":"min_confidence","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":1,"minimum":0},{"type":"null"}],"title":"Min Confidence"}},{"name":"max_confidence","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":1,"minimum":0},{"type":"null"}],"title":"Max Confidence"}},{"name":"doc_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"invoice, order, receipt, or unknown","title":"Doc Type"},"description":"invoice, order, receipt, or unknown"},{"name":"purchase_category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"fb_food, fb_drink, fb_other, fb, or general","title":"Purchase Category"},"description":"fb_food, fb_drink, fb_other, fb, or general"},{"name":"has_source","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by has/missing source extraction","title":"Has Source"},"description":"Filter by has/missing source extraction"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PaginatedInvoices"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/audit/{invoice_id}":{"get":{"tags":["cbpulse","Invoice Audit"],"summary":"Invoice audit detail with source data","description":"Full invoice with OCR text preview, parsed extraction data, email metadata, and match info.","operationId":"get_invoice_audit_api_invoices_audit__invoice_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InvoiceAuditDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"patch":{"tags":["cbpulse","Invoice Audit"],"summary":"Correct invoice fields","description":"Staff can adjust vendor name, amount, dates, business assignment, or status. All corrections are audit-logged.\n\n**Reassigning `business_id` is scoped (cbpulse#563 item 5).** That field moves an invoice's spend between property P&Ls, and this route previously allowed it behind a bare authenticated user — no role, no business scope, no check that the target was a real business. It is permitted when the caller can see **either** the current or the target business, which is the rule that lets a single-property operator both pull a misfiled invoice toward themselves and push a stray off their own books. Requiring both sides would refuse nearly every move in #565's re-attribution review.","operationId":"correct_invoice_api_invoices_audit__invoice_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InvoiceCorrectionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InvoiceAuditDetail"}}}},"403":{"description":"Not permitted to reassign between these businesses"},"404":{"description":"Invoice not found, or target business does not exist"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/audit/{invoice_id}/reprocess":{"post":{"tags":["cbpulse","Invoice Audit"],"summary":"Re-run extraction on a record's stored source pages","description":"Development / test escape hatch: re-feeds the record's stored page images back through cbingest (classify + schema extraction) and overwrites the extracted fields in place — same record id, no duplicate. Resets status to 'pending'. Requires stored source_images.","operationId":"reprocess_invoice_api_invoices_audit__invoice_id__reprocess_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InvoiceAuditDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/audit/{invoice_id}/source-images":{"get":{"tags":["cbpulse","Invoice Audit"],"summary":"Signed URLs for invoice source-page images","description":"Returns short-lived signed cbfiles URLs for each normalized page image captured at ingest time. `record_type` defaults to 'invoice' but may be 'statement' or 'receipt' to resolve records persisted to those tables.","operationId":"get_invoice_source_images_api_invoices_audit__invoice_id__source_images_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}},{"name":"record_type","in":"query","required":false,"schema":{"type":"string","pattern":"^(invoice|statement|receipt|order|unknown)$","default":"invoice","title":"Record Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SourceImagesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/check-alerts":{"post":{"tags":["cbpulse","Invoice Jobs"],"summary":"Check invoice and budget alerts","description":"Check budget thresholds, overdue payables, unmatched invoice backlog. Called by cbcron every 30 min at :06.","operationId":"check_invoice_alerts_api_invoices_check_alerts_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__JobResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/dedup-coverage":{"get":{"tags":["cbpulse","Invoice Audit"],"summary":"Which dedup keys are populated, and the residual duplicate exposure","description":"cbpulse#579 fix 3 — a missing dedup key is a triage signal, not something to silently trust the next key about.\n\n`no_usable_key` counts rows where none of `content_hash`, `message_id`, `order_number` or `invoice_number` is present, so no key can fire and a second arrival WILL create a second row.\n\n`residual_*` counts rows that already share `(business_id, invoice_number, amount)`. The #579 key stops NEW duplicates; it cannot retroactively merge rows that were ingested before it, and which copy to keep is a judgement call the issue deliberately leaves open. `residual_excess_amount` is what approving every extra copy would over-book into `business_financials.cogs`.\n\nRead-only. Reports, never merges.","operationId":"dedup_coverage_api_invoices_dedup_coverage_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","description":"invoice status to measure","default":"pending","title":"Status"},"description":"invoice status to measure"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DedupKeyCoverage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/duplicate-orders":{"get":{"tags":["cbpulse","Invoice Audit"],"summary":"One purchase ingested as several invoices (cbpulse#563 follow-up)","description":"Groups invoices that look like the same purchase arriving more than once. Dedup in the ingest path keys on `content_hash`, then `message_id`, then `(business_id, order_number)`, then `(business_id, invoice_number, amount)`. The last of those replaced `(source_email, invoice_number)` in cbpulse#579: the sender address is a property of the delivery channel, not of the document, so the same invoice sent direct and forwarded via QuickBooks had two different keys and could never dedup. The first two keys remain blind to a forward chain — a forwarded order is a different email at every hop, and an `email-body.pdf` render produces different bytes (so a different hash) on each arrival.\n\nGrouped on `(business_id, invoice_date, amount)` — deliberately NOT vendor, since the same order has been extracted as `Merchants Market`, `Merchant's Market` and `THE SAINT RESORT` (the customer read as the seller).\n\n**Reports, never merges.** That key alone collides on 15% of invoices and 19 of 88 colliding groups have genuinely different line items — separate purchases a blind merge would destroy. `verdict` is therefore:\n\n- `identical_lines` — same basket, same total, same day. Confident duplicate; `excess_amount` is what approving every copy would over-book.\n- `differing_lines` — coincident total, different basket. **Not** a duplicate; listed so the coincidence is visible rather than suspicious.\n- `undecidable` — neither the basket nor the vendor invoice number can rule.\n\n`decided_by` says WHICH field ruled — `line_items`, `invoice_number`, or null when still undecidable — so a duplicate call can be inspected rather than trusted.\n\n**On the invoice-number tie-breaker (cbpulse#570).** The undecidable bucket was assumed to be a line-item extraction backlog. Measured, it is not: 22 of the 32 live undecidable groups consist wholly of SERVICE invoices — pest control, maintenance, advertising, SaaS (63 `general` vs 10 `fb_drink`) — and a monthly pest-control invoice has no basket to extract, so no amount of re-extraction could ever decide them. The vendor invoice number does, and it was already on the column. It is consulted ONLY where the basket could not rule, so it can never move a basket-decided verdict. Matching is containment-tolerant (`W-4203313/W-4202969` vs `W-4203313` is one document read twice, not two invoices) and abstains rather than guess, because wrongly declaring 'not a duplicate' retires a group from review and is the only direction that costs money.\n\nRejected invoices are excluded: a rejected copy is one already dealt with.","operationId":"duplicate_orders_api_invoices_duplicate_orders_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"verdict","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: identical_lines | differing_lines | undecidable","title":"Verdict"},"description":"Filter: identical_lines | differing_lines | undecidable"},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"purchase_category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional comma-separated purchase_category filter, e.g. `fb_food,fb_drink,fb_other` (cbpulse#586). Omit for every category. The F&B Invoice Queue is this endpoint's only consumer and was being shown whole-pile duplicates -- solar, estates, trucking -- under a count an F&B approver could not reconcile with their own lane.","title":"Purchase Category"},"description":"Optional comma-separated purchase_category filter, e.g. `fb_food,fb_drink,fb_other` (cbpulse#586). Omit for every category. The F&B Invoice Queue is this endpoint's only consumer and was being shown whole-pile duplicates -- solar, estates, trucking -- under a count an F&B approver could not reconcile with their own lane."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/ingest":{"post":{"tags":["cbpulse","Invoice Jobs"],"summary":"Ingest invoices from email","description":"Poll the Invoices email folder, fetch PDF/DOCX/image attachments, send to cbingest for OCR + extraction, store as InvoiceORM. Called by cbcron every 15 min at :02.","operationId":"ingest_invoices_api_invoices_ingest_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__JobResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/pending":{"get":{"tags":["cbpulse","Invoices"],"summary":"List pending invoices","description":"Shortcut to return only invoices in 'pending' status that need review. RLS-filtered. Requires admin+ role.","operationId":"list_pending_invoices_api_invoices_pending_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__InvoiceResponse"},"title":"Response List Pending Invoices Api Invoices Pending Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/process":{"post":{"tags":["cbpulse","Invoice Jobs"],"summary":"Match and auto-approve invoices","description":"Find ingested invoices, match to F&B orders, auto-approve if rules pass. Called by cbcron every 15 min at :04.","operationId":"process_invoices_api_invoices_process_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__JobResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/stats":{"get":{"tags":["cbpulse","Invoice Audit"],"summary":"Invoice stats by status and business","description":"Dashboard overview: counts per status, totals by business, pending/approved amounts.","operationId":"invoice_stats_api_invoices_stats_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InvoiceStats"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/upload":{"post":{"tags":["cbpulse","Invoices"],"summary":"Upload a document via the centralized pipeline","operationId":"upload_document_api_invoices_upload_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UploadRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Upload Document Api Invoices Upload Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/invoices/{invoice_id}":{"get":{"tags":["cbpulse","Invoices"],"summary":"Get invoice by ID","description":"Retrieve a single invoice by its ID. Returns 404 if not found. Requires admin+ role.","operationId":"get_invoice_api_invoices__invoice_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InvoiceResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Invoice not found","content":{"application/json":{"example":{"detail":"Invoice not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/{invoice_id}/approve":{"post":{"tags":["cbpulse","Invoices"],"summary":"Approve an invoice","description":"Approve a pending invoice for payment. Records the approving user and timestamp. Returns 400 if the invoice is not in 'pending' status. Requires admin+ role.","operationId":"approve_invoice_api_invoices__invoice_id__approve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InvoiceResponse"}}}},"400":{"description":"Invalid state transition","content":{"application/json":{"example":{"detail":"Cannot approve invoice in status 'approved'"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Invoice not found","content":{"application/json":{"example":{"detail":"Invoice not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/invoices/{invoice_id}/reject":{"post":{"tags":["cbpulse","Invoices"],"summary":"Reject an invoice","description":"Reject a pending invoice with a required reason. Records the rejecting user, timestamp, and reason. Returns 400 if the invoice is not in 'pending' status. Requires admin+ role.","operationId":"reject_invoice_api_invoices__invoice_id__reject_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","title":"Invoice Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__invoices__RejectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InvoiceResponse"}}}},"400":{"description":"Invalid state transition","content":{"application/json":{"example":{"detail":"Cannot reject invoice in status 'approved'"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Invoice not found","content":{"application/json":{"example":{"detail":"Invoice not found"}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","reason"],"msg":"Field required","type":"missing"}]}}}}}}},"/cbpulse/api/jobs/hr/notifications/scan":{"post":{"tags":["cbpulse","HR Jobs"],"summary":"Scan for overdue HR events and emit notifications","description":"Called by cbcron. Scans writeups, onboarding, compliance docs, and overtime requests; emits the four HR notification event types (cbpulse#317). Idempotent via source_type/source_id dedup.","operationId":"scan_hr_notifications_api_jobs_hr_notifications_scan_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ScanResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/knowledge-base":{"get":{"tags":["cbpulse","Knowledge Base"],"summary":"List knowledge base articles","description":"Return articles with optional filters for business, category, and publish status. Supports pagination via limit/offset. RLS-filtered by user's business access. Excludes soft-deleted articles.","operationId":"list_articles_api_knowledge_base_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"is_published","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Published"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ArticleResponse"},"title":"Response List Articles Api Knowledge Base Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Knowledge Base"],"summary":"Create a knowledge base article","description":"Create a new article for a property's knowledge base. Sets created_by to the current user. Creates an audit log entry. Requires manager+ role.","operationId":"create_article_api_knowledge_base_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CreateArticleRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ArticleResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/knowledge-base/categories":{"get":{"tags":["cbpulse","Knowledge Base"],"summary":"List article categories with counts","description":"Return distinct categories with article counts, scoped by the user's business access. Excludes soft-deleted articles.","operationId":"list_categories_api_knowledge_base_categories_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__CategoryCount"},"title":"Response List Categories Api Knowledge Base Categories Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/knowledge-base/search":{"get":{"tags":["cbpulse","Knowledge Base"],"summary":"Search knowledge base articles","description":"Full-text search across article titles and bodies using SQL LIKE. Excludes soft-deleted articles. RLS-filtered.","operationId":"search_articles_api_knowledge_base_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Search query","title":"Q"},"description":"Search query"},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ArticleResponse"},"title":"Response Search Articles Api Knowledge Base Search Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/knowledge-base/{article_id}":{"get":{"tags":["cbpulse","Knowledge Base"],"summary":"Get article by ID","description":"Retrieve a single knowledge base article by its ID. Returns 404 if not found or deleted, 403 if the user lacks access to the article's business.","operationId":"get_article_api_knowledge_base__article_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"article_id","in":"path","required":true,"schema":{"type":"string","title":"Article Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ArticleResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"No access to this article"}}}},"404":{"description":"Article not found","content":{"application/json":{"example":{"detail":"Article not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"put":{"tags":["cbpulse","Knowledge Base"],"summary":"Update article fields","description":"Partially update an article's title, body, category, tags, or publish status. Cannot update deleted articles. Creates an audit log entry. Requires manager+ role.","operationId":"update_article_api_knowledge_base__article_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"article_id","in":"path","required":true,"schema":{"type":"string","title":"Article Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UpdateArticleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ArticleResponse"}}}},"400":{"description":"Invalid update","content":{"application/json":{"example":{"detail":"No fields to update"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"404":{"description":"Article not found","content":{"application/json":{"example":{"detail":"Article not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"delete":{"tags":["cbpulse","Knowledge Base"],"summary":"Delete a knowledge base article","description":"Soft-delete an article (sets is_deleted=True). Creates an audit log entry. Requires manager+ role.","operationId":"delete_article_api_knowledge_base__article_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"article_id","in":"path","required":true,"schema":{"type":"string","title":"Article Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"404":{"description":"Article not found","content":{"application/json":{"example":{"detail":"Article not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/labor/ot-trends":{"get":{"tags":["cbpulse","Labor"],"summary":"Weekly OT hours per employee over time","description":"Weekly regular / overtime / double-OT hours per employee for one business over a lookback window (default 12 weeks). Each employee's series is zero-filled across every week in the window so the frontend can plot a continuous line — a one-off spike vs a repeat pattern is immediately visible. Employees are named via HBEmployeeORM (bare id fallback). Businesses on Clover fallback show flat/zero OT until moved to real Homebase credentials — a known, separate data gap.","operationId":"ot_trends_api_labor_ot_trends_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":true,"schema":{"type":"string","description":"Business to report on","title":"Business Id"},"description":"Business to report on"},{"name":"weeks","in":"query","required":false,"schema":{"type":"integer","maximum":52,"minimum":1,"description":"Lookback weeks","default":12,"title":"Weeks"},"description":"Lookback weeks"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__OtTrendEmployee"},"title":"Response Ot Trends Api Labor Ot Trends Get"}}}},"403":{"description":"No access to requested business"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/logs/audit":{"get":{"tags":["cbpulse","Audit Logs"],"summary":"List audit logs","description":"Return audit log entries with optional filters for user, action type, target type, business, and date range. Supports pagination via limit/offset. Requires admin+ role.","operationId":"list_audit_logs_api_logs_audit_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"action_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Type"}},{"name":"target_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Type"}},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__AuditLogResponse"},"title":"Response List Audit Logs Api Logs Audit Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/logs/audit/{log_id}":{"get":{"tags":["cbpulse","Audit Logs"],"summary":"Get audit log entry","description":"Retrieve a single audit log entry by its ID. Returns 404 if not found. Requires admin+ role.","operationId":"get_audit_log_api_logs_audit__log_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"log_id","in":"path","required":true,"schema":{"type":"string","title":"Log Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AuditLogResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Audit log not found","content":{"application/json":{"example":{"detail":"Audit log not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/logs/manager":{"post":{"tags":["cbpulse","Audit Logs","Manager Logs"],"summary":"Create a manager log entry","description":"Record a shift note, incident report, or operational observation. Entries are tied to a business and optionally a shift date. Creates an audit log entry. Requires manager+ role.","operationId":"create_manager_log_api_logs_manager_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CreateManagerLogRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ManagerLogResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Manager role required","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"422":{"description":"Validation error in request body","content":{"application/json":{"example":{"detail":[{"loc":["body","business_id"],"msg":"Field required","type":"missing"}]}}}}}},"get":{"tags":["cbpulse","Audit Logs","Manager Logs"],"summary":"List manager logs","description":"Return manager log entries with optional filters for business, entry type, shift date, author, flag status, and date range. RLS-filtered. Supports pagination via limit/offset. Requires manager+ role.","operationId":"list_manager_logs_api_logs_manager_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"entry_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entry Type"}},{"name":"shift_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shift Date"}},{"name":"author_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Id"}},{"name":"is_flagged","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Flagged"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ManagerLogResponse"},"title":"Response List Manager Logs Api Logs Manager Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Manager role required","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/marketing-content/admin/purge":{"post":{"tags":["cbpulse","Marketing Content"],"summary":"Purge","description":"Hard-delete draft rows (admin). Dry-run unless told otherwise.\n\nWhy this exists alongside `DELETE /drafts/{id}`: that route is a SOFT\ndelete, setting `status=\"discarded\"`. Correct for a human rejecting one\npiece of content, useless for clearing a pre-production test corpus — the\nLoom app's default \"Active\" view hides discarded rows, but the Discarded tab\nstill shows every one of them with a count. Soft-deleting a test corpus\nmoves the clutter instead of removing it.\n\n`pushed` is refused unless BOTH named in `statuses` and `allow_pushed=True`.\nDeleting a pushed row does not un-publish anything; it destroys the only\nrecord that we published, including the `vista_post_ids` needed to find the\npost again. Two independent opt-ins, because this endpoint will still be\nhere long after the corpus it was written to clear.","operationId":"purge_api_marketing_content_admin_purge_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PurgeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Purge Api Marketing Content Admin Purge Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/marketing-content/drafts":{"get":{"tags":["cbpulse","Marketing Content"],"summary":"List All","operationId":"list_all_api_marketing_content_drafts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"brand_slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__DraftResponse"},"title":"Response List All Api Marketing Content Drafts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/marketing-content/drafts/{draft_id}":{"get":{"tags":["cbpulse","Marketing Content"],"summary":"Get One","operationId":"get_one_api_marketing_content_drafts__draft_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DraftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"patch":{"tags":["cbpulse","Marketing Content"],"summary":"Patch One","operationId":"patch_one_api_marketing_content_drafts__draft_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UpdateDraftRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DraftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"delete":{"tags":["cbpulse","Marketing Content"],"summary":"Discard","operationId":"discard_api_marketing_content_drafts__draft_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/marketing-content/drafts/{draft_id}/push":{"post":{"tags":["cbpulse","Marketing Content"],"summary":"Push","operationId":"push_api_marketing_content_drafts__draft_id__push_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Push Api Marketing Content Drafts  Draft Id  Push Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/marketing-content/drafts/{draft_id}/regenerate":{"post":{"tags":["cbpulse","Marketing Content"],"summary":"Regenerate","description":"Re-run the pipeline in the background. Same reasoning as `generate`.\n\nFlips the row to `generating` first so the queue shows work in progress\nrather than the previous attempt's copy sitting there looking current for\na minute and a half.","operationId":"regenerate_api_marketing_content_drafts__draft_id__regenerate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DraftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/marketing-content/generate":{"post":{"tags":["cbpulse","Marketing Content"],"summary":"Generate","description":"Create the draft row and return it immediately; fill it in the background.\n\nWHY THIS IS 202 AND NOT 200 (cbpulse#537 follow-up, 2026-08-16)\n---------------------------------------------------------------\nGeneration is one LLM call plus a photo search and takes 60-90 seconds. It\nused to run inside the request, which broke in two ways at once:\n\n- Every standard client gives up first. cbcli's default is 30s, so a\n  generate returned a timeout while the work carried on server-side. The\n  caller was told nothing useful and had no id to go look at.\n- Worse, a request that died before `create_draft` committed left **no\n  trace at all** — no draft, no `generation_failed` row, nothing in the\n  queue. Measured live on 2026-08-16: an attempt died on `database is\n  locked` at the very first INSERT and the app had no memory of it.\n\nThe row is now committed first and the pipeline runs after the response,\nso a draft always exists to carry the outcome — `draft`, `needs_photo` or\n`generation_failed`. The client polls the queue it already refreshes.\n\nThat also means the DB write in the request is now a single fast INSERT\nrather than an INSERT plus a 90-second transaction, which is a large\nreduction in how long this route holds the SQLite writer. It does not\n*fix* the contention (see the note in `src/database.py`) — it stops this\nroute being a major contributor to it.","operationId":"generate_api_marketing_content_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__marketing_content__GenerateRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DraftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/marketing/overview":{"get":{"tags":["cbpulse","Marketing Dashboard"],"summary":"Marketing dashboard overview","description":"Return a composite marketing dashboard payload aggregating 6 domains: signals, campaigns, guest CRM, reviews, inbox, and brand status. Optionally scope to a single business via `business_id` query param. RLS-filtered to businesses the current user can access.","operationId":"marketing_overview_api_marketing_overview_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Scope to a single business. Omit for portfolio-wide view.","title":"Business Id"},"description":"Scope to a single business. Omit for portfolio-wide view."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MarketingOverviewResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to requested business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/nosj/articles/batch":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 3: Batch Production (#346)","description":"Produce N articles in parallel without context pollution between them and without opening-pattern collisions. Creates one parent batch job + one child job per topic SYNCHRONOUSLY, then kicks the batch off as a background task and returns {job_id, child_job_ids} IMMEDIATELY (async). Each article is an isolated article-producer worker (a clean Mode 1 or, when refresh_post_id is given, Mode 2 run) with its own brief + draft. Opening patterns are pre-assigned round-robin over the four NOSJ patterns (greeting / scene-setter / direct / you-lead) so a batch never collides. A worker failure marks only that child failed; the rest still complete and the parent still finishes. NEVER auto-publishes — every article is a DRAFT for CJ. Poll GET /api/nosj/jobs/{job_id} for status. Requires manager role.","operationId":"mode_batch_api_nosj_articles_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BatchRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BatchAccepted"}}}},"422":{"description":"Empty topics list"}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/nosj/articles/refresh":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 2: Article Refresh (#345)","description":"Refresh an existing NOSJ article for the current year: fetch the original via WP REST -> delta research (what changed since publication) -> rewrite (preserve the best, refresh all facts, rebuild structure) -> 13-gate QA -> persist. Returns the draft summary, the QA verdict + per-gate results, and a diff summary (new vs carried sources + factual changes). ALWAYS a DRAFT for CJ — NEVER auto-publishes. Synchronous (mirrors /single), not a job_id handoff. Requires manager role.","operationId":"mode_refresh_api_nosj_articles_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__nosj__RefreshRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__NOSJRefreshResult"}}}},"404":{"description":"Source article not found"},"422":{"description":"Missing/invalid source_post_id"}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/nosj/drafts":{"get":{"tags":["cbpulse","NOSJ"],"summary":"List NOSJ article drafts","description":"List persisted NOSJ articles, newest first. Optionally filter by status (default: only 'draft'). Articles are never auto-published; this is CJ's review queue. Requires manager role.","operationId":"list_drafts_api_nosj_drafts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status; omit/empty for all statuses","default":"draft","title":"Status"},"description":"Filter by status; omit/empty for all statuses"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__NOSJArticleSummary"},"title":"Response List Drafts Api Nosj Drafts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/nosj/drafts/{article_id}":{"get":{"tags":["cbpulse","NOSJ"],"summary":"Get a NOSJ article draft","description":"Fetch a single NOSJ article draft by id, including the full body, the structured research brief, and the 13-gate QA report. Requires manager role.","operationId":"get_draft_api_nosj_drafts__article_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"article_id","in":"path","required":true,"schema":{"type":"string","title":"Article Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__NOSJArticleDetail"}}}},"404":{"description":"Draft not found","content":{"application/json":{"example":{"detail":"Draft not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"patch":{"tags":["cbpulse","NOSJ"],"summary":"Edit a NOSJ draft (title / body)","description":"Let CJ tweak a draft before publishing — voice/fact fixes the writer's QA left as soft warns. Patches `title` and/or `body` (markdown); omitted fields are unchanged. Does NOT re-run QA (the edit is CJ's authority). Requires manager role. (cbpulse#514)","operationId":"edit_draft_api_nosj_drafts__article_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"article_id","in":"path","required":true,"schema":{"type":"string","title":"Article Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EditDraftRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__NOSJArticleDetail"}}}},"404":{"description":"Draft not found","content":{"application/json":{"example":{"detail":"Draft not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/nosj/drafts/{article_id}/publish":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Publish a NOSJ draft to WordPress","description":"CJ's in-app publish: render the draft body (markdown → HTML), create the post on newsofstjohn.com via the WP REST API (Application-Password auth), set status='approved', and store the WP post id + link in metadata. The WP post status is `nosj_wordpress_publish_status` — default 'draft' (pushed to WP but NOT live, for a final human publish in WP); set 'publish' for one-click-live. NEVER automatic — only this explicit CJ action publishes. Returns 503 if WP write creds aren't configured, 502 if WordPress rejects the post. Requires manager role. (cbpulse#514)","operationId":"publish_draft_api_nosj_drafts__article_id__publish_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"article_id","in":"path","required":true,"schema":{"type":"string","title":"Article Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__NOSJArticleSummary"}}}},"404":{"description":"Draft not found","content":{"application/json":{"example":{"detail":"Draft not found"}}}},"502":{"description":"WordPress rejected the post"},"503":{"description":"WordPress publishing not configured (no write creds)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/nosj/drafts/{article_id}/reject":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Reject a NOSJ draft","description":"Mark a draft as 'rejected' — CJ declined it (or it's a duplicate / junk run). Sets status='rejected' so it leaves the active review queue while the row is kept for audit (body + QA report preserved). Optional `note` records why (stored in metadata). Idempotent: re-rejecting is a no-op returning the same summary. No un-reject transition (re-run the pipeline for a fresh draft). Requires manager role. (cbpulse#514)","operationId":"reject_draft_api_nosj_drafts__article_id__reject_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"article_id","in":"path","required":true,"schema":{"type":"string","title":"Article Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbpulse__src__api__routes__nosj__RejectRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__NOSJArticleSummary"}}}},"404":{"description":"Draft not found","content":{"application/json":{"example":{"detail":"Draft not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/nosj/editorial/full-pipeline/plan":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 5: Full Pipeline — Plan (#348)","description":"Phase A of the weekly content session. Runs the Mode 4 editorial planner (identical scan + cbai-Claude scoring) and returns the same prioritized slate of 5-15 recommendations — but ALSO persists it with a slate_id and a 24h TTL so CJ can review it in the Loom UI and then send the approved selections to /full-pipeline/produce. Like Mode 4 this NEVER auto-executes: no article is written, nothing is persisted as a draft. Requires manager role.","operationId":"mode_full_pipeline_plan_api_nosj_editorial_full_pipeline_plan_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EditorialPlanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FullPipelinePlanResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/nosj/editorial/full-pipeline/produce":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 5: Full Pipeline — Produce (#348)","description":"Phase C of the weekly content session. Loads the slate persisted by /full-pipeline/plan, selects the recommendations CJ approved (approved_indices), maps each to a Mode 3 batch topic (a refresh item -> a refresh of its source post via Mode 2; otherwise a new article via Mode 1), and dispatches them through the SAME Mode 3 batch machinery: one parent + one child job per approved item, opening-pattern tracker, background workers. Returns {job_id, child_job_ids} IMMEDIATELY (async); poll GET /api/nosj/jobs/{job_id}. NEVER auto-publishes — every article is a DRAFT for CJ. A missing slate -> 404; an expired slate (past its 24h TTL) -> 410; an out-of-range index -> 400. Requires manager role.","operationId":"mode_full_pipeline_produce_api_nosj_editorial_full_pipeline_produce_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FullPipelineProduceRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BatchAccepted"}}}},"400":{"description":"Out-of-range approved index"},"404":{"description":"Slate not found"},"410":{"description":"Slate expired (past its 24h TTL)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/nosj/editorial/plan":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 4: Editorial Planning (#347)","description":"Return a prioritized editorial slate — a MENU of 5-15 recommendations, NEVER articles. Scans the WP archive for stale evergreen content (refresh candidates), scans VI news via cbintel (new-article candidates), identifies content gaps, then scores + ranks every candidate on the four reference/09 dimensions via the cbai Claude tier. Each item carries a suggested action (single vs refresh-of-post-id) for CJ to send to Mode 1/2/3. This path NEVER auto-executes: no article is written, nothing is persisted as a draft. A failing scan (cbintel/WP down) degrades gracefully — the slate carries whatever was gathered. Requires manager role.","operationId":"mode_editorial_plan_api_nosj_editorial_plan_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EditorialPlanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__EditorialSlate"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/nosj/jobs/{job_id}":{"get":{"tags":["cbpulse","NOSJ"],"summary":"Mode 3: Batch job status (#346)","description":"Poll a batch job: the parent status plus every child article job (status, assigned opening pattern, produced draft id, any error). Requires manager role.","operationId":"get_batch_job_api_nosj_jobs__job_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__NOSJJobStatus"}}}},"404":{"description":"Job not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/nosj/monitoring/jobs":{"get":{"tags":["cbpulse","NOSJ"],"summary":"Mode 6: List monitoring jobs (#349)","description":"List configured NOSJ monitoring jobs and their status (pending_approval | active | inactive). Requires manager role.","operationId":"list_monitoring_jobs_api_nosj_monitoring_jobs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__MonitoringJobResponse"},"type":"array","title":"Response List Monitoring Jobs Api Nosj Monitoring Jobs Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 6: Configure a monitoring job (#349)","description":"Configure a recurring cbintel sweep. Created as 'pending_approval' — this returns the config for CJ to approve and does NOT register a cbcron job yet. Activation requires an explicit POST to /monitoring/jobs/{id}/activate. Requires manager role.","operationId":"create_monitoring_job_api_nosj_monitoring_jobs_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MonitoringJobCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MonitoringJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/nosj/monitoring/jobs/{job_id}/activate":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 6: Activate a monitoring job (#349)","description":"Turn on a monitoring job after CJ approves it: registers a cbcron job targeting the authless localhost sweep endpoint with the job's schedule_cron in USVI time (America/St_Thomas), stores the cbcron job id, and sets status='active'. Idempotent — re-activating an already active job does not double-register. A cbcron failure returns a clean error and leaves the job inactive. Requires manager role.","operationId":"activate_monitoring_job_api_nosj_monitoring_jobs__job_id__activate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MonitoringJobResponse"}}}},"404":{"description":"Monitoring job not found"},"502":{"description":"cbcron unreachable / rejected the request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/nosj/monitoring/jobs/{job_id}/deactivate":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 6: Deactivate a monitoring job (#349)","description":"Turn off a monitoring job: pauses/deletes its cbcron job (by stored cbcron_job_id) so no further sweeps run, and sets status='inactive'. Requires manager role.","operationId":"deactivate_monitoring_job_api_nosj_monitoring_jobs__job_id__deactivate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MonitoringJobResponse"}}}},"404":{"description":"Monitoring job not found"},"502":{"description":"cbcron unreachable / rejected the request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/nosj/monitoring/jobs/{job_id}/run":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 6: Run a sweep (cron-triggered, #349)","description":"Run the intelligence sweep for a monitoring job — fires cbintel crawl for each source (or run_graph when a template is set), records last_run_at / last_run_status, and returns the submitted cbintel job ids. AUTHLESS + localhost-only: this is the URL the cbcron job targets (cbcron POSTs the internal port with NO auth header, the convention used by /api/system/tick). NOT manager-gated. Async submit — the sweep feeds the planner's fresh signal; it does not block on crawl results.","operationId":"run_monitoring_sweep_api_nosj_monitoring_jobs__job_id__run_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__MonitoringSweepResult"}}}},"404":{"description":"Monitoring job not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/nosj/single":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 1: Single Article (#344)","description":"Produce one article on a named St. John topic, end to end: research -> write -> 13-gate QA -> persist. The article is ALWAYS a DRAFT for CJ — QA APPROVED lands as 'draft', NEEDS_REVISION as 'needs_revision', REJECTED as 'rejected' (still persisted so CJ sees what failed). NEVER auto-publishes. Requires manager role.","operationId":"mode_single_api_nosj_single_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ModeRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__NOSJSingleResult"}}}},"400":{"description":"Empty/invalid topic"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/nosj/single/async":{"post":{"tags":["cbpulse","NOSJ"],"summary":"Mode 1 async: Single Article, returns a job id (#573)","description":"Same generation as `POST /single`, handed off to the background so the client is not holding an HTTP connection open for minutes.\n\n**Why this exists.** Mode 1 does research, then writing, then 13 QA gates, most of them LLM calls through cbai — CJ's run was still going at 120 seconds. Nothing tunable makes that fit a normal proxy timeout, and when the proxy gave up the frontend could not parse the resulting redirect and reported failure for an article that had actually succeeded. That is worse than an error: it invites a retry and produces duplicate drafts. Same failure as infrastructure#258 / cbradio-ui#157.\n\nCreates the job rows synchronously so the returned id is immediately pollable, then generates in the background. Reuses the Mode 3 batch machinery as a batch of one — deliberately, so there is no second job system to keep correct — and polls the SAME `GET /api/nosj/jobs/{job_id}`.\n\n**Generation is identical to the inline route.** In particular no opening pattern is pre-assigned: Mode 3 assigns them round-robin to stop articles in one batch colliding, which for a batch of one would pin every article to \"greeting\" forever. The writer chooses, exactly as in `POST /single`.\n\nNEVER auto-publishes — the article is always a DRAFT for CJ. Requires manager role.","operationId":"mode_single_async_api_nosj_single_async_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ModeRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SingleAccepted"}}}},"400":{"description":"Empty/invalid topic"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/notifications":{"get":{"tags":["cbpulse","Notifications"],"summary":"List notifications","description":"List the current user's notifications with optional unread filter. Returns items and total unread count for badge display.","operationId":"list_notifications_api_notifications_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"unread_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only return unread notifications","default":false,"title":"Unread Only"},"description":"Only return unread notifications"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max items to return","default":50,"title":"Limit"},"description":"Max items to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__NotificationListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/notifications/preferences":{"get":{"tags":["cbpulse","Notifications"],"summary":"Get notification preferences","description":"Get the current user's notification preferences. Creates default preferences if none exist.","operationId":"get_preferences_api_notifications_preferences_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PreferenceResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["cbpulse","Notifications"],"summary":"Update notification preferences","description":"Update the current user's notification preferences. Only provided fields are changed.","operationId":"update_preferences_api_notifications_preferences_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PreferenceUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PreferenceResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"422":{"description":"Validation error in request body","content":{"application/json":{"example":{"detail":[{"loc":["body","quiet_hours_start"],"msg":"Input should be a valid string","type":"string_type"}]}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/notifications/read-all":{"post":{"tags":["cbpulse","Notifications"],"summary":"Mark all notifications as read","description":"Mark all unread notifications as read for the current user. Returns the count of notifications marked.","operationId":"mark_all_notifications_read_api_notifications_read_all_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/notifications/unread-count":{"get":{"tags":["cbpulse","Notifications"],"summary":"Get unread count","description":"Lightweight endpoint for the notification bell badge. Returns only the unread count.","operationId":"unread_count_api_notifications_unread_count_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UnreadCountResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/notifications/{notification_id}/read":{"post":{"tags":["cbpulse","Notifications"],"summary":"Mark notification as read","description":"Mark a single notification as read. Only the owning user can mark their notifications.","operationId":"mark_notification_read_api_notifications__notification_id__read_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","title":"Notification Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__NotificationResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"404":{"description":"Notification not found","content":{"application/json":{"example":{"detail":"Notification not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/onboarding":{"get":{"tags":["cbpulse","HR"],"summary":"List onboarding packets","operationId":"list_onboarding_api_onboarding_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"employee_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employee Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__OnboardingResponse"},"title":"Response List Onboarding Api Onboarding Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","HR"],"summary":"Create onboarding packet","description":"Creates a packet in 'pending' state. Set send_now=true to also mark it as 'sent' (full BoldSign dispatch wires in #312).","operationId":"create_onboarding_api_onboarding_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OnboardingCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OnboardingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/onboarding/quick-send":{"post":{"tags":["cbpulse","HR"],"summary":"Create + dispatch an onboarding packet using the property's default template","description":"Single-call shortcut for the Loom Employee File 'Send Onboarding' button (cbpulse#387). Resolves the employee's business, picks the configured default onboarding template for that property (``ONBOARDING_TEMPLATES[business.slug]``), creates an ``HROnboardingORM`` row, and immediately dispatches it via cbesig — all in one round-trip. 409 if the property has no default template or if an open packet (``status='pending'``/``'sent'``/``'partially_signed'``) already exists for this (employee, business).","operationId":"quick_send_onboarding_api_onboarding_quick_send_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"X-On-Behalf-Of-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of-Email"}},{"name":"X-On-Behalf-Of-Name","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of-Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OnboardingQuickSendRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OnboardingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/onboarding/{ob_id}":{"get":{"tags":["cbpulse","HR"],"summary":"Get onboarding detail","operationId":"get_onboarding_api_onboarding__ob_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ob_id","in":"path","required":true,"schema":{"type":"string","title":"Ob Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OnboardingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/onboarding/{ob_id}/cancel":{"post":{"tags":["cbpulse","HR"],"summary":"Cancel onboarding packet","operationId":"cancel_onboarding_api_onboarding__ob_id__cancel_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ob_id","in":"path","required":true,"schema":{"type":"string","title":"Ob Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OnboardingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/onboarding/{ob_id}/document-url":{"get":{"tags":["cbpulse","HR"],"summary":"Mint a fresh PDF URL for an onboarding packet","description":"Returns a time-limited PDF URL for the packet's BoldSign envelope, resolved at request time (no stale presigned URLs in the DB). Completed envelopes return a cbesig archive presigned URL (source=archive); in-flight envelopes return a cbpulse proxy URL that streams the live PDF (source=live); legacy airtable-backfill rows with no envelope return their stored document_url (source=legacy). Cancelled packets return a graceful 404 (cbpulse#519); otherwise no status gate — completed/signed packets must be openable. Mirrors the write-up resolver (cbpulse#428/#488).","operationId":"get_onboarding_document_url_api_onboarding__ob_id__document_url_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ob_id","in":"path","required":true,"schema":{"type":"string","title":"Ob Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OnboardingDocumentURLResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/onboarding/{ob_id}/document.pdf":{"get":{"tags":["cbpulse","HR"],"summary":"Stream an onboarding packet's PDF (live BoldSign envelopes)","description":"Proxies the PDF bytes from cbesig's ``/api/v1/documents/{id}/download`` endpoint. Used as the fallback target by ``/document-url`` for envelopes that aren't archived yet. cbpulse#488.","operationId":"stream_onboarding_document_api_onboarding__ob_id__document_pdf_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ob_id","in":"path","required":true,"schema":{"type":"string","title":"Ob Id"}}],"responses":{"200":{"description":"PDF stream","content":{"application/pdf":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/onboarding/{ob_id}/remind":{"post":{"tags":["cbpulse","HR"],"summary":"Send BoldSign reminder for an outstanding onboarding packet","description":"Re-emails the pending signer(s) on this onboarding envelope without invalidating signatures already collected. Allowed when status is 'sent', 'delivered', or 'partially_signed' and the row has a ``boldsign_doc_id``; anything else returns 409. Resolves cbpulse#385 + cbpulse#430.","operationId":"remind_onboarding_api_onboarding__ob_id__remind_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ob_id","in":"path","required":true,"schema":{"type":"string","title":"Ob Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbpulse__OnboardingRemindRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OnboardingRemindResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/onboarding/{ob_id}/send":{"post":{"tags":["cbpulse","HR"],"summary":"Send onboarding packet via cbesig","description":"Dispatches the property-specific BoldSign onboarding template via cbesig and stores the envelope id on the row. Manager identity is resolved from X-On-Behalf-Of-* headers when present (mesh proxy case), else from the JWT subject. Resolves cbpulse#312/#326/#361.","operationId":"send_onboarding_api_onboarding__ob_id__send_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ob_id","in":"path","required":true,"schema":{"type":"string","title":"Ob Id"}},{"name":"X-On-Behalf-Of-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of-Email"}},{"name":"X-On-Behalf-Of-Name","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of-Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OnboardingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/opentable/ingest-gold":{"post":{"tags":["cbpulse","OpenTable Jobs"],"summary":"Ingest GuestCenter gold reservations","description":"Accept raw reservation JSON from the out-of-mesh GuestCenter gold fetcher, map to dining reservations (venue + service-period auto-tagged), and upsert idempotently. Called by scripts/opentable_gold_fetch.py via cbcron daily.","operationId":"ingest_gold_api_opentable_ingest_gold_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GoldIngestRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GoldIngestResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/briefing":{"get":{"tags":["cbpulse","Operations"],"summary":"The manager's shift-start note, from the day's one read (management)","operationId":"briefing_api_ops_businesses__business_id__briefing_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"fresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Fresh"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Briefing Api Ops Businesses  Business Id  Briefing Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/channels":{"get":{"tags":["cbpulse","Operations"],"summary":"The property's department channels, with the last line in each","operationId":"list_channels_api_ops_businesses__business_id__channels_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ChannelOut"},"title":"Response List Channels Api Ops Businesses  Business Id  Channels Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/channels/{channel}/messages":{"get":{"tags":["cbpulse","Operations"],"summary":"A channel's lines, oldest first; ``after`` for polling","operationId":"list_channel_messages_api_ops_businesses__business_id__channels__channel__messages_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}},{"name":"after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"After"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ChannelMessageOut"},"title":"Response List Channel Messages Api Ops Businesses  Business Id  Channels  Channel  Messages Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Operations"],"summary":"Say something in a channel","operationId":"post_channel_message_api_ops_businesses__business_id__channels__channel__messages_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ChannelMessageCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ChannelMessageOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/checklist-runs":{"get":{"tags":["cbpulse","Operations"],"summary":"Checklist runs for a period (default: today)","operationId":"list_runs_api_ops_businesses__business_id__checklist_runs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"period_key","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Key"}},{"name":"room_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__RunOut"},"title":"Response List Runs Api Ops Businesses  Business Id  Checklist Runs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Operations"],"summary":"Start (or resume) a run of a checklist for today — idempotent","operationId":"start_run_api_ops_businesses__business_id__checklist_runs_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RunStart"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RunOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/checklist-runs/{run_id}/complete":{"post":{"tags":["cbpulse","Operations"],"summary":"Mark a run complete","operationId":"complete_run_api_ops_businesses__business_id__checklist_runs__run_id__complete_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RunOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/checklist-runs/{run_id}/items/{run_item_id}":{"patch":{"tags":["cbpulse","Operations"],"summary":"Tick or untick one item","operationId":"patch_run_item_api_ops_businesses__business_id__checklist_runs__run_id__items__run_item_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}},{"name":"run_item_id","in":"path","required":true,"schema":{"type":"string","title":"Run Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RunItemPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RunOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/checklists":{"get":{"tags":["cbpulse","Operations"],"summary":"Checklist templates at a property","operationId":"list_checklists_api_ops_businesses__business_id__checklists_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"}},{"name":"dept","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"}},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ChecklistOut"},"title":"Response List Checklists Api Ops Businesses  Business Id  Checklists Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Operations"],"summary":"Create a checklist template (management)","operationId":"create_checklist_api_ops_businesses__business_id__checklists_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ChecklistCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ChecklistOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/checklists/{checklist_id}":{"patch":{"tags":["cbpulse","Operations"],"summary":"Rename, re-home, reorder or archive a template (management)","operationId":"patch_checklist_api_ops_businesses__business_id__checklists__checklist_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"checklist_id","in":"path","required":true,"schema":{"type":"string","title":"Checklist Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ChecklistPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ChecklistOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/checklists/{checklist_id}/items":{"put":{"tags":["cbpulse","Operations"],"summary":"Replace a template's items, in order (management)","operationId":"put_checklist_items_api_ops_businesses__business_id__checklists__checklist_id__items_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"checklist_id","in":"path","required":true,"schema":{"type":"string","title":"Checklist Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ChecklistItemsPut"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ChecklistOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/housekeeping/refresh":{"post":{"tags":["cbpulse","Operations"],"summary":"Pull housekeeping status from Cloudbeds now (throttled to once a minute)","operationId":"refresh_housekeeping_api_ops_businesses__business_id__housekeeping_refresh_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Refresh Housekeeping Api Ops Businesses  Business Id  Housekeeping Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/housekeeping/{room_id}/condition":{"put":{"tags":["cbpulse","Operations"],"summary":"Mark a room clean or dirty — writes to Cloudbeds, then mirrors locally","operationId":"put_room_condition_api_ops_businesses__business_id__housekeeping__room_id__condition_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"room_id","in":"path","required":true,"schema":{"type":"string","title":"Room Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RoomConditionPut"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Put Room Condition Api Ops Businesses  Business Id  Housekeeping  Room Id  Condition Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/inventory-alerts":{"get":{"tags":["cbpulse","Operations"],"summary":"Inventory alerts at a property","operationId":"list_inventory_alerts_api_ops_businesses__business_id__inventory_alerts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"dept","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__InventoryAlertOut"},"title":"Response List Inventory Alerts Api Ops Businesses  Business Id  Inventory Alerts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Operations"],"summary":"Log an inventory alert","operationId":"create_inventory_alert_api_ops_businesses__business_id__inventory_alerts_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InventoryAlertCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InventoryAlertOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/inventory-alerts/{alert_id}":{"patch":{"tags":["cbpulse","Operations"],"summary":"Update an inventory alert","operationId":"patch_inventory_alert_api_ops_businesses__business_id__inventory_alerts__alert_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InventoryAlertPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__InventoryAlertOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/requests":{"get":{"tags":["cbpulse","Operations"],"summary":"Guest requests at a property","operationId":"list_requests_api_ops_businesses__business_id__requests_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"dept","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__GuestRequestOut"},"title":"Response List Requests Api Ops Businesses  Business Id  Requests Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Operations"],"summary":"Log a guest request","operationId":"create_request_api_ops_businesses__business_id__requests_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GuestRequestCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GuestRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/requests/{request_id}":{"patch":{"tags":["cbpulse","Operations"],"summary":"Update a guest request (status, department, assignee)","operationId":"patch_request_api_ops_businesses__business_id__requests__request_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GuestRequestPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__GuestRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/tasks":{"get":{"tags":["cbpulse","Operations"],"summary":"Tasks at a property","operationId":"list_tasks_api_ops_businesses__business_id__tasks_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"dept","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"}},{"name":"assignee","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee"}},{"name":"include_done","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Done"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"default":300,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__TaskOut"},"title":"Response List Tasks Api Ops Businesses  Business Id  Tasks Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Operations"],"summary":"Create a task","operationId":"create_task_api_ops_businesses__business_id__tasks_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TaskCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TaskOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/tasks/{task_id}":{"get":{"tags":["cbpulse","Operations"],"summary":"A task with its comments and activity","operationId":"get_task_api_ops_businesses__business_id__tasks__task_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TaskDetailOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"patch":{"tags":["cbpulse","Operations"],"summary":"Update a task","operationId":"patch_task_api_ops_businesses__business_id__tasks__task_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TaskPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TaskOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/tasks/{task_id}/comments":{"post":{"tags":["cbpulse","Operations"],"summary":"Comment on a task","operationId":"comment_task_api_ops_businesses__business_id__tasks__task_id__comments_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CommentCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TaskCommentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/ops/businesses/{business_id}/today":{"get":{"tags":["cbpulse","Operations"],"summary":"The property's day in one read: rooms, arrivals, requests, tickets, tasks, checklists","operationId":"today_api_ops_businesses__business_id__today_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Today Api Ops Businesses  Business Id  Today Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/orders":{"get":{"tags":["cbpulse","Orders"],"summary":"List orders","description":"Return purchase orders with optional filters for status, business, and vendor. Supports pagination via limit/offset. RLS-filtered by user's business access.","operationId":"list_orders_api_orders_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"vendor_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Name"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__OrderResponse"},"title":"Response List Orders Api Orders Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Orders"],"summary":"Create a purchase order","description":"Submit a new purchase order request. Status is set to 'pending_approval'. Creates an audit log entry. Requires manager+ role with access to the target business.","operationId":"create_order_api_orders_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__orders__CreateOrderRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OrderResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","vendor_name"],"msg":"Field required","type":"missing"}]}}}}}}},"/cbpulse/api/orders/{order_id}":{"get":{"tags":["cbpulse","Orders"],"summary":"Get order by ID","description":"Retrieve a single purchase order by its ID. Returns 404 if not found, 403 if the user lacks access to the order's business.","operationId":"get_order_api_orders__order_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OrderResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"No access to this order"}}}},"404":{"description":"Order not found","content":{"application/json":{"example":{"detail":"Order not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/orders/{order_id}/approve":{"post":{"tags":["cbpulse","Orders"],"summary":"Approve an order","description":"Approve a pending purchase order. Records the approving user and timestamp. Returns 400 if the order is not in 'pending_approval' status. Requires admin+ role.","operationId":"approve_order_api_orders__order_id__approve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OrderResponse"}}}},"400":{"description":"Invalid state transition","content":{"application/json":{"example":{"detail":"Cannot approve order in status 'approved'"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Order not found","content":{"application/json":{"example":{"detail":"Order not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/orders/{order_id}/reject":{"post":{"tags":["cbpulse","Orders"],"summary":"Reject an order","description":"Reject a pending purchase order with a required reason. Sets status to 'cancelled'. Returns 400 if the order is not in 'pending_approval' status. Requires admin+ role.","operationId":"reject_order_api_orders__order_id__reject_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__orders__RejectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OrderResponse"}}}},"400":{"description":"Invalid state transition","content":{"application/json":{"example":{"detail":"Cannot reject order in status 'approved'"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Order not found","content":{"application/json":{"example":{"detail":"Order not found"}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","reason"],"msg":"Field required","type":"missing"}]}}}}}}},"/cbpulse/api/orders/{order_id}/status":{"put":{"tags":["cbpulse","Orders"],"summary":"Update order fulfillment status","description":"Update an order's status to one of: placed, in_transit, delivered, cancelled. Optionally set tracking_number and expected_delivery. Requires admin+ role.","operationId":"update_order_status_api_orders__order_id__status_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__StatusUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OrderResponse"}}}},"400":{"description":"Invalid status value","content":{"application/json":{"example":{"detail":"Invalid status. Must be one of: cancelled, delivered, in_transit, placed"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Order not found","content":{"application/json":{"example":{"detail":"Order not found"}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","status"],"msg":"Field required","type":"missing"}]}}}}}}},"/cbpulse/api/overtime":{"get":{"tags":["cbpulse","HR"],"summary":"List overtime requests","operationId":"list_overtime_api_overtime_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"employee_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employee Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__OvertimeResponse"},"title":"Response List Overtime Api Overtime Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","HR"],"summary":"Create overtime request","operationId":"create_overtime_api_overtime_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OvertimeCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OvertimeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/overtime/{ot_id}/approve":{"post":{"tags":["cbpulse","HR"],"summary":"Approve overtime request","operationId":"approve_overtime_api_overtime__ot_id__approve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ot_id","in":"path","required":true,"schema":{"type":"string","title":"Ot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OvertimeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/overtime/{ot_id}/deny":{"post":{"tags":["cbpulse","HR"],"summary":"Deny overtime request (reason required)","operationId":"deny_overtime_api_overtime__ot_id__deny_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ot_id","in":"path","required":true,"schema":{"type":"string","title":"Ot Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OvertimeDeny"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OvertimeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/overtime/{ot_id}/remind":{"post":{"tags":["cbpulse","HR"],"summary":"Send BoldSign reminder for an outstanding overtime request","description":"Re-emails the pending signer(s) on this overtime envelope without invalidating signatures already collected. Allowed when status is 'sent', 'delivered', or 'partially_signed' and the row has a ``boldsign_doc_id``; anything else returns 409. Resolves cbpulse#385 + cbpulse#430.","operationId":"remind_overtime_api_overtime__ot_id__remind_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ot_id","in":"path","required":true,"schema":{"type":"string","title":"Ot Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbpulse__OvertimeRemindRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OvertimeRemindResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/overtime/{ot_id}/send":{"post":{"tags":["cbpulse","HR"],"summary":"Send overtime request via cbesig","description":"Dispatches the overtime template via cbesig (Employee → Manager signing order) and stores the envelope id on the request. Manager identity is resolved from X-On-Behalf-Of-* headers when present, else from the JWT subject. Resolves cbpulse#357/#361.","operationId":"send_overtime_api_overtime__ot_id__send_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ot_id","in":"path","required":true,"schema":{"type":"string","title":"Ot Id"}},{"name":"X-On-Behalf-Of-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of-Email"}},{"name":"X-On-Behalf-Of-Name","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of-Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OvertimeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/contractors":{"get":{"tags":["cbpulse","Payroll"],"summary":"List contractor pay periods","description":"View contractor payment periods with different deadlines.","operationId":"list_contractor_periods_api_payroll_contractors_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__PayPeriodResponse"},"title":"Response List Contractor Periods Api Payroll Contractors Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/countdown":{"get":{"tags":["cbpulse","Payroll"],"summary":"Payroll countdown widget","description":"Days until next payroll submission (15th/last day) and contractor pay (4th/19th).","operationId":"get_countdown_api_payroll_countdown_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CountdownResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/payroll/exceptions":{"get":{"tags":["cbpulse","Payroll"],"summary":"List timesheet exceptions","description":"All unresolved timesheet exceptions across pay periods.","operationId":"list_exceptions_api_payroll_exceptions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"default":"open","title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__TimesheetExceptionResponse"},"title":"Response List Exceptions Api Payroll Exceptions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/exceptions/{exception_id}/resolve":{"post":{"tags":["cbpulse","Payroll"],"summary":"Resolve timesheet exception","description":"Resolve a timesheet exception with notes. Requires manager+ role.","operationId":"resolve_exception_api_payroll_exceptions__exception_id__resolve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"exception_id","in":"path","required":true,"schema":{"type":"string","title":"Exception Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ResolveExceptionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TimesheetExceptionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/labor-cost":{"get":{"tags":["cbpulse","Payroll"],"summary":"Labor cost by pay period","description":"Accumulated labor cost in current period by business with projected total.","operationId":"get_labor_cost_api_payroll_labor_cost_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__LaborCostResponse"},"title":"Response Get Labor Cost Api Payroll Labor Cost Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/overtime-history":{"get":{"tags":["cbpulse","Payroll"],"summary":"Rolling OT history","description":"Per-employee OT history by semi-monthly pay period for a configurable lookback.","operationId":"get_overtime_history_api_payroll_overtime_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":90,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OvertimeHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/overtime-tracking":{"get":{"tags":["cbpulse","Payroll"],"summary":"Current pay-period OT tracking","description":"Employees projected to exceed 40 hours in the current semi-monthly pay period.","operationId":"get_overtime_tracking_api_payroll_overtime_tracking_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__OvertimeTrackingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/periods":{"get":{"tags":["cbpulse","Payroll"],"summary":"List pay periods","description":"Current and historical pay periods with optional business and status filters.","operationId":"list_periods_api_payroll_periods_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"period_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Type"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__PayPeriodResponse"},"title":"Response List Periods Api Payroll Periods Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/periods/{period_id}/approve":{"post":{"tags":["cbpulse","Payroll"],"summary":"Approve payroll period","description":"Approve a payroll period. Fails if unresolved blocking exceptions exist. Requires admin+ role.","operationId":"approve_period_api_payroll_periods__period_id__approve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"period_id","in":"path","required":true,"schema":{"type":"string","title":"Period Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ApproveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PayPeriodResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/periods/{period_id}/export-paychex":{"post":{"tags":["cbpulse","Payroll"],"summary":"Export to Paychex Flex","description":"Export approved payroll package in Paychex Flex format. Returns 501 until Paychex API details are confirmed.","operationId":"export_paychex_api_payroll_periods__period_id__export_paychex_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"period_id","in":"path","required":true,"schema":{"type":"string","title":"Period Id"}}],"responses":{"501":{"description":"Not implemented","content":{"application/json":{"schema":{},"example":{"detail":"Paychex Flex integration not yet implemented"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/periods/{period_id}/package":{"get":{"tags":["cbpulse","Payroll"],"summary":"Get payroll package","description":"Full payroll package with line-by-line employee detail and exceptions.","operationId":"get_package_api_payroll_periods__period_id__package_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"period_id","in":"path","required":true,"schema":{"type":"string","title":"Period Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PayPeriodDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/periods/{period_id}/reject":{"post":{"tags":["cbpulse","Payroll"],"summary":"Reject payroll period","description":"Reject a payroll period with required notes. Requires admin+ role.","operationId":"reject_period_api_payroll_periods__period_id__reject_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"period_id","in":"path","required":true,"schema":{"type":"string","title":"Period Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__payroll__RejectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PayPeriodResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/payroll/variance":{"get":{"tags":["cbpulse","Payroll"],"summary":"Payroll variance report","description":"Comparison of current vs previous pay period totals by business.","operationId":"get_variance_api_payroll_variance_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__VarianceResponse"},"title":"Response Get Variance Api Payroll Variance Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/portfolio/ai-summary":{"get":{"tags":["cbpulse","Portfolio"],"summary":"Get AI summary with anomalies","description":"Combined view: flash report, exception feed, variance reports, and cross-business anomaly patterns for the owner's morning briefing.","operationId":"ai_summary_api_portfolio_ai_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"date","in":"query","required":false,"schema":{"type":"string","description":"Report date (YYYY-MM-DD). Defaults to today.","title":"Date"},"description":"Report date (YYYY-MM-DD). Defaults to today."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__AISummaryResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/portfolio/business-ranking":{"get":{"tags":["cbpulse","Portfolio"],"summary":"Get business ranking","description":"Return all businesses ranked by a chosen KPI. Default: worst health scores first (ascending).","operationId":"business_ranking_api_portfolio_business_ranking_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"date","in":"query","required":false,"schema":{"type":"string","description":"Report date (YYYY-MM-DD). Defaults to today.","title":"Date"},"description":"Report date (YYYY-MM-DD). Defaults to today."},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"KPI to sort by: health_score, prime_cost_pct, total_revenue","default":"health_score","title":"Sort By"},"description":"KPI to sort by: health_score, prime_cost_pct, total_revenue"},{"name":"order","in":"query","required":false,"schema":{"type":"string","description":"Sort order: asc or desc","default":"asc","title":"Order"},"description":"Sort order: asc or desc"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__FlashReportBusiness"},"title":"Response Business Ranking Api Portfolio Business Ranking Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/portfolio/exceptions":{"get":{"tags":["cbpulse","Portfolio"],"summary":"Get exception feed","description":"Consolidated feed of all items requiring attention: metric anomalies, active alerts, integration failures, and missing data. Sorted by severity (critical first).","operationId":"exceptions_feed_api_portfolio_exceptions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"date","in":"query","required":false,"schema":{"type":"string","description":"Report date (YYYY-MM-DD). Defaults to today.","title":"Date"},"description":"Report date (YYYY-MM-DD). Defaults to today."},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by severity: critical, warning, or info","title":"Severity"},"description":"Filter by severity: critical, warning, or info"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ExceptionFeedResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/portfolio/flash-report":{"get":{"tags":["cbpulse","Portfolio"],"summary":"Get portfolio flash report","description":"Return a flash report with KPIs and WoW comparisons for all active businesses. Supports day, week-to-date (wtd), and period-to-date (ptd) aggregation periods.","operationId":"flash_report_api_portfolio_flash_report_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"date","in":"query","required":false,"schema":{"type":"string","description":"Report date (YYYY-MM-DD). Defaults to today.","title":"Date"},"description":"Report date (YYYY-MM-DD). Defaults to today."},{"name":"period","in":"query","required":false,"schema":{"type":"string","description":"Aggregation period: day, wtd, or ptd","default":"day","title":"Period"},"description":"Aggregation period: day, wtd, or ptd"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__FlashReportResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/quickbooks/{business_id}/accounts":{"get":{"tags":["cbpulse","QuickBooks"],"summary":"List synced QB accounts","description":"Return the chart of accounts synced from QuickBooks for a business. Supports filtering by account_type, classification, and active status.","operationId":"list_accounts_api_quickbooks__business_id__accounts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"account_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by account type","title":"Account Type"},"description":"Filter by account type"},{"name":"classification","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by classification","title":"Classification"},"description":"Filter by classification"},{"name":"active_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only show active accounts","default":true,"title":"Active Only"},"description":"Only show active accounts"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__QBAccountListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/quickbooks/{business_id}/mappings":{"get":{"tags":["cbpulse","QuickBooks"],"summary":"List category mappings","description":"Return all VIP category → QB account mappings for a business.","operationId":"list_mappings_api_quickbooks__business_id__mappings_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__CategoryMappingResponse"},"title":"Response List Mappings Api Quickbooks  Business Id  Mappings Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","QuickBooks"],"summary":"Create category mapping","description":"Map a VIP transaction category to a QuickBooks account.","operationId":"create_mapping_api_quickbooks__business_id__mappings_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CategoryMappingCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CategoryMappingResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"409":{"description":"Duplicate mapping","content":{"application/json":{"example":{"detail":"Mapping for food/revenue already exists"}}}},"422":{"description":"Validation error"}}}},"/cbpulse/api/quickbooks/{business_id}/mappings/{mapping_id}":{"patch":{"tags":["cbpulse","QuickBooks"],"summary":"Update category mapping","description":"Update a VIP→QB category mapping's target account or notes.","operationId":"update_mapping_api_quickbooks__business_id__mappings__mapping_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"mapping_id","in":"path","required":true,"schema":{"type":"string","title":"Mapping Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CategoryMappingUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CategoryMappingResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Mapping not found","content":{"application/json":{"example":{"detail":"Mapping not found"}}}},"422":{"description":"Validation error"}}},"delete":{"tags":["cbpulse","QuickBooks"],"summary":"Delete category mapping","description":"Remove a VIP→QB category mapping.","operationId":"delete_mapping_api_quickbooks__business_id__mappings__mapping_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"mapping_id","in":"path","required":true,"schema":{"type":"string","title":"Mapping Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"Mapping not found","content":{"application/json":{"example":{"detail":"Mapping not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/quickbooks/{business_id}/pnl-comparison":{"get":{"tags":["cbpulse","QuickBooks"],"summary":"P&L comparison: VIP vs QuickBooks","description":"Side-by-side comparison of VIP computed P&L vs QB P&L snapshot for a given date range. Shows variance in dollars and percentage.","operationId":"pnl_comparison_api_quickbooks__business_id__pnl_comparison_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"Start date (YYYY-MM-DD)","title":"Start Date"},"description":"Start date (YYYY-MM-DD)"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"End date (YYYY-MM-DD)","title":"End Date"},"description":"End date (YYYY-MM-DD)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PnLComparisonResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/reports/daily-ops-digest":{"get":{"tags":["cbpulse","generated-reports"],"summary":"Get Daily Ops Digest","operationId":"get_daily_ops_digest_api_reports_daily_ops_digest_get","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","description":"YYYY-MM-DD","title":"Date"},"description":"YYYY-MM-DD"},{"name":"audience","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/reports/generate/daily-ops-digest":{"post":{"tags":["cbpulse","generated-reports"],"summary":"Generate Daily Ops Digest","operationId":"generate_daily_ops_digest_api_reports_generate_daily_ops_digest_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/reports/generate/inventory-snapshot":{"post":{"tags":["cbpulse","generated-reports"],"summary":"Generate Inventory Snapshot","operationId":"generate_inventory_snapshot_api_reports_generate_inventory_snapshot_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/reports/generate/labor-period":{"post":{"tags":["cbpulse","generated-reports"],"summary":"Generate Labor Period","operationId":"generate_labor_period_api_reports_generate_labor_period_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/reports/generate/weekly-revenue-intel":{"post":{"tags":["cbpulse","generated-reports"],"summary":"Generate Weekly Revenue Intel","operationId":"generate_weekly_revenue_intel_api_reports_generate_weekly_revenue_intel_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/reports/inventory-snapshot":{"get":{"tags":["cbpulse","generated-reports"],"summary":"Get Inventory Snapshot","operationId":"get_inventory_snapshot_api_reports_inventory_snapshot_get","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","description":"YYYY-MM-DD","title":"Date"},"description":"YYYY-MM-DD"},{"name":"audience","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/reports/labor-period":{"get":{"tags":["cbpulse","generated-reports"],"summary":"Get Labor Period","operationId":"get_labor_period_api_reports_labor_period_get","parameters":[{"name":"period_start","in":"query","required":true,"schema":{"type":"string","description":"YYYY-MM-DD","title":"Period Start"},"description":"YYYY-MM-DD"},{"name":"period_end","in":"query","required":true,"schema":{"type":"string","description":"YYYY-MM-DD","title":"Period End"},"description":"YYYY-MM-DD"},{"name":"audience","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/reports/render-and-send":{"post":{"tags":["cbpulse","reports"],"summary":"Render and send a config-driven report","description":"Accepts a JSON payload (typically from CBCron), fetches data from internal service functions, renders HTML using a named template, and delivers via SMTP or CBFront. Localhost-only (CBCron → 127.0.0.1).","operationId":"render_and_send_api_reports_render_and_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ReportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ReportResponse"}}}},"400":{"description":"Bad request — invalid report name, template, or parameters"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/reports/weekly-revenue-intel":{"get":{"tags":["cbpulse","generated-reports"],"summary":"Get Weekly Revenue Intel","operationId":"get_weekly_revenue_intel_api_reports_weekly_revenue_intel_get","parameters":[{"name":"week_start","in":"query","required":true,"schema":{"type":"string","description":"YYYY-MM-DD (Monday)","title":"Week Start"},"description":"YYYY-MM-DD (Monday)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/signals":{"get":{"tags":["cbpulse","Signals"],"summary":"List marketing signals","description":"Return marketing signals visible to the current user with optional filters for status, severity, signal_type, and business_id. Results are RLS-filtered and ordered by creation date descending. Supports limit/offset pagination.","operationId":"list_signals_api_signals_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity"}},{"name":"signal_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signal Type"}},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SignalListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to requested business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/signals/summary":{"get":{"tags":["cbpulse","Signals"],"summary":"Signal summary counts","description":"Return signal counts by status and severity for dashboard display. Results are RLS-filtered to businesses the current user can access.","operationId":"signal_summary_api_signals_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SignalSummaryResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/signals/thresholds/{business_id}":{"get":{"tags":["cbpulse","Signals"],"summary":"List thresholds for a business","description":"Return all signal thresholds configured for a business. Requires manager+ role and RLS access to the business.","operationId":"list_thresholds_api_signals_thresholds__business_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__ThresholdResponse"},"title":"Response List Thresholds Api Signals Thresholds  Business Id  Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Signals"],"summary":"Create a signal threshold","description":"Create a new signal threshold for a business. Each business can have one threshold per signal_type + metric_name combination. Requires admin+ role.","operationId":"create_threshold_api_signals_thresholds__business_id__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ThresholdCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ThresholdResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to business","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"409":{"description":"Threshold already exists for this signal_type + metric_name","content":{"application/json":{"example":{"detail":"Threshold for low_occupancy/occupancy_rate already exists"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/signals/thresholds/{threshold_id}":{"put":{"tags":["cbpulse","Signals"],"summary":"Update a signal threshold","description":"Partially update a signal threshold. Only provided fields are changed. Requires admin+ role.","operationId":"update_threshold_api_signals_thresholds__threshold_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"threshold_id","in":"path","required":true,"schema":{"type":"string","title":"Threshold Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ThresholdUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ThresholdResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to threshold's business","content":{"application/json":{"example":{"detail":"No access to this threshold"}}}},"404":{"description":"Threshold not found","content":{"application/json":{"example":{"detail":"Threshold not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"delete":{"tags":["cbpulse","Signals"],"summary":"Delete a signal threshold","description":"Permanently delete a signal threshold. Requires admin+ role.","operationId":"delete_threshold_api_signals_thresholds__threshold_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"threshold_id","in":"path","required":true,"schema":{"type":"string","title":"Threshold Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to threshold's business","content":{"application/json":{"example":{"detail":"No access to this threshold"}}}},"404":{"description":"Threshold not found","content":{"application/json":{"example":{"detail":"Threshold not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/signals/{signal_id}":{"get":{"tags":["cbpulse","Signals"],"summary":"Get signal by ID","description":"Retrieve a single marketing signal by its ID. Returns 404 if not found, 403 if the user does not have access to the signal's business.","operationId":"get_signal_api_signals__signal_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"signal_id","in":"path","required":true,"schema":{"type":"string","title":"Signal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SignalResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"No access to this signal's business","content":{"application/json":{"example":{"detail":"No access to this signal"}}}},"404":{"description":"Signal not found","content":{"application/json":{"example":{"detail":"Signal not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/signals/{signal_id}/acknowledge":{"post":{"tags":["cbpulse","Signals"],"summary":"Acknowledge a signal","description":"Transition a signal from NEW to ACKNOWLEDGED. Requires manager+ role. Returns 400 if the signal is not in NEW status.","operationId":"acknowledge_signal_api_signals__signal_id__acknowledge_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"signal_id","in":"path","required":true,"schema":{"type":"string","title":"Signal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SignalResponse"}}}},"400":{"description":"Signal not in acknowledgeable status","content":{"application/json":{"example":{"detail":"Cannot acknowledge signal in status 'actioned'"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to signal","content":{"application/json":{"example":{"detail":"No access to this signal"}}}},"404":{"description":"Signal not found","content":{"application/json":{"example":{"detail":"Signal not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/signals/{signal_id}/action":{"post":{"tags":["cbpulse","Signals"],"summary":"Action a signal","description":"Transition a signal to ACTIONED status with optional notes. Cannot action an already-actioned or dismissed signal. Requires manager+ role.","operationId":"action_signal_api_signals__signal_id__action_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"signal_id","in":"path","required":true,"schema":{"type":"string","title":"Signal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ActionSignalRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SignalResponse"}}}},"400":{"description":"Signal cannot be actioned","content":{"application/json":{"example":{"detail":"Signal is already actioned"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to signal","content":{"application/json":{"example":{"detail":"No access to this signal"}}}},"404":{"description":"Signal not found","content":{"application/json":{"example":{"detail":"Signal not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/signals/{signal_id}/dismiss":{"post":{"tags":["cbpulse","Signals"],"summary":"Dismiss a signal","description":"Transition a signal to DISMISSED status with optional reason. Cannot dismiss an already-actioned or dismissed signal. Requires manager+ role.","operationId":"dismiss_signal_api_signals__signal_id__dismiss_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"signal_id","in":"path","required":true,"schema":{"type":"string","title":"Signal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__DismissSignalRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SignalResponse"}}}},"400":{"description":"Signal cannot be dismissed","content":{"application/json":{"example":{"detail":"Signal is already dismissed"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions or no access to signal","content":{"application/json":{"example":{"detail":"No access to this signal"}}}},"404":{"description":"Signal not found","content":{"application/json":{"example":{"detail":"Signal not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/social/posts":{"post":{"tags":["cbpulse","Social"],"summary":"Record a post CB pushed to Vista","description":"Capture the record of a post CB pushed to Vista — the ORIGINAL copy plus the Vista post id returned on push. This is the CB Loom learning-loop capture foundation (cbpulse#505): the generation skill calls this right after pushing to Vista so the later approved-vs-original diff and performance loop have data. Created with `status: pushed`. Manager role or above required.","operationId":"record_post_api_social_posts_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PostCreate"}}}},"responses":{"201":{"description":"Post recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__social__PostResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least manager role"},"404":{"description":"No brand with this slug"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"get":{"tags":["cbpulse","Social"],"summary":"List recorded pushed posts","description":"List recorded social posts (newest first). Filter by `brand` and/or `status`. Manager role or above required.","operationId":"list_posts_api_social_posts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"brand","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a brand slug","title":"Brand"},"description":"Filter to a brand slug"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a status","title":"Status"},"description":"Filter to a status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max rows to return","default":50,"title":"Limit"},"description":"Max rows to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__src__api__routes__social__PostResponse"},"title":"Response List Posts Api Social Posts Get"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least manager role"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/social/posts/{post_id}":{"get":{"tags":["cbpulse","Social"],"summary":"Get one recorded post","description":"Fetch a single recorded social post by id. Manager role or above.","operationId":"get_post_api_social_posts__post_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","title":"Post Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__social__PostResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least manager role"},"404":{"description":"No post with this id"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/social/validate":{"post":{"tags":["cbpulse","Social"],"summary":"Validate a social post against the CB Loom guardrails","description":"Run the deterministic Phase-1 guardrail checks against a proposed social post and return a structured pass/fail result. The copy-generation skill calls this BEFORE pushing to Vista; any error-severity violation (`ok: false`) is a hard gate. Manager role or above required.\n\n### Checks (Phase 1)\n- **errors** (fail the post): `em_dash`, `semicolon`, `banned_word`, `banned_phrase`, `hashtag_count`, `brand_unknown`, `platform_not_allowed`, `workflow_gid_missing`, `nosj_marketing`\n- **warnings** (advisory): `emoji_count`, `weak_hook`, `length`, `photo_brand_mismatch`\n\n### Response Interpretation for LLMs\n- If `ok` is false, fix every error-severity violation before pushing\n- Warnings should be addressed but do not block the push","operationId":"validate_social_post_api_social_validate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ValidateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ValidateResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires at least manager role"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/summaries/daily":{"get":{"tags":["cbpulse","Summaries"],"summary":"Get latest daily summary","description":"Return the most recent AI-generated daily executive summary. Includes narrative text, structured sections, portfolio revenue, labor cost, health score, and active alert count. Requires admin+ role.","operationId":"get_latest_summary_api_summaries_daily_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SummaryResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"No summaries available","content":{"application/json":{"example":{"detail":"No summaries available"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/summaries/daily/{date}":{"get":{"tags":["cbpulse","Summaries"],"summary":"Get daily summary by date","description":"Return the AI-generated summary for a specific date (YYYY-MM-DD format). Returns 404 if no summary exists for that date. Requires admin+ role.","operationId":"get_summary_by_date_api_summaries_daily__date__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"date","in":"path","required":true,"schema":{"type":"string","title":"Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SummaryResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"404":{"description":"No summary for date","content":{"application/json":{"example":{"detail":"No summary for 2026-02-15"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/summaries/generate":{"post":{"tags":["cbpulse","Summaries"],"summary":"Trigger summary generation","description":"Manually trigger AI summary generation for a specific date (defaults to today). Calls the CBAI service to produce the narrative. Requires sysop role.","operationId":"trigger_summary_generation_api_summaries_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__summaries__GenerateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SummaryResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Sysop role required","content":{"application/json":{"example":{"detail":"Sysop role required"}}}},"422":{"description":"Validation error"}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/summaries/history":{"get":{"tags":["cbpulse","Summaries"],"summary":"Get summary history","description":"Return recent daily summaries ordered by date descending. Default limit is 7, max 30. Requires admin+ role.","operationId":"get_summary_history_api_summaries_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":30,"default":7,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__SummaryResponse"},"title":"Response Get Summary History Api Summaries History Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Admin role required","content":{"application/json":{"example":{"detail":"Admin role required"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/budgets":{"get":{"tags":["cbpulse","Supply Budgets"],"summary":"List all supply budgets","operationId":"list_all_budgets_api_supply_budgets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__SupplyBudgetOut"},"type":"array","title":"Response List All Budgets Api Supply Budgets Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/supply/budgets/{business_id}":{"get":{"tags":["cbpulse","Supply Budgets"],"summary":"List budgets for a business","operationId":"list_budgets_for_business_api_supply_budgets__business_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__SupplyBudgetOut"},"title":"Response List Budgets For Business Api Supply Budgets  Business Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/budgets/{business_id}/{year_month}":{"put":{"tags":["cbpulse","Supply Budgets"],"summary":"Set or update monthly supply budget","operationId":"upsert_budget_api_supply_budgets__business_id___year_month__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}},{"name":"year_month","in":"path","required":true,"schema":{"type":"string","title":"Year Month"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__BudgetSetBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyBudgetOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/inventory/dashboard/executive":{"get":{"tags":["cbpulse","Supply Inventory"],"summary":"Executive supply dashboard","operationId":"executive_dashboard_api_supply_inventory_dashboard_executive_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Executive Dashboard Api Supply Inventory Dashboard Executive Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/supply/inventory/dashboard/{business_id}":{"get":{"tags":["cbpulse","Supply Inventory"],"summary":"Property supply dashboard","operationId":"property_dashboard_api_supply_inventory_dashboard__business_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"path","required":true,"schema":{"type":"string","title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Property Dashboard Api Supply Inventory Dashboard  Business Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/inventory/low-stock":{"get":{"tags":["cbpulse","Supply Inventory"],"summary":"Items below reorder threshold","operationId":"list_low_stock_api_supply_inventory_low_stock_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Low Stock Api Supply Inventory Low Stock Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/inventory/search":{"get":{"tags":["cbpulse","Supply Inventory"],"summary":"Search supply requests and Clover mappings","operationId":"search_inventory_api_supply_inventory_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","description":"Search string","title":"Q"},"description":"Search string"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Search Inventory Api Supply Inventory Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/inventory/stock":{"get":{"tags":["cbpulse","Supply Inventory"],"summary":"List Clover stock levels","operationId":"list_stock_api_supply_inventory_stock_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Stock Api Supply Inventory Stock Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/orders":{"post":{"tags":["cbpulse","Supply Orders"],"summary":"Create supply order","operationId":"create_supply_order_api_supply_orders_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyOrderCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyOrderOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"get":{"tags":["cbpulse","Supply Orders"],"summary":"List supply orders","operationId":"list_supply_orders_api_supply_orders_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"vendor_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__SupplyOrderOut"},"title":"Response List Supply Orders Api Supply Orders Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/orders/{order_id}":{"get":{"tags":["cbpulse","Supply Orders"],"summary":"Get supply order","operationId":"get_supply_order_api_supply_orders__order_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyOrderOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"patch":{"tags":["cbpulse","Supply Orders"],"summary":"Update supply order","operationId":"update_supply_order_api_supply_orders__order_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyOrderUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyOrderOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/requests":{"post":{"tags":["cbpulse","Supply Requests"],"summary":"Create supply request","operationId":"create_supply_request_api_supply_requests_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyRequestCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"get":{"tags":["cbpulse","Supply Requests"],"summary":"List supply requests","operationId":"list_supply_requests_api_supply_requests_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"urgency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Urgency"}},{"name":"category_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Id"}},{"name":"purpose","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purpose"}},{"name":"requested_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested By"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__SupplyRequestOut"},"title":"Response List Supply Requests Api Supply Requests Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/requests/mine":{"get":{"tags":["cbpulse","Supply Requests"],"summary":"My supply requests","operationId":"my_supply_requests_api_supply_requests_mine_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__SupplyRequestOut"},"type":"array","title":"Response My Supply Requests Api Supply Requests Mine Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/supply/requests/pending":{"get":{"tags":["cbpulse","Supply Requests"],"summary":"Pending supply requests (approved, not ordered)","operationId":"pending_supply_requests_api_supply_requests_pending_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__SupplyRequestOut"},"type":"array","title":"Response Pending Supply Requests Api Supply Requests Pending Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/supply/requests/{request_id}":{"get":{"tags":["cbpulse","Supply Requests"],"summary":"Get supply request","operationId":"get_supply_request_api_supply_requests__request_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/requests/{request_id}/approve":{"post":{"tags":["cbpulse","Supply Requests"],"summary":"Approve supply request","operationId":"approve_supply_request_api_supply_requests__request_id__approve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/requests/{request_id}/reject":{"post":{"tags":["cbpulse","Supply Requests"],"summary":"Reject supply request","operationId":"reject_supply_request_api_supply_requests__request_id__reject_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__supply_schemas__RejectBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyRequestOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/shipments":{"post":{"tags":["cbpulse","Supply Shipments"],"summary":"Create supply shipment","operationId":"create_supply_shipment_api_supply_shipments_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyShipmentCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyShipmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"get":{"tags":["cbpulse","Supply Shipments"],"summary":"List supply shipments","operationId":"list_supply_shipments_api_supply_shipments_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"route_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Route Type"}},{"name":"carrier","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Carrier"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__SupplyShipmentOut"},"title":"Response List Supply Shipments Api Supply Shipments Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/shipments/overdue":{"get":{"tags":["cbpulse","Supply Shipments"],"summary":"List overdue shipments","operationId":"list_overdue_shipments_api_supply_shipments_overdue_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__SupplyShipmentOut"},"type":"array","title":"Response List Overdue Shipments Api Supply Shipments Overdue Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/supply/shipments/{shipment_id}":{"get":{"tags":["cbpulse","Supply Shipments"],"summary":"Get supply shipment","operationId":"get_supply_shipment_api_supply_shipments__shipment_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"shipment_id","in":"path","required":true,"schema":{"type":"string","title":"Shipment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyShipmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/shipments/{shipment_id}/status":{"post":{"tags":["cbpulse","Supply Shipments"],"summary":"Update shipment status","operationId":"update_shipment_status_api_supply_shipments__shipment_id__status_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"shipment_id","in":"path","required":true,"schema":{"type":"string","title":"Shipment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ShipmentStatusUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyShipmentOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/supply/vendors":{"get":{"tags":["cbpulse","Supply Vendors"],"summary":"List active supply vendors","description":"Return active vendors ordered by name.","operationId":"list_supply_vendors_api_supply_vendors_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__SupplyVendorOut"},"type":"array","title":"Response List Supply Vendors Api Supply Vendors Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["cbpulse","Supply Vendors"],"summary":"Create supply vendor","operationId":"create_supply_vendor_api_supply_vendors_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyVendorCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyVendorOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/supply/vendors/categories":{"get":{"tags":["cbpulse","Supply Vendors"],"summary":"List supply categories","description":"Return all categories ordered by purpose, then sort_order.","operationId":"list_supply_categories_api_supply_vendors_categories_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__SupplyCategoryOut"},"type":"array","title":"Response List Supply Categories Api Supply Vendors Categories Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/supply/vendors/{vendor_id}":{"get":{"tags":["cbpulse","Supply Vendors"],"summary":"Get supply vendor","operationId":"get_supply_vendor_api_supply_vendors__vendor_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"vendor_id","in":"path","required":true,"schema":{"type":"string","title":"Vendor Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyVendorOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"patch":{"tags":["cbpulse","Supply Vendors"],"summary":"Update supply vendor","operationId":"update_supply_vendor_api_supply_vendors__vendor_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"vendor_id","in":"path","required":true,"schema":{"type":"string","title":"Vendor Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyVendorUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__SupplyVendorOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/system/jobs":{"get":{"tags":["cbpulse","System"],"summary":"List job schedules","description":"Return all configured background job schedules with their interval, last run time, next run time, and enabled status. Requires sysop role.","operationId":"list_jobs_api_system_jobs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__JobScheduleResponse"},"type":"array","title":"Response List Jobs Api System Jobs Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Sysop role required","content":{"application/json":{"example":{"detail":"Sysop role required"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/system/jobs/{job_name}/run":{"post":{"tags":["cbpulse","System"],"summary":"Run a job manually","description":"Manually trigger execution of a specific background job by name. Returns the job result including records processed and duration. Requires sysop role.","operationId":"run_job_manually_api_system_jobs__job_name__run_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_name","in":"path","required":true,"schema":{"type":"string","title":"Job Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__JobRunResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Sysop role required","content":{"application/json":{"example":{"detail":"Sysop role required"}}}},"404":{"description":"Job not found","content":{"application/json":{"example":{"detail":"Job 'unknown_job' not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/system/tick":{"post":{"tags":["cbpulse","System"],"summary":"Execute system tick","description":"Cron-triggered endpoint that queues due background jobs and returns immediately. Jobs run sequentially in a background task so the cron caller doesn't have to wait on multi-minute syncs (cbpulse#414 — previously cbcron's 120s timeout was killing syncs that ran 3+ min). Per-job progress lands in JobExecutionORM. Concurrent ticks are coalesced: if a previous background tick is still running, this call returns ``jobs_executed=0`` with ``status='skipped'`` rather than spawning overlapping runs that would race on the SQLite writer lock. Authenticates via X-API-Key.","operationId":"tick_api_system_tick_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TickResponse"}}}},"401":{"description":"Invalid tick API key","content":{"application/json":{"example":{"detail":"Invalid tick API key"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/tickets":{"get":{"tags":["cbpulse","Tickets"],"summary":"List maintenance tickets","description":"Return maintenance tickets with optional filters for status, priority, business, assignee, and category. Supports pagination via limit/offset. RLS-filtered by user's business access.","operationId":"list_tickets_api_tickets_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"priority","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Priority"}},{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"assigned_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__TicketResponse"},"title":"Response List Tickets Api Tickets Get"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"No access to this business"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Tickets"],"summary":"Create a maintenance ticket","description":"Report a new maintenance issue at a property. Status is set to 'open'. Supports priority, category, location, and photo attachments. Creates an audit log entry. Requires manager+ role.","operationId":"create_ticket_api_tickets_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CreateTicketRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TicketResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","title"],"msg":"Field required","type":"missing"}]}}}}}}},"/cbpulse/api/tickets/{ticket_id}":{"get":{"tags":["cbpulse","Tickets"],"summary":"Get ticket by ID","description":"Retrieve a single maintenance ticket by its ID. Returns 404 if not found, 403 if the user lacks access to the ticket's business.","operationId":"get_ticket_api_tickets__ticket_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TicketResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"No access to this ticket"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"example":{"detail":"Ticket not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"put":{"tags":["cbpulse","Tickets"],"summary":"Update ticket fields","description":"Partially update a ticket's priority, description, category, location, or status. Cannot update a closed ticket. At least one field must be provided. Creates an audit log entry. Requires manager+ role.","operationId":"update_ticket_api_tickets__ticket_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UpdateTicketRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TicketResponse"}}}},"400":{"description":"Invalid update","content":{"application/json":{"example":{"detail":"Cannot update a closed ticket"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"example":{"detail":"Ticket not found"}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body"],"msg":"Field required","type":"missing"}]}}}}}}},"/cbpulse/api/tickets/{ticket_id}/assign":{"post":{"tags":["cbpulse","Tickets"],"summary":"Assign a ticket","description":"Assign a ticket to a user and set status to 'in_progress'. Cannot assign resolved or closed tickets. Creates an audit log entry. Requires manager+ role.","operationId":"assign_ticket_api_tickets__ticket_id__assign_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__tickets__AssignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TicketResponse"}}}},"400":{"description":"Invalid state transition","content":{"application/json":{"example":{"detail":"Cannot assign ticket in status 'closed'"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"example":{"detail":"Ticket not found"}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","assigned_to"],"msg":"Field required","type":"missing"}]}}}}}}},"/cbpulse/api/tickets/{ticket_id}/close":{"post":{"tags":["cbpulse","Tickets"],"summary":"Close a ticket","description":"Close a ticket (typically after resolution is verified). Cannot close an already-closed ticket. Creates an audit log entry. Requires manager+ role.","operationId":"close_ticket_api_tickets__ticket_id__close_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TicketResponse"}}}},"400":{"description":"Invalid state transition","content":{"application/json":{"example":{"detail":"Ticket is already closed"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"example":{"detail":"Ticket not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/tickets/{ticket_id}/photos":{"post":{"tags":["cbpulse","Tickets"],"summary":"Append a photo reference to a ticket","description":"Anyone in scope at the property. The list is append-only from here; the reference is whatever the caller stores — the Apex app stores a cbfiles object reference and signs it on read.","operationId":"add_ticket_photo_api_tickets__ticket_id__photos_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PhotoAdd"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TicketResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/tickets/{ticket_id}/resolve":{"post":{"tags":["cbpulse","Tickets"],"summary":"Resolve a ticket","description":"Mark a ticket as resolved with optional resolution notes. Cannot resolve a closed or already-resolved ticket. Creates an audit log entry. Requires manager+ role.","operationId":"resolve_ticket_api_tickets__ticket_id__resolve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__src__api__routes__tickets__ResolveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TicketResponse"}}}},"400":{"description":"Invalid state transition","content":{"application/json":{"example":{"detail":"Cannot resolve a closed ticket"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"example":{"detail":"Not authenticated"}}}},"403":{"description":"Insufficient permissions","content":{"application/json":{"example":{"detail":"Manager role required"}}}},"404":{"description":"Ticket not found","content":{"application/json":{"example":{"detail":"Ticket not found"}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"detail":[{"loc":["body","notes"],"msg":"Input should be a valid string","type":"string_type"}]}}}}}}},"/cbpulse/api/users":{"get":{"tags":["cbpulse","Users"],"summary":"List all users","description":"Return all users with their system role and functional roles. Requires admin or sysop role.","operationId":"list_users_api_users_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__UserSummary"},"type":"array","title":"Response List Users Api Users Get"}}}},"401":{"description":"Missing or invalid authentication token","content":{"application/json":{"example":{"detail":"Invalid or expired token"}}}},"403":{"description":"Insufficient permissions — requires admin or sysop role","content":{"application/json":{"example":{"detail":"Requires at least admin role"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/users/notification-preferences":{"get":{"tags":["cbpulse","Users"],"summary":"List every user's notification preferences (admin)","description":"Admin roster of who receives which alert emails. Read-only: users without a stored preference row are reported with their effective defaults and has_explicit_preferences=false, and no row is created.","operationId":"list_notification_preferences_api_users_notification_preferences_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__UserPreferenceRow"},"type":"array","title":"Response List Notification Preferences Api Users Notification Preferences Get"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires admin role"}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/users/{user_id}/notification-preferences":{"get":{"tags":["cbpulse","Users"],"summary":"Get another user's notification preferences (admin)","description":"Same shape as the self-serve GET /api/notifications/preferences, but for an arbitrary user. Creates the default row if none exists.","operationId":"get_user_notification_preferences_api_users__user_id__notification_preferences_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PreferenceResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires admin role"},"404":{"description":"User not found","content":{"application/json":{"example":{"detail":"User not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"put":{"tags":["cbpulse","Users"],"summary":"Update another user's notification preferences (admin)","description":"Same shape as the self-serve PUT /api/notifications/preferences, but for an arbitrary user. Only provided fields are changed; the row is created first if the user had none.","operationId":"update_user_notification_preferences_api_users__user_id__notification_preferences_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PreferenceUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PreferenceResponse"}}}},"401":{"description":"Not authenticated"},"403":{"description":"Requires admin role"},"404":{"description":"User not found","content":{"application/json":{"example":{"detail":"User not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/users/{user_id}/roles":{"patch":{"tags":["cbpulse","Users"],"summary":"Update user functional roles","description":"Replace a user's functional roles. Requires admin or sysop role.","operationId":"update_user_roles_api_users__user_id__roles_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__RoleUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UserSummary"}}}},"401":{"description":"Missing or invalid authentication token","content":{"application/json":{"example":{"detail":"Invalid or expired token"}}}},"403":{"description":"Insufficient permissions — requires admin or sysop role","content":{"application/json":{"example":{"detail":"Requires at least admin role"}}}},"404":{"description":"User not found","content":{"application/json":{"example":{"detail":"User not found"}}}},"422":{"description":"Validation error (e.g. invalid functional role value)"}}}},"/cbpulse/api/v1/datasets":{"get":{"tags":["cbpulse","Datasets (Fabric)"],"summary":"List datasets exposed by this driver (Saint only)","operationId":"list_datasets_api_v1_datasets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Datasets Api V1 Datasets Get"}}}}}}},"/cbpulse/api/v1/datasets/{dataset_id}/rows":{"get":{"tags":["cbpulse","Datasets (Fabric)"],"summary":"Stream dataset rows as ndjson","description":"Stream all Saint rows for a dataset as newline-delimited JSON.\n\nConsumed by the cbmodels fabric materializer (httpx fetch + DuckDB\nread_json_auto). Column set is the spec whitelist — nothing else,\nnot even business_id, leaves this endpoint.","operationId":"stream_rows_api_v1_datasets__dataset_id__rows_get","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","title":"Dataset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/vehicles":{"get":{"tags":["cbpulse","Entity Compliance"],"summary":"List vehicles","description":"Return fleet vehicles, optionally filtered by owning entity / active flag.","operationId":"list_vehicles_api_vehicles_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"owning_entity_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owning Entity Id"}},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__VehicleResponse"},"title":"Response List Vehicles Api Vehicles Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Entity Compliance"],"summary":"Create a vehicle","description":"Add a fleet vehicle. Manager+ only. Audited.","operationId":"create_vehicle_api_vehicles_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__VehicleCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__VehicleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/vehicles/{vehicle_id}":{"get":{"tags":["cbpulse","Entity Compliance"],"summary":"Get vehicle detail","description":"Return a vehicle with its compliance items and derived status.","operationId":"get_vehicle_api_vehicles__vehicle_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"vehicle_id","in":"path","required":true,"schema":{"type":"string","title":"Vehicle Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Vehicle Api Vehicles  Vehicle Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"patch":{"tags":["cbpulse","Entity Compliance"],"summary":"Update a vehicle","description":"Patch vehicle fields. Manager+ only. Audited.","operationId":"update_vehicle_api_vehicles__vehicle_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"vehicle_id","in":"path","required":true,"schema":{"type":"string","title":"Vehicle Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__VehicleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__VehicleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/vendor-templates":{"get":{"tags":["cbpulse","Vendor Templates"],"summary":"List Templates","operationId":"list_templates_api_vendor_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TemplateListResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["cbpulse","Vendor Templates"],"summary":"Create Template","operationId":"create_template_api_vendor_templates_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TemplateCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/vendor-templates/{vendor_name}":{"get":{"tags":["cbpulse","Vendor Templates"],"summary":"Get Template","operationId":"get_template_api_vendor_templates__vendor_name__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"vendor_name","in":"path","required":true,"schema":{"type":"string","title":"Vendor Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"put":{"tags":["cbpulse","Vendor Templates"],"summary":"Update Template","operationId":"update_template_api_vendor_templates__vendor_name__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"vendor_name","in":"path","required":true,"schema":{"type":"string","title":"Vendor Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TemplateUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__TemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/vista-profiles":{"get":{"tags":["cbpulse","Vista Social Profiles"],"summary":"List Profiles","operationId":"list_profiles_api_vista_profiles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbpulse__ProfileResponse"},"type":"array","title":"Response List Profiles Api Vista Profiles Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["cbpulse","Vista Social Profiles"],"summary":"Create Profile","operationId":"create_profile_api_vista_profiles_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ProfileCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ProfileResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/cbpulse/api/vista-profiles/{profile_id}":{"patch":{"tags":["cbpulse","Vista Social Profiles"],"summary":"Update Profile","operationId":"update_profile_api_vista_profiles__profile_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ProfileUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__ProfileResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"delete":{"tags":["cbpulse","Vista Social Profiles"],"summary":"Delete Profile","operationId":"delete_profile_api_vista_profiles__profile_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/webhooks/boldsign":{"post":{"tags":["cbpulse","Webhooks"],"summary":"Receive BoldSign webhook event","description":"Receives signing events from BoldSign (Sent, Viewed, Signed, Completed, Declined, Expired, Reassigned). Validates the webhook, stores the payload, and updates the corresponding Airtable record asynchronously via cbairtable.","operationId":"receive_webhook_api_webhooks_boldsign_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Receive Webhook Api Webhooks Boldsign Post"}}}}}}},"/cbpulse/api/webhooks/boldsign/status":{"get":{"tags":["cbpulse","Webhooks"],"summary":"BoldSign webhook health check","description":"Returns webhook receiver status, event count, and last event timestamp.","operationId":"webhook_status_api_webhooks_boldsign_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Webhook Status Api Webhooks Boldsign Status Get"}}}}}}},"/cbpulse/api/webhooks/meta":{"get":{"tags":["cbpulse","Webhooks"],"summary":"Verify Meta webhook","description":"Meta sends a GET request with hub.mode, hub.verify_token, and hub.challenge to verify webhook ownership. Returns the challenge string if the token matches.","operationId":"verify_webhook_api_webhooks_meta_get","parameters":[{"name":"hub.mode","in":"query","required":true,"schema":{"type":"string","title":"Hub.Mode"}},{"name":"hub.verify_token","in":"query","required":true,"schema":{"type":"string","title":"Hub.Verify Token"}},{"name":"hub.challenge","in":"query","required":true,"schema":{"type":"string","title":"Hub.Challenge"}}],"responses":{"200":{"description":"Verification successful — returns hub.challenge","content":{"application/json":{"schema":{"type":"string","title":"Response Verify Webhook Api Webhooks Meta Get"}},"text/plain":{"example":"1234567890"}}},"403":{"description":"Verification failed — token mismatch","content":{"application/json":{"example":{"detail":"Invalid verify token"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","Webhooks"],"summary":"Receive Meta webhook event","description":"Receives incoming message events from Meta (Facebook Messenger, Instagram DM). Currently logs the payload and returns 200. Message normalisation and inbox ingestion are stubbed for future implementation.","operationId":"receive_webhook_api_webhooks_meta_post","responses":{"200":{"description":"Webhook received","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Receive Webhook Api Webhooks Meta Post"},"example":{"status":"received"}}}}}}},"/cbpulse/api/writeups":{"get":{"tags":["cbpulse","HR"],"summary":"List write-ups","description":"Paginated write-up list with filters. RLS-filtered.","operationId":"list_writeups_api_writeups_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"business_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"}},{"name":"employee_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employee Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbpulse__WriteUpResponse"},"title":"Response List Writeups Api Writeups Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"post":{"tags":["cbpulse","HR"],"summary":"Create write-up","description":"Create a formal write-up document. Validates discipline level against progressive ladder. Requires manager+ role.","operationId":"create_writeup_api_writeups_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__CreateWriteUpRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteUpResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/writeups/{writeup_id}":{"get":{"tags":["cbpulse","HR"],"summary":"Get write-up detail","operationId":"get_writeup_api_writeups__writeup_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteUpResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}},"patch":{"tags":["cbpulse","HR"],"summary":"Update a draft write-up","description":"Only draft writeups can be edited. version_id bumps on every write. Clients SHOULD send If-Match: W/\"<version>\" from the last GET response; mismatch returns 409 (cbpulse#319).","operationId":"update_writeup_api_writeups__writeup_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"If-Match"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__UpdateWriteUpRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteUpResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/writeups/{writeup_id}/approve":{"post":{"tags":["cbpulse","HR"],"summary":"Approve a draft write-up (ready to send)","operationId":"approve_writeup_api_writeups__writeup_id__approve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteUpResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/writeups/{writeup_id}/cancel":{"post":{"tags":["cbpulse","HR"],"summary":"Cancel a write-up (allowed before sent)","operationId":"cancel_writeup_api_writeups__writeup_id__cancel_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteUpResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/writeups/{writeup_id}/document-url":{"get":{"tags":["cbpulse","HR"],"summary":"Mint a fresh PDF URL for a write-up's envelope","description":"Returns a time-limited PDF URL for the writeup's BoldSign / DocuSign envelope. Resolved at request time — no stale presigned URLs in the DB. Archived envelopes return a cbesig presigned URL on files.campaignbrain.dev (typically 1h TTL). Live envelopes return a cbpulse proxy URL that streams the PDF from cbesig on each request. cbpulse#428.","operationId":"get_writeup_document_url_api_writeups__writeup_id__document_url_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteUpDocumentURLResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/writeups/{writeup_id}/document.pdf":{"get":{"tags":["cbpulse","HR"],"summary":"Stream a write-up's PDF (live BoldSign envelopes)","description":"Proxies the PDF bytes from cbesig's ``/api/v1/documents/{id}/download`` endpoint. Used as the fallback target by ``/document-url`` for envelopes that aren't archived yet. cbpulse#428.","operationId":"stream_writeup_document_api_writeups__writeup_id__document_pdf_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}}],"responses":{"200":{"description":"PDF stream","content":{"application/pdf":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/writeups/{writeup_id}/recall":{"post":{"tags":["cbpulse","HR"],"summary":"Recall an in-flight writeup (manager)","description":"Manager-scoped recall for writeups whose envelope is out but signing isn't complete — typically when a recipient asks for a correction before signing. Revokes the BoldSign envelope (best-effort) and flips status to 'cancelled'. Returns 409 'writeup_state_conflict' for any status outside {sent, delivered, partially_signed}; use the sysop /admin/writeups/{id}/force-cancel for inconsistent-state cleanup. Resolves cbpulse#433.","operationId":"recall_writeup_api_writeups__writeup_id__recall_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteupRecallRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteupRecallResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/writeups/{writeup_id}/remind":{"post":{"tags":["cbpulse","HR"],"summary":"Send BoldSign reminder for an outstanding writeup","description":"Re-emails the pending signer(s) on this writeup's envelope without invalidating signatures already collected. Allowed when status is 'sent', 'delivered', or 'partially_signed' and the row has a ``boldsign_doc_id``; signed / cancelled / expired return 409. Resolves cbpulse#385 + cbpulse#430.","operationId":"remind_writeup_api_writeups__writeup_id__remind_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbpulse__WriteupRemindRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteupRemindResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/writeups/{writeup_id}/send":{"post":{"tags":["cbpulse","HR"],"summary":"Send write-up to employee via BoldSign (routed through cbesig)","description":"Dispatches the Universal Disciplinary Form template via cbesig (Manager → Witness → Employee signing order) and stores the envelope id on the writeup. Witness identity is supplied at dispatch time. cbesig owns the BoldSign API key and template registry; cbpulse produces the request body and consumes the document_id. Resolves cbpulse#357, cbpulse#360.","operationId":"send_writeup_api_writeups__writeup_id__send_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}},{"name":"X-On-Behalf-Of-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of-Email"}},{"name":"X-On-Behalf-Of-Name","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-On-Behalf-Of-Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteUpSendRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteUpResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/api/writeups/{writeup_id}/void":{"post":{"tags":["cbpulse","HR"],"summary":"Void / discard an undispatched write-up (manager)","description":"Terminal discard for a writeup that was never dispatched to BoldSign — the manager approved it but the envelope never went out (or it's still a draft). Flips status to 'voided'. Allowed ONLY when boldsign_doc_id is null AND status is draft/approved. A writeup that already carries a boldsign_doc_id is rejected 409 'writeup_envelope_exists' — use /writeups/{id}/recall (manager) or /admin/writeups/{id}/force-cancel (sysop), which also revoke the live envelope, so a real BoldSign document is never silently stranded. Voiding an already-voided writeup is an idempotent 200 replay. Resolves cbpulse#456.","operationId":"void_writeup_api_writeups__writeup_id__void_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"writeup_id","in":"path","required":true,"schema":{"type":"string","title":"Writeup Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteupVoidRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__WriteUpResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HTTPValidationError"}}}}}}},"/cbpulse/health":{"get":{"tags":["cbpulse","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HealthResponse"}}}}}}},"/cbpulse/health/detailed":{"get":{"tags":["cbpulse","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__HealthResponse"}}}}}}},"/cbpulse/prime":{"get":{"tags":["cbpulse","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__PrimeResponse"}}}}}}},"/cbpulse/status":{"get":{"tags":["cbpulse","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbpulse__StatusResponse"}}}}}}},"/cbquickbooks/api/v1/datasets":{"get":{"tags":["cbquickbooks","Datasets (Fabric)"],"summary":"List datasets exposed by this driver (stub v1)","description":"Return the Fabric dataset catalog this driver publishes — the dataset id, a `kind_hint`, the column schema, row count, freshness/health, and a `flavor` descriptor that tells **cbmodels** how to interpret the data. **v1 stub:** currently advertises a single `qb.accounts` (balances) dataset with `health: stale` and a zero row count until the real QuickBooks pull is wired up.","operationId":"list_datasets_api_v1_datasets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Datasets Api V1 Datasets Get"}}}}}}},"/cbquickbooks/api/v1/datasets/{dataset_id}/rows":{"get":{"tags":["cbquickbooks","Datasets (Fabric)"],"summary":"Stream dataset rows as ndjson (stub v1)","description":"Stream the rows of a Fabric dataset as newline-delimited JSON (`application/x-ndjson`), one row object per line, for consumption by **cbmodels**' cash-position fabric integration. Only `qb.accounts` is recognised; any other `{dataset_id}` returns `404`. **v1 stub:** the stream is currently empty — the real QuickBooks pull lands in a later step. The columns are described by `GET /api/v1/datasets`.","operationId":"stream_rows_api_v1_datasets__dataset_id__rows_get","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","title":"Dataset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__HTTPValidationError"}}}}}}},"/cbquickbooks/api/v1/{slug}/entity/{entity}":{"post":{"tags":["cbquickbooks","CBQUICKBOOKS"],"summary":"Create an entity","description":"Create a new QuickBooks entity of the allowlisted type `{entity}` for the account named by `{slug}`. The request body is the QuickBooks entity payload, forwarded verbatim to Intuit. Returns the created object (including its assigned `Id`) as raw QuickBooks JSON. Disallowed entity names are rejected with `400`.","operationId":"create_entity_api_v1__slug__entity__entity__post","parameters":[{"name":"entity","in":"path","required":true,"schema":{"type":"string","title":"Entity"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Entity Api V1  Slug  Entity  Entity  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__HTTPValidationError"}}}}}}},"/cbquickbooks/api/v1/{slug}/entity/{entity}/{entity_id}":{"get":{"tags":["cbquickbooks","CBQUICKBOOKS"],"summary":"Get an entity by id","description":"Fetch a single QuickBooks entity (e.g. `Invoice`, `Customer`, `Vendor`) by its QuickBooks `Id` for the account named by `{slug}`. `{entity}` must be a canonical, allowlisted entity name (case sensitive) or the call is rejected with `400` before any upstream request. Returns the raw QuickBooks JSON object.","operationId":"get_entity_api_v1__slug__entity__entity___entity_id__get","parameters":[{"name":"entity","in":"path","required":true,"schema":{"type":"string","title":"Entity"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Entity Api V1  Slug  Entity  Entity   Entity Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__HTTPValidationError"}}}}}},"post":{"tags":["cbquickbooks","CBQUICKBOOKS"],"summary":"Update an entity (sparse)","description":"Apply a **sparse update** to an existing QuickBooks entity. Only the fields present in the body are changed. QuickBooks requires the entity `Id` inside the body for sparse updates; if it is absent it is injected from the `{entity_id}` path parameter. `{entity}` must be allowlisted. Returns the updated object as raw QuickBooks JSON.","operationId":"update_entity_api_v1__slug__entity__entity___entity_id__post","parameters":[{"name":"entity","in":"path","required":true,"schema":{"type":"string","title":"Entity"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Entity Api V1  Slug  Entity  Entity   Entity Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__HTTPValidationError"}}}}}},"delete":{"tags":["cbquickbooks","CBQUICKBOOKS"],"summary":"Delete an entity","description":"Delete a QuickBooks entity by `Id` for the account named by `{slug}`. `{entity}` must be an allowlisted entity name. Returns the raw QuickBooks delete response. Note that QuickBooks treats many entities as deactivated rather than hard-deleted; behaviour follows Intuit's API for the given entity type.","operationId":"delete_entity_api_v1__slug__entity__entity___entity_id__delete","parameters":[{"name":"entity","in":"path","required":true,"schema":{"type":"string","title":"Entity"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Entity Api V1  Slug  Entity  Entity   Entity Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__HTTPValidationError"}}}}}}},"/cbquickbooks/api/v1/{slug}/query":{"post":{"tags":["cbquickbooks","CBQUICKBOOKS"],"summary":"Run a QB SQL-like query","description":"Forward a QuickBooks Query Language (SQL-like) statement to Intuit's `/query` endpoint for the account named by `{slug}` and return the raw QuickBooks JSON response. The caller must pass all three permission layers for the slug. Token refresh is handled transparently; a `502` is returned if QuickBooks itself rejects the query.","operationId":"run_query_api_v1__slug__query_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__QueryRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Run Query Api V1  Slug  Query Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__HTTPValidationError"}}}}}}},"/cbquickbooks/api/v1/{slug}/refresh":{"post":{"tags":["cbquickbooks","CBQUICKBOOKS"],"summary":"Force an immediate token rotation","description":"Force an immediate OAuth token rotation for the account named by `{slug}`, regardless of how much life the current access token has left. Exchanges the stored refresh token with Intuit, persists the new bundle, and returns the new access/refresh expiry timestamps. Returns `409` if the account is not connected or its refresh token has expired (re-seed required), or `502` if Intuit rejects the exchange. The caller must pass all three permission layers for the slug.","operationId":"force_refresh_api_v1__slug__refresh_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Force Refresh Api V1  Slug  Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__HTTPValidationError"}}}}}}},"/cbquickbooks/api/v1/{slug}/status":{"get":{"tags":["cbquickbooks","CBQUICKBOOKS"],"summary":"Connection and token expiry report","description":"Report the OAuth connection state for the account named by `{slug}`: whether it is connected, its environment (sandbox/production), and how much life remains on the access and refresh tokens. Use this to decide whether a `/refresh` or a full re-seed is needed before making passthrough calls. The caller must pass all three permission layers for the slug.","operationId":"get_status_api_v1__slug__status_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Status Api V1  Slug  Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__HTTPValidationError"}}}}}}},"/cbquickbooks/health":{"get":{"tags":["cbquickbooks","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__HealthResponse"}}}}}}},"/cbquickbooks/health/detailed":{"get":{"tags":["cbquickbooks","Health"],"summary":"Detailed health with per-account token status","description":"Returns the CBApp standard detailed-health payload extended with an `accounts` map. Each registered slug reports whether it is `connected`, its `environment` (sandbox/production), and — when connected — `access_expires_in_seconds` and `refresh_expires_in_days`. Use this to spot accounts whose refresh token is approaching its absolute expiry and needs a re-seed.","operationId":"_health_detailed_with_accounts_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response  Health Detailed With Accounts Health Detailed Get"}}}}}}},"/cbquickbooks/prime":{"get":{"tags":["cbquickbooks","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__PrimeResponse"}}}}}}},"/cbquickbooks/status":{"get":{"tags":["cbquickbooks","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbquickbooks__StatusResponse"}}}}}}},"/cbradio-qa/api":{"get":{"summary":"Api Root","description":"Root API endpoint with version and status information.","operationId":"api_root_api_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["cbradio-qa"]}},"/cbradio-qa/api/auth/me":{"get":{"tags":["cbradio-qa","authentication"],"summary":"Get current user","description":"Return the profile of the currently authenticated user.\n\n    Requires a valid Bearer token. Returns user ID, username, and role.","operationId":"read_users_me_api_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__User"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/auth/token":{"post":{"tags":["cbradio-qa","authentication"],"summary":"Login for access token","description":"Authenticate with username and password to obtain a JWT access token.\n\n    The token is valid for the configured expiration period and must be\n    included as a Bearer token in subsequent authenticated requests.","operationId":"login_for_access_token_api_auth_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Body_login_for_access_token_api_auth_token_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Token"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/health":{"get":{"summary":"Api Health Compat","description":"Legacy health endpoint — redirects to CBApp /health.","operationId":"api_health_compat_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["cbradio-qa"]}},"/cbradio-qa/api/protected":{"get":{"summary":"Protected Route","description":"Example of a protected route.","operationId":"protected_route_api_protected_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}],"tags":["cbradio-qa"]}},"/cbradio-qa/api/v1/admin/reextract-gaps":{"post":{"tags":["cbradio-qa","admin"],"summary":"Reprocess gap documents through cbingest","description":"Finds documents with stored OCR text but zero extracted rates, and sends them through cbingest for re-extraction.\n\n**Bounded by `limit`.** Every document is a synchronous LLM round trip, and the live gap pool is 3,579 — an unbounded run is a request that cannot finish, which is why this endpoint had never been run. `remaining` in the response says how many are left.\n\nDocuments are taken in order of what recovering them is worth: current cycle first, then rate cards, then in-network stations. A 2020 agreement is not worth an LLM call while a 2026 rate card for an in-network station is waiting.","operationId":"reextract_gaps_api_v1_admin_reextract_gaps_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"How many documents to re-extract in this call.","default":25,"title":"Limit"},"description":"How many documents to re-extract in this call."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ReextractGapsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/admin/sync-airtable-rates":{"post":{"tags":["cbradio-qa","admin"],"summary":"Trigger 2026 rate card sync pipeline","description":"Spawns the unified sync pipeline as a background subprocess.\n\nThe pipeline:\n1. Fetches 2026 rate card attachments from Airtable\n2. Downloads files to disk\n3. Creates document records\n4. Runs OCR + rate extraction\n5. Backfills rate_card records\n\nReturns 409 if a sync is already running.","operationId":"trigger_sync_api_v1_admin_sync_airtable_rates_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__SyncTriggerRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SyncTriggerResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/admin/sync-airtable-rates/logs/{job_id}":{"get":{"tags":["cbradio-qa","admin"],"summary":"Get sync pipeline log","description":"Returns the full log file content for a given job.","operationId":"get_sync_log_api_v1_admin_sync_airtable_rates_logs__job_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/admin/sync-airtable-rates/status":{"get":{"tags":["cbradio-qa","admin"],"summary":"Get sync pipeline status","description":"Returns the current or last run status and summary counts.","operationId":"get_sync_status_api_v1_admin_sync_airtable_rates_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SyncStatusResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/agreements":{"get":{"tags":["cbradio-qa","agreements"],"summary":"List agreements","description":"Retrieve a paginated list of agreements with optional filters.","operationId":"list_agreements_api_v1_agreements_get","parameters":[{"name":"station_callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station","title":"Station Callsign"},"description":"Filter by station"},{"name":"lifecycle_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state (comma-separated for multiple)","title":"Lifecycle State"},"description":"Filter by state (comma-separated for multiple)"},{"name":"agreement_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by type","title":"Agreement Type"},"description":"Filter by type"},{"name":"created_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by creator","title":"Created By"},"description":"Filter by creator"},{"name":"expiring_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Find agreements expiring before","title":"Expiring Before"},"description":"Find agreements expiring before"},{"name":"expiring_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Find agreements expiring after","title":"Expiring After"},"description":"Find agreements expiring after"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"Sort field","default":"created_at","title":"Sort"},"description":"Sort field"},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AgreementListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","agreements"],"summary":"Create agreement","description":"Create a new agreement (starts in draft state).","operationId":"create_agreement_api_v1_agreements_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AgreementCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AgreementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/agreements/expiring":{"get":{"tags":["cbradio-qa","agreements"],"summary":"Get expiring agreements","description":"Get agreements expiring within a time window (for renewal alerts).","operationId":"get_expiring_agreements_api_v1_agreements_expiring_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Days until expiration","default":30,"title":"Days"},"description":"Days until expiration"},{"name":"lifecycle_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"Lifecycle State"},"description":"Filter by state"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/agreements/{agreement_id}":{"get":{"tags":["cbradio-qa","agreements"],"summary":"Get agreement detail","description":"Get full agreement details including documents and audit trail.","operationId":"get_agreement_api_v1_agreements__agreement_id__get","parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AgreementDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","agreements"],"summary":"Update agreement","description":"Update agreement fields (not lifecycle state).","operationId":"update_agreement_api_v1_agreements__agreement_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AgreementUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AgreementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/agreements/{agreement_id}/documents":{"post":{"tags":["cbradio-qa","agreements"],"summary":"Upload agreement document","description":"Upload a document to an agreement.","operationId":"upload_document_api_v1_agreements__agreement_id__documents_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}},{"name":"document_type","in":"query","required":true,"schema":{"type":"string","description":"Type of document","title":"Document Type"},"description":"Type of document"},{"name":"file_name","in":"query","required":true,"schema":{"type":"string","description":"Original filename","title":"File Name"},"description":"Original filename"},{"name":"file_url","in":"query","required":true,"schema":{"type":"string","description":"CBFiles URL after upload","title":"File Url"},"description":"CBFiles URL after upload"},{"name":"file_size","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"File size in bytes","title":"File Size"},"description":"File size in bytes"},{"name":"mime_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"MIME type","title":"Mime Type"},"description":"MIME type"},{"name":"description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional description","title":"Description"},"description":"Optional description"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__DocumentUploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/agreements/{agreement_id}/documents/{document_id}":{"delete":{"tags":["cbradio-qa","agreements"],"summary":"Delete agreement document","description":"Soft delete a document from an agreement.","operationId":"delete_document_api_v1_agreements__agreement_id__documents__document_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}},{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/agreements/{agreement_id}/transition":{"post":{"tags":["cbradio-qa","agreements"],"summary":"Transition agreement state","description":"Move agreement through lifecycle states.","operationId":"transition_agreement_api_v1_agreements__agreement_id__transition_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AgreementTransition"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AgreementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/buyer-agencies":{"get":{"tags":["cbradio-qa","buyer-agencies"],"summary":"List buyer agencies","description":"List media buying agencies with optional filters.\n\n    **Filters:**\n    - `status`: Filter by agency status (active, inactive)\n    - `search`: Search by name (partial match)\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 500.","operationId":"list_buyer_agencies_api_v1_buyer_agencies_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AgencyStatus"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name (partial match)","title":"Search"},"description":"Search by name (partial match)"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Political cycle filter (e.g. '2025-2026'). When provided, aggregate fields (total_buys, total_volume, client_count, last_buy_date) scope to buys in that cycle only. Absent = all-time totals.","title":"Cycle"},"description":"Political cycle filter (e.g. '2025-2026'). When provided, aggregate fields (total_buys, total_volume, client_count, last_buy_date) scope to buys in that cycle only. Absent = all-time totals."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__BuyerAgencySummary"},"title":"Response List Buyer Agencies Api V1 Buyer Agencies Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","buyer-agencies"],"summary":"Create buyer agency","description":"Create a new buyer agency. Requires authentication.","operationId":"create_buyer_agency_api_v1_buyer_agencies_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BuyerAgencyCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BuyerAgency"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/buyer-agencies/count":{"get":{"tags":["cbradio-qa","buyer-agencies"],"summary":"Count buyer agencies","description":"Return the total number of buyer agencies matching the given filters.\n\n    Accepts the same filter parameters as the list endpoint (status, search)\n    but returns only a count instead of full records.","operationId":"count_buyer_agencies_api_v1_buyer_agencies_count_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AgencyStatus"},{"type":"null"}],"title":"Status"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Count Buyer Agencies Api V1 Buyer Agencies Count Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/buyer-agencies/{agency_id}":{"get":{"tags":["cbradio-qa","buyer-agencies"],"summary":"Get buyer agency details","description":"Get a buyer agency by ID with associated radio buys.","operationId":"get_buyer_agency_api_v1_buyer_agencies__agency_id__get","parameters":[{"name":"agency_id","in":"path","required":true,"schema":{"type":"string","title":"Agency Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BuyerAgencyWithRadioBuys"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","buyer-agencies"],"summary":"Update buyer agency","description":"Partially update a buyer agency. Requires authentication.","operationId":"update_buyer_agency_api_v1_buyer_agencies__agency_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agency_id","in":"path","required":true,"schema":{"type":"string","title":"Agency Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BuyerAgencyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BuyerAgency"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","buyer-agencies"],"summary":"Delete buyer agency","description":"Delete a buyer agency. Requires authentication. Fails if agency has associated radio buys.","operationId":"delete_buyer_agency_api_v1_buyer_agencies__agency_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agency_id","in":"path","required":true,"schema":{"type":"string","title":"Agency Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/clients":{"get":{"tags":["cbradio-qa","clients"],"summary":"List clients","description":"List political clients (candidates, PACs, campaigns) with optional filters.\n\n    **Filters:**\n    - `state`: Filter by state abbreviation (e.g., TX, CA)\n    - `search`: Search by name (partial match)\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 500.","operationId":"list_clients_api_v1_clients_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state abbreviation","title":"State"},"description":"Filter by state abbreviation"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name (partial match)","title":"Search"},"description":"Search by name (partial match)"},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientType"},{"type":"null"}],"description":"Filter by client type","title":"Type"},"description":"Filter by client type"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientStatus"},{"type":"null"}],"description":"Filter by client status","title":"Status"},"description":"Filter by client status"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Political cycle filter (e.g. '2025-2026'). When provided, aggregate fields (total_buys, total_spend, last_buy_date) scope to buys in that cycle only. Absent = all-time totals.","title":"Cycle"},"description":"Political cycle filter (e.g. '2025-2026'). When provided, aggregate fields (total_buys, total_spend, last_buy_date) scope to buys in that cycle only. Absent = all-time totals."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__ClientSummary"},"title":"Response List Clients Api V1 Clients Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","clients"],"summary":"Create client","description":"Create a new political client. Requires authentication.","operationId":"create_client_api_v1_clients_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ClientCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Client"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/clients/count":{"get":{"tags":["cbradio-qa","clients"],"summary":"Count clients","description":"Return the total number of clients matching the given filters.\n\n    Accepts the same filter parameters as the list endpoint (state, search,\n    type, status) but returns only a count instead of full records.","operationId":"count_clients_api_v1_clients_count_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientType"},{"type":"null"}],"title":"Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientStatus"},{"type":"null"}],"title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Count Clients Api V1 Clients Count Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/clients/{client_id}":{"get":{"tags":["cbradio-qa","clients"],"summary":"Get client details","description":"Get a client by ID with associated radio buys.","operationId":"get_client_api_v1_clients__client_id__get","parameters":[{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ClientWithRadioBuys"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","clients"],"summary":"Update client","description":"Partially update a client. Requires authentication.","operationId":"update_client_api_v1_clients__client_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ClientUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Client"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","clients"],"summary":"Delete client","description":"Delete a client. Requires authentication. Fails if client has associated radio buys.","operationId":"delete_client_api_v1_clients__client_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/competitive/detected-airings":{"get":{"tags":["cbradio-qa","competitive"],"summary":"Verified radio airings detected by cbtuner (read-only passthrough)","description":"Recurring radio spots cbtuner has captured + transcribed across its monitored\n    stations, proxied from cbintel over the mesh for the RCI competitive panel\n    (cbradio#293). cbradio-ui can't reach cbintel directly; this is the bridge.\n\n    Returns **recurring-spot clusters** (cbtuner's `/discover`): each is one spot\n    detected looping across stations, with `n_times` (temporal recurrence),\n    `n_stations` (reach), the labelled `kind` (ad|program|unknown), the `stations`\n    it aired on, and the distinct `egresses` used to capture them.\n\n    **Tiering:** these are `source=cbtuner, confidence=verified` — a context strip\n    only. Never blend with modeled (AdImpact) or actual (OPIF) spend.\n\n    **Filters:** `callsigns` narrows to clusters that aired on the requested\n    stations; `state` is informational (cbtuner's monitored corpus is the MI set).\n    `kind=all` returns ad + program + unknown.\n\n    **Degrades gracefully:** if cbtuner/cbintel is unavailable, returns\n    `available=false` with an empty `airings` list (never errors the panel).","operationId":"detected_airings_api_v1_competitive_detected_airings_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Informational geography tag (cbtuner corpus is MI).","title":"State"},"description":"Informational geography tag (cbtuner corpus is MI)."},{"name":"callsigns","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated callsigns to narrow to (e.g. WJR-AM,WWJ-AM).","title":"Callsigns"},"description":"Comma-separated callsigns to narrow to (e.g. WJR-AM,WWJ-AM)."},{"name":"kind","in":"query","required":false,"schema":{"type":"string","description":"ad | program | unknown | all","default":"ad","title":"Kind"},"description":"ad | program | unknown | all"},{"name":"min_stations","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Min distinct stations a spot must air on.","default":1,"title":"Min Stations"},"description":"Min distinct stations a spot must air on."},{"name":"window","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":100,"description":"Most-recent transcript rows to cluster.","default":1500,"title":"Window"},"description":"Most-recent transcript rows to cluster."}],"responses":{"200":{"description":"Detected-airing clusters with source/confidence tiering","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/competitive/opif-ingest":{"post":{"tags":["cbradio-qa","competitive"],"summary":"Trigger an OPIF political-file ingest run (HMAC-authed, in-process)","description":"Runs the FCC OPIF crawl **inside the cbradio process** (cbradio#294) — it\n    reads its own station table and writes `opif_filing` via the service's own\n    data layer (no external process touches the DB), and reaches cbintel +\n    cbingest over the mesh. Designed to be fired by **cbcron** on a schedule.\n\n    Auth: HMAC-SHA256 over the raw body with `OPIF_INGEST_HMAC_SECRET`, hex in\n    `X-Cbradio-Signature` (`sha256=` prefix accepted) — same pattern as\n    `/internal/sync_gdrive`. cbcron signs its fixed payload.\n\n    `dry_run=true` runs synchronously and returns a parsed sample (no write).\n    Otherwise the crawl runs as a background task (returns immediately; results\n    are logged) — incremental, since already-ingested orders are dedup-skipped.","operationId":"opif_ingest_api_v1_competitive_opif_ingest_post","parameters":[{"name":"x-cbradio-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cbradio-Signature"}}],"responses":{"200":{"description":"Run summary (sync sample for dry_run; started ack otherwise)","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/competitive/opif-spend":{"get":{"tags":["cbradio-qa","competitive"],"summary":"FCC OPIF political-file spend (radio ground-truth, source=opif)","description":"Structured political-ad spend parsed from FCC OPIF political files\n    (cbradio#294), keyed on `fcc_facility_id`. These are **actual** filings —\n    `source=opif, confidence=actual` — and are the radio ground-truth backbone\n    for the RCI competitive panel.\n\n    Returns `competitive_spend`-shaped rows the panel consumes unchanged:\n    advertiser (name/party/type), office, market_or_station, flight window,\n    `amount_est` (gross), `occurrences` (spots), `raw_ref` (link to the filing),\n    plus `needs_review` for low-confidence extractions.\n\n    **Tiering:** never blend with modeled (AdImpact) or verified (cbtuner) — each\n    is segmented by source/confidence.\n\n    **Filters:** `state`, `callsigns`, `campaign_year`, `since` (ISO flight-start).\n    Degrades gracefully to `available=false` with empty `rows` if the store is\n    empty / the ingestion hasn't run.","operationId":"opif_spend_api_v1_competitive_opif_spend_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state filter.","title":"State"},"description":"Two-letter state filter."},{"name":"callsigns","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated callsigns to narrow to.","title":"Callsigns"},"description":"Comma-separated callsigns to narrow to."},{"name":"campaign_year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filing/campaign year.","title":"Campaign Year"},"description":"Filing/campaign year."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only flights on/after this ISO date (YYYY-MM-DD).","title":"Since"},"description":"Only flights on/after this ISO date (YYYY-MM-DD)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}}],"responses":{"200":{"description":"OPIF competitive_spend rows (source=opif/confidence=actual)","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/consultants":{"get":{"tags":["cbradio-qa","consultants"],"summary":"List consultants","description":"List sales consultants with optional filters.\n\n    **Filters:**\n    - `status`: Filter by consultant status (active, inactive)\n    - `search`: Search by name (partial match)\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 500.","operationId":"list_consultants_api_v1_consultants_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ConsultantStatus"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name (partial match)","title":"Search"},"description":"Search by name (partial match)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__ConsultantSummary"},"title":"Response List Consultants Api V1 Consultants Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","consultants"],"summary":"Create consultant","description":"Create a new consultant. Requires authentication.","operationId":"create_consultant_api_v1_consultants_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ConsultantCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Consultant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/consultants/count":{"get":{"tags":["cbradio-qa","consultants"],"summary":"Count consultants","description":"Return the total number of consultants matching the given filters.\n\n    Accepts the same filter parameters as the list endpoint (status, search)\n    but returns only a count instead of full records.","operationId":"count_consultants_api_v1_consultants_count_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ConsultantStatus"},{"type":"null"}],"title":"Status"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Count Consultants Api V1 Consultants Count Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/consultants/{consultant_id}":{"get":{"tags":["cbradio-qa","consultants"],"summary":"Get consultant details","description":"Get a consultant by ID with associated clients and radio buys.","operationId":"get_consultant_api_v1_consultants__consultant_id__get","parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ConsultantWithRelations"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","consultants"],"summary":"Update consultant","description":"Partially update a consultant. Requires authentication.","operationId":"update_consultant_api_v1_consultants__consultant_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ConsultantUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Consultant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","consultants"],"summary":"Delete consultant","description":"Delete a consultant. Requires authentication.","operationId":"delete_consultant_api_v1_consultants__consultant_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/consultants/{consultant_id}/clients/{client_id}":{"post":{"tags":["cbradio-qa","consultants"],"summary":"Associate consultant with client","description":"Create a consultant-client association. Requires authentication.","operationId":"add_client_to_consultant_api_v1_consultants__consultant_id__clients__client_id__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}},{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Client To Consultant Api V1 Consultants  Consultant Id  Clients  Client Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","consultants"],"summary":"Remove client from consultant","description":"Remove a consultant-client association. Requires authentication.","operationId":"remove_client_from_consultant_api_v1_consultants__consultant_id__clients__client_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}},{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/consultants/{consultant_id}/performance":{"get":{"tags":["cbradio-qa","consultants"],"summary":"Get consultant commission performance metrics","description":"Return aggregated commission performance metrics for a consultant across\n    all radio buys.\n\n    Merges data from commission rules and the radio_buy_consultant junction\n    table, preferring commission_rule data when both exist. Returns totals\n    for gross revenue, commissions earned, profit, average commission rate,\n    and per-buy breakdowns including hold status.","operationId":"get_consultant_performance_api_v1_consultants__consultant_id__performance_get","parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/coverage-gaps":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Identify coverage gaps","description":"Identifies districts without adequate network coverage.\n\n**Filters:**\n- `state_code` - Filter to a specific state\n- `district_type` - Filter to specific district type\n- `threshold` - Coverage threshold percentage (default 50)\n\n**Returns:**\nDistricts where no in-network station meets the coverage threshold.","operationId":"get_coverage_gaps_api_v1_coverage_gaps_get","parameters":[{"name":"state_code","in":"query","required":false,"schema":{"type":"string","description":"Filter to state (two-letter code)","title":"State Code"},"description":"Filter to state (two-letter code)"},{"name":"district_type","in":"query","required":false,"schema":{"type":"string","description":"District type to analyze","default":"congressional","title":"District Type"},"description":"District type to analyze"},{"name":"threshold","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Coverage threshold %","default":50,"title":"Threshold"},"description":"Coverage threshold %"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/coverage/listenable/districts/{geoid}/stations":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Listenable stations covering a district (Geo%)","description":"Stations that **listenably** cover a district, valued in DataSoup Geo% and\nordered Geo% desc. This is the first-party per-district coverage surface\n(cbradio#317). Coverage is read from listenable Geo% ONLY — FCC contour\nenclosure and Nielsen DMA membership are never used (they are banned proxies,\ncbradio#319). A station covers only when its listenable Geo% exceeds\n`min_geo_pct`; measured non-covering (geo_pct=0) stations are excluded.\n\n**Path Parameters:**\n- `geoid` - District id, e.g. `MI-HD-042` (case-insensitive)\n\n**Query Parameters:**\n- `min_geo_pct` - Minimum listenable Geo% to count as covering (default 0 = any positive)\n- `in_network_only` - Only in-network / Salem stations","operationId":"get_listenable_district_stations_api_v1_coverage_listenable_districts__geoid__stations_get","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","title":"Geoid"}},{"name":"min_geo_pct","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum listenable Geo% to count as covering","default":0.0,"title":"Min Geo Pct"},"description":"Minimum listenable Geo% to count as covering"},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only in-network stations","default":false,"title":"In Network Only"},"description":"Only in-network stations"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/coverage/listenable/stations/{callsign}/districts":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Listenable districts covered by a station (Geo%)","description":"Districts a station **listenably** covers, valued in DataSoup Geo% and ordered\nGeo% desc — the station→district direction of the first-party coverage surface\n(cbradio#317). Same trust rule: listenable Geo% only, never contour enclosure or\nDMA membership.\n\n**Path Parameters:**\n- `callsign` - Station callsign, e.g. `WDTK` (case-insensitive)\n\n**Query Parameters:**\n- `min_geo_pct` - Minimum listenable Geo% to count as covering (default 0 = any positive)\n- `district_type` - Optional filter: congressional, state_senate, state_house, county","operationId":"get_listenable_station_districts_api_v1_coverage_listenable_stations__callsign__districts_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"min_geo_pct","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum listenable Geo% to count as covering","default":0.0,"title":"Min Geo Pct"},"description":"Minimum listenable Geo% to count as covering"},{"name":"district_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional district-type filter","title":"District Type"},"description":"Optional district-type filter"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/cycles":{"get":{"tags":["cbradio-qa","cycles"],"summary":"List political cycles","description":"Return all political cycles ordered by start year descending, then name.","operationId":"list_cycles_api_v1_cycles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["cbradio-qa","cycles"],"summary":"Create a political cycle","description":"Create a new political cycle (e.g., \"2026 Midterms\"). A cycle groups\n    radio buys by election period for reporting and CSV export.\n\n    Requires authentication.","operationId":"create_cycle_api_v1_cycles_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CycleCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/cycles/{cycle_id}":{"get":{"tags":["cbradio-qa","cycles"],"summary":"Get a political cycle","description":"Return a single political cycle by ID. Returns 404 if not found.","operationId":"get_cycle_api_v1_cycles__cycle_id__get","parameters":[{"name":"cycle_id","in":"path","required":true,"schema":{"type":"string","title":"Cycle Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"put":{"tags":["cbradio-qa","cycles"],"summary":"Update a political cycle","description":"Partially update a political cycle. Only provided fields are changed.\n\n    Requires authentication. Returns 404 if the cycle does not exist, or\n    400 if no fields are provided.","operationId":"update_cycle_api_v1_cycles__cycle_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"cycle_id","in":"path","required":true,"schema":{"type":"string","title":"Cycle Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CycleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","cycles"],"summary":"Delete a political cycle","description":"Delete a political cycle. Any radio buys linked to the cycle are unlinked\n    (their cycle_id is set to NULL) before the cycle is removed.\n\n    Requires authentication.","operationId":"delete_cycle_api_v1_cycles__cycle_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"cycle_id","in":"path","required":true,"schema":{"type":"string","title":"Cycle Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/cycles/{cycle_id}/export":{"get":{"tags":["cbradio-qa","cycles"],"summary":"Export commission data as CSV","description":"Export commission data for a cycle.\n\n**Formats:**\n- `detailed` — one row per commission rule per buy\n- `summary` — one row per buy\n- `accounting` — grouped by recipient across all buys","operationId":"export_cycle_csv_api_v1_cycles__cycle_id__export_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"cycle_id","in":"path","required":true,"schema":{"type":"string","title":"Cycle Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","description":"Export format: detailed, summary, accounting","default":"summary","title":"Format"},"description":"Export format: detailed, summary, accounting"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/cycles/{cycle_id}/summary":{"get":{"tags":["cbradio-qa","cycles"],"summary":"Get aggregated totals for a cycle","description":"Return aggregated financial totals for all radio buys in a cycle.\n\n    Includes total gross, total profit, commission totals (active vs. held),\n    and paid/unpaid buy counts.","operationId":"get_cycle_summary_api_v1_cycles__cycle_id__summary_get","parameters":[{"name":"cycle_id","in":"path","required":true,"schema":{"type":"string","title":"Cycle Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/datasets":{"get":{"tags":["cbradio-qa","Datasets (Fabric)"],"summary":"List datasets exposed by this driver","operationId":"list_datasets_api_v1_datasets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Datasets Api V1 Datasets Get"}}}}}}},"/cbradio-qa/api/v1/datasets/{dataset_id}/rows":{"get":{"tags":["cbradio-qa","Datasets (Fabric)"],"summary":"Stream dataset rows as ndjson","description":"Stream all rows for a dataset as newline-delimited JSON.\n\nConsumed by the cbmodels fabric materializer. Only whitelisted columns\nleave this endpoint. radio.rates is ~500K rows — streamed in pages so\nthe response never holds the full table in memory.","operationId":"stream_rows_api_v1_datasets__dataset_id__rows_get","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","title":"Dataset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/districts/batch-boundaries":{"post":{"tags":["cbradio-qa","coverage"],"summary":"Get boundaries for multiple districts","description":"Get GeoJSON boundaries for multiple districts in a single request.\n\n**Request:** district_type and comma-separated district IDs (max 50)\n\n**Response:** GeoJSON FeatureCollection with all boundaries.","operationId":"get_batch_district_boundaries_api_v1_districts_batch_boundaries_post","parameters":[{"name":"district_type","in":"query","required":true,"schema":{"type":"string","description":"Type: congressional, state_senate, state_house, county","title":"District Type"},"description":"Type: congressional, state_senate, state_house, county"},{"name":"district_ids","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated district IDs (max 50)","title":"District Ids"},"description":"Comma-separated district IDs (max 50)"},{"name":"simplify","in":"query","required":false,"schema":{"type":"boolean","description":"Return simplified geometries","default":false,"title":"Simplify"},"description":"Return simplified geometries"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/districts/counts":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Get district counts per state","description":"Returns the number of congressional, state senate, and state house districts\nfor a given state (or all states). Used by the frontend to generate district\nID lists for coverage map queries.","operationId":"get_district_counts_api_v1_districts_counts_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state code (e.g. MI). Omit for all states.","title":"State"},"description":"Two-letter state code (e.g. MI). Omit for all states."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/districts/county/list":{"get":{"tags":["cbradio-qa","coverage"],"summary":"List counties by state (lightweight)","description":"Returns county FIPS codes and names from local data. No external API call.","operationId":"list_counties_by_state_api_v1_districts_county_list_get","parameters":[{"name":"state","in":"query","required":true,"schema":{"type":"string","description":"Two-letter state code","title":"State"},"description":"Two-letter state code"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/districts/{district_id}/voter-totals":{"get":{"tags":["cbradio-qa","voters"],"summary":"Aggregated voter totals for any district type","description":"Returns total voter counts across a district, suitable as the\ndenominator for in-district CPE calculations (cbradio#228).\n\n**District types supported:**\n- `county` — direct lookup in `county_voter_data` by 5-digit FIPS.\n- `state_senate` / `state_house` / `congressional` — sums `county_voter_data`\n  weighted by the fraction of each constituent county that lies inside the\n  district. Constituent-county mapping is precomputed in cbdistricts.\n\n**Use this endpoint when:**\n- Computing in-district voter reach for CPE (sub-state proposals).\n- Showing total voter universe for a target district in the UI.\n\n**Pairs with `/stations/batch-coverage?district_id=...&district_type=...`** —\nmultiply this endpoint's `total_registered_voters` by each station's\n`station_in_district_pct` to get per-station in-district voter reach.","operationId":"get_district_voter_totals_api_v1_districts__district_id__voter_totals_get","parameters":[{"name":"district_id","in":"path","required":true,"schema":{"type":"string","title":"District Id"}},{"name":"district_type","in":"query","required":false,"schema":{"type":"string","pattern":"^(county|state_senate|state_house|congressional)$","description":"One of: county, state_senate, state_house, congressional.","default":"county","title":"District Type"},"description":"One of: county, state_senate, state_house, congressional."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__DistrictVoterTotalsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/districts/{district_type}/{district_id}/boundary":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Get district boundary GeoJSON","description":"Returns the GeoJSON boundary polygon for a district.\n\n**Path Parameters:**\n- `district_type` - One of: congressional, state_senate, state_house, county, state\n- `district_id` - District identifier (e.g., MI-07, MI-SD-24, 26065, MI)\n\n**Query Parameters:**\n- `simplify` - Return simplified geometry for faster rendering","operationId":"get_district_boundary_api_v1_districts__district_type___district_id__boundary_get","parameters":[{"name":"district_type","in":"path","required":true,"schema":{"type":"string","title":"District Type"}},{"name":"district_id","in":"path","required":true,"schema":{"type":"string","title":"District Id"}},{"name":"simplify","in":"query","required":false,"schema":{"type":"boolean","description":"Return simplified geometry","default":false,"title":"Simplify"},"description":"Return simplified geometry"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/districts/{district_type}/{district_id}/stations":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Get stations covering a district","description":"Returns all stations whose coverage contours intersect with a specific district.\n\n**Path Parameters:**\n- `district_type` - One of: congressional, state_senate, state_house, county\n- `district_id` - District identifier (e.g., MI-07, MI-SD-24, 26065)\n\n**Query Parameters:**\n- `min_coverage` - Minimum coverage percentage (0-100)\n- `threshold_only` - Only return stations meeting 50% threshold\n- `in_network_only` - Only return in-network stations\n- `sort_by` - Sort by: coverage, callsign, frequency","operationId":"get_district_stations_api_v1_districts__district_type___district_id__stations_get","parameters":[{"name":"district_type","in":"path","required":true,"schema":{"type":"string","title":"District Type"}},{"name":"district_id","in":"path","required":true,"schema":{"type":"string","title":"District Id"}},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"default":0,"title":"Min Coverage"}},{"name":"threshold_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only stations meeting 50% threshold","default":false,"title":"Threshold Only"},"description":"Only stations meeting 50% threshold"},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only in-network stations","default":false,"title":"In Network Only"},"description":"Only in-network stations"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by am or fm","title":"Service Type"},"description":"Filter by am or fm"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"AM only: day, night, or service","title":"Contour Type"},"description":"AM only: day, night, or service"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort by: coverage, callsign, frequency","default":"coverage","title":"Sort By"},"description":"Sort by: coverage, callsign, frequency"},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","description":"Sort order: asc, desc","default":"desc","title":"Sort Order"},"description":"Sort order: asc, desc"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/districts/{geoid}/voters":{"get":{"tags":["cbradio-qa","voters"],"summary":"Get voter data and covering stations for a district","description":"Returns voter data for a county (by FIPS code) along with all stations\nwhose broadcast contour covers it.\n\n**Reverse of the station endpoint:** given a county, find all stations that reach it.\n\n**Use this endpoint when:**\n- Evaluating which stations can reach a target county\n- Assessing voter universe for a geographic area\n- Planning multi-station buys to maximize county coverage","operationId":"get_district_voters_api_v1_districts__geoid__voters_get","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","title":"Geoid"}},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage percentage to include a station.","default":0.5,"title":"Min Coverage"},"description":"Minimum coverage percentage to include a station."},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"If true, only return in-network stations.","default":false,"title":"In Network Only"},"description":"If true, only return in-network stations."}],"responses":{"200":{"description":"District voter data returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__DistrictVoterResponse"}}}},"404":{"description":"No coverage data for this district","content":{"application/json":{"example":{"detail":"No station coverage data for district 99999"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/documents":{"get":{"tags":["cbradio-qa","documents"],"summary":"List documents","description":"Retrieve a paginated list of documents with optional filters.\n\n    Supports filtering by station callsign (partial match), document type,\n    file format, year, extraction status, and rate card type. Results are\n    ordered by callsign, then year descending, then filename.\n\n    **Filters:**\n    - `callsign`: Partial match on station callsign (case-insensitive)\n    - `type`: Document type (e.g. rate_card)\n    - `format`: File format (e.g. pdf, xlsx)\n    - `year`: Filing year\n    - `status`: Extraction status (pending, completed, failed, needs_review)\n    - `rate_card_type`: Rate card subtype\n\n    **Response:** Paginated object with `items`, `total`, `limit`, and `offset`.\n    Each item includes document metadata, extraction status, and confidence score.","operationId":"list_documents_api_v1_documents_get","parameters":[{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station callsign","title":"Callsign"},"description":"Filter by station callsign"},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by document type","title":"Type"},"description":"Filter by document type"},{"name":"format","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by file format","title":"Format"},"description":"Filter by file format"},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by year","title":"Year"},"description":"Filter by year"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by extraction status","title":"Status"},"description":"Filter by extraction status"},{"name":"rate_card_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by rate card type","title":"Rate Card Type"},"description":"Filter by rate card type"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/documents/by-format":{"get":{"tags":["cbradio-qa","documents"],"summary":"Get document counts by format","description":"Return document counts grouped by file format (e.g. pdf, xlsx, doc).\n\n    Results are ordered by count descending. Each row includes the\n    format name and its total count.","operationId":"documents_by_format_api_v1_documents_by_format_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/documents/by-status":{"get":{"tags":["cbradio-qa","documents"],"summary":"Get document counts by extraction status","description":"Return document counts grouped by extraction status.\n\n    Possible statuses are pending, completed, failed, and needs_review.\n    Results are ordered by count descending.","operationId":"documents_by_status_api_v1_documents_by_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/documents/by-type":{"get":{"tags":["cbradio-qa","documents"],"summary":"Get document counts by type","description":"Return document counts grouped by document type.\n\n    Each row includes the document type, total count, and the number of\n    documents with a completed extraction. Results are ordered by count\n    descending.","operationId":"documents_by_type_api_v1_documents_by_type_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/documents/by-year":{"get":{"tags":["cbradio-qa","documents"],"summary":"Get document counts by year","description":"Return document counts grouped by filing year.\n\n    Only documents with a non-null year are included. Results are ordered\n    by year descending (most recent first).","operationId":"documents_by_year_api_v1_documents_by_year_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/documents/count":{"get":{"tags":["cbradio-qa","documents"],"summary":"Count documents","description":"Return the total number of documents matching the given filters.\n\n    Accepts the same filter parameters as the list endpoint: callsign\n    (partial, case-insensitive), document type, extraction status, and year.\n\n    **Response:** Object with a single `count` field.","operationId":"count_documents_api_v1_documents_count_get","parameters":[{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callsign"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/documents/pending":{"get":{"tags":["cbradio-qa","documents"],"summary":"List pending documents","description":"Retrieve documents that are awaiting extraction, prioritized for processing.\n\n    Rate card documents are returned first, then other types. Within each\n    group, results are ordered by year descending and callsign.\n\n    **Response:** Array of document objects including file path information\n    needed for extraction processing. Limited to 500 results maximum.","operationId":"get_pending_documents_api_v1_documents_pending_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/documents/summary":{"get":{"tags":["cbradio-qa","documents"],"summary":"Get document summary statistics","description":"Return aggregate statistics across all documents in the database.\n\n    **Response fields:**\n    - `total_documents`: Total document count\n    - `unique_stations`: Number of distinct station callsigns\n    - `pending`, `completed`, `failed`, `needs_review`: Counts by extraction status\n    - `total_extracted_rates`: Sum of all extracted rate entries","operationId":"document_summary_api_v1_documents_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/documents/{document_id}":{"get":{"tags":["cbradio-qa","documents"],"summary":"Get document by ID","description":"Retrieve a single document record by its unique ID.\n\n    Returns all stored fields for the document, including metadata,\n    extraction status, confidence score, and file path information.\n\n    Returns 404 if the document ID does not exist.","operationId":"get_document_api_v1_documents__document_id__get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/documents/{document_id}/extracted-rates":{"get":{"tags":["cbradio-qa","documents"],"summary":"Get extracted rates for document","description":"Retrieve all advertising rates that were extracted from this document.\n\n    Returns rate entries from the rate table linked by source_document_id,\n    ordered by station callsign and duration. Numeric fields (gross_rate,\n    net_rate) are converted to floats for JSON serialization.\n\n    Returns an empty array if no rates have been extracted yet.","operationId":"get_document_extracted_rates_api_v1_documents__document_id__extracted_rates_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/documents/{document_id}/file":{"head":{"tags":["cbradio-qa","documents"],"summary":"Check document file metadata","description":"Return HTTP headers for the document file without downloading the body.\n\n    Useful for checking content type, file size, and filename before\n    initiating a full download. The response includes Content-Type,\n    Content-Length, and Content-Disposition headers.\n\n    Returns 404 if the document does not exist or the file is not found on disk.","operationId":"head_document_file_api_v1_documents__document_id__file_head","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"get":{"tags":["cbradio-qa","documents"],"summary":"Download document file","description":"Download the actual document file from disk.\n\n    Returns the file with the appropriate media type (PDF, image, Word,\n    Excel, etc.) and the original filename in the Content-Disposition header.\n    File paths are resolved using configurable rewrites to handle relocated\n    storage directories.\n\n    Returns 404 if the document does not exist or the file is not found on disk.","operationId":"get_document_file_api_v1_documents__document_id__file_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/fcc/lookup/{callsign}":{"get":{"tags":["cbradio-qa","fcc"],"summary":"Look up FCC station by callsign","description":"Look up FCC station data by callsign.\n\nHandles common callsign formats:\n- WABC (returns both AM and FM if they exist)\n- WABC-AM (returns specific service type)\n- WABC-FM","operationId":"lookup_by_callsign_api_v1_fcc_lookup__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__FCCStation"},"title":"Response Lookup By Callsign Api V1 Fcc Lookup  Callsign  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/fcc/partners":{"get":{"tags":["cbradio-qa","fcc"],"summary":"Discover potential partners","description":"List FCC stations that are NOT in our database - potential new partners.\n\nThese are licensed AM/FM stations from the FCC database that we don't\ncurrently have in our system. Useful for outreach and expanding the network.","operationId":"discover_partners_api_v1_fcc_partners_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"State"},"description":"Filter by state"},{"name":"service_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by AM or FM","title":"Service Code"},"description":"Filter by AM or FM"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Max results","default":100,"title":"Limit"},"description":"Max results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Skip N results","default":0,"title":"Offset"},"description":"Skip N results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__FCCStation"},"title":"Response Discover Partners Api V1 Fcc Partners Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/fcc/states":{"get":{"tags":["cbradio-qa","fcc"],"summary":"List states with FCC station counts","description":"Get count of FCC stations by state.","operationId":"get_states_summary_api_v1_fcc_states_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/fcc/stations":{"get":{"tags":["cbradio-qa","fcc"],"summary":"List FCC stations","description":"List all stations from the FCC LMS database with optional filters.\n\nThis is the complete registry of all licensed AM/FM stations in the US\nfrom the FCC's authoritative database.\n\n**Filters:**\n- `state`: Two-letter state code\n- `service_code`: AM or FM\n- `unmatched_only`: Only show stations not matched to our database (for partner discovery)\n- `callsign`: Search by callsign (partial match)","operationId":"list_fcc_stations_api_v1_fcc_stations_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state (e.g., TX, CA)","title":"State"},"description":"Filter by state (e.g., TX, CA)"},{"name":"service_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by AM or FM","title":"Service Code"},"description":"Filter by AM or FM"},{"name":"unmatched_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only show unmatched stations","default":false,"title":"Unmatched Only"},"description":"Only show unmatched stations"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search callsign (partial)","title":"Callsign"},"description":"Search callsign (partial)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Max results","default":100,"title":"Limit"},"description":"Max results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Skip N results","default":0,"title":"Offset"},"description":"Skip N results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__FCCStation"},"title":"Response List Fcc Stations Api V1 Fcc Stations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/fcc/stations/{facility_id}":{"get":{"tags":["cbradio-qa","fcc"],"summary":"Get FCC station by facility ID","description":"Get a specific FCC station by its FCC facility ID.","operationId":"get_fcc_station_api_v1_fcc_stations__facility_id__get","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","title":"Facility Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__FCCStation"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/fcc/stats":{"get":{"tags":["cbradio-qa","fcc"],"summary":"FCC import statistics","description":"Get statistics about FCC data import and matching.","operationId":"get_fcc_stats_api_v1_fcc_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__FCCStatsResponse"}}}}}}},"/cbradio-qa/api/v1/financials/import-legacy":{"post":{"tags":["cbradio-qa","financials"],"summary":"Import legacy buy records","description":"Accepts array of historical buy records with financial data. Idempotent — updates existing buys by radio_buy_id. The cycle query param sets political_cycle on records that don't specify their own.","operationId":"import_legacy_api_v1_financials_import_legacy_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Default political cycle for records without one","title":"Cycle"},"description":"Default political cycle for records without one"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__LegacyBuyImport"},"title":"Records"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/financials/reconciliation":{"get":{"tags":["cbradio-qa","financials"],"summary":"Financial reconciliation report","description":"Flags buys with mismatched splits, missing recipients, or unpaid status.","operationId":"get_reconciliation_api_v1_financials_reconciliation_get","parameters":[{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Political cycle filter","title":"Cycle"},"description":"Political cycle filter"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/financials/summary":{"get":{"tags":["cbradio-qa","financials"],"summary":"Financial summary across radio buys","description":"Returns aggregated P&L, paid/unpaid counts, rural/non-rural breakdown, and per-buy details.\n\n**Source filter (cbradio#230):** defaults to `tracker` to surface only the canonical Buy Tracker spreadsheet rows (`id LIKE 'tracker-%'`). Set `source=ui` for the UI-conversion UUID rows alone, or `source=all` to see both (debugging). Until the UI conversion path stops emitting placeholder dates, `tracker` is the operator-facing canonical view.","operationId":"get_financial_summary_api_v1_financials_summary_get","parameters":[{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Political cycle filter, e.g. '2025-2026'","title":"Cycle"},"description":"Political cycle filter, e.g. '2025-2026'"},{"name":"source","in":"query","required":false,"schema":{"type":"string","pattern":"^(tracker|ui|all)$","description":"Buy source filter: 'tracker' (default, canonical), 'ui', or 'all'.","default":"tracker","title":"Source"},"description":"Buy source filter: 'tracker' (default, canonical), 'ui', or 'all'."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/contours":{"post":{"tags":["cbradio-qa","geo"],"summary":"Batch-fetch coverage contours for multiple stations","description":"Return coverage contours for many stations as a single GeoJSON\n    FeatureCollection.\n\n    Collapses the proposal coverage map's per-station contour fan-out into ONE\n    request (cbradio-ui#139). Cache hits are served from a single bulk read;\n    misses are filled from cbdistricts bounded to a small concurrency, so a\n    large statewide map (~50-150 stations) can never exhaust the API's DB pool\n    the way the old unbounded per-station browser fan-out did (cbradio#304).\n\n    AM stations default to the `day` contour unless `contour_type` is given.\n    FM stations only have `service` contours.\n\n    Stations whose contour can't be resolved (unknown callsign, or synthesis\n    impossible upstream) are listed in `properties.missing` — the response is\n    never a 404 for partial misses.\n\n    **Maximum**: 250 callsigns per request.","operationId":"get_contours_batch_api_v1_geo_contours_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ContoursBatchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/contours/stats":{"get":{"tags":["cbradio-qa","geo"],"summary":"Get contour data statistics from districts service","description":"Get statistics about available contour data from districts.campaignbrain.dev.\n\n    Returns counts by service type (AM/FM) and contour type (day/night/service).","operationId":"get_contour_stats_api_v1_geo_contours_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/geo/coverage":{"get":{"tags":["cbradio-qa","geo"],"summary":"Get coverage contours","description":"Return coverage contours as GeoJSON FeatureCollection.\n\n    **For AM stations**, you can specify the contour type:\n    - `day`: Daytime coverage (default for AM)\n    - `night`: Nighttime coverage\n    - `service`: Default service contour\n\n    **For FM stations**, only `service` contour is available.\n\n    **cbradio#246**: when `state` is provided AND `in_network_only=true`, takes a\n    fast batched path through cbdistricts' `/states/{state}/coverage` (a single\n    call instead of N per-station calls) and includes synthesized polygons\n    (tier 2–4 from cbdistricts#14). Each Feature carries\n    `properties.contour_source` ∈ {`fcc`, `class_transmitter`,\n    `default_transmitter`, `class_city`, `default_city`}, and the\n    FeatureCollection's top-level `properties` includes `feature_count`,\n    `in_network_total`, `source_counts`, and `synthesis_failed`. This is the\n    customer-facing coverage map path.\n\n    Without `in_network_only=true`, falls back to the legacy per-station fetch\n    path (no synthesis, filtered by `has fcc_facility_id`).","operationId":"get_coverage_contours_api_v1_geo_coverage_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"State"},"description":"Filter by state"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"am or fm","title":"Service Type"},"description":"am or fm"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service (AM only). Defaults to day for AM.","title":"Contour Type"},"description":"Contour type: day, night, or service (AM only). Defaults to day for AM."},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"cbradio#246: when true with state, returns a batched FeatureCollection filtered to in-network stations with synthesized-or-canonical contours.","default":false,"title":"In Network Only"},"description":"cbradio#246: when true with state, returns a batched FeatureCollection filtered to in-network stations with synthesized-or-canonical contours."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/facilities/bulk":{"post":{"tags":["cbradio-qa","geo"],"summary":"Bulk lookup stations by facility IDs","description":"Get FCC/geo data for multiple stations by their facility IDs.\n\n    Returns results for each requested facility ID. Stations not found\n    will have `error: \"Facility not found\"` in their result entry.\n\n    **Maximum**: 100 facility IDs per request.","operationId":"bulk_lookup_facilities_api_v1_geo_facilities_bulk_post","requestBody":{"content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Facility Ids"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/facilities/{facility_id}":{"get":{"tags":["cbradio-qa","geo"],"summary":"Get station by FCC facility ID","description":"Get complete FCC/geo data for a station by its FCC facility ID.\n\n    The facility_id is the canonical identifier in FCC databases.\n    This endpoint enables lookup without callsign ambiguity.","operationId":"get_station_by_facility_id_api_v1_geo_facilities__facility_id__get","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","title":"Facility Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/facilities/{facility_id}/contour":{"get":{"tags":["cbradio-qa","geo"],"summary":"Get station coverage contour by facility ID","description":"Return coverage contour as GeoJSON Feature using FCC facility ID.\n\n    **For AM stations**, you can specify the contour type:\n    - `day`: Daytime coverage (0.5 mV/m field strength)\n    - `night`: Nighttime coverage (2.0 mV/m field strength)\n    - `service`: Default service contour\n\n    **For FM stations**, only `service` contour is available.","operationId":"get_station_contour_by_facility_api_v1_geo_facilities__facility_id__contour_get","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","title":"Facility Id"}},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service (AM only)","title":"Contour Type"},"description":"Contour type: day, night, or service (AM only)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/nearby":{"get":{"tags":["cbradio-qa","geo"],"summary":"Find nearby stations","description":"Find stations within a radius of a point using Haversine distance.\n\n    Returns stations sorted by distance from the query point.","operationId":"find_nearby_stations_api_v1_geo_nearby_get","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","maximum":90,"minimum":-90,"description":"Center latitude","title":"Lat"},"description":"Center latitude"},{"name":"lon","in":"query","required":true,"schema":{"type":"number","maximum":180,"minimum":-180,"description":"Center longitude","title":"Lon"},"description":"Center longitude"},{"name":"radius_km","in":"query","required":false,"schema":{"type":"number","maximum":500,"minimum":1,"description":"Search radius in km","default":50,"title":"Radius Km"},"description":"Search radius in km"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/points":{"get":{"tags":["cbradio-qa","geo"],"summary":"Get stations as points","description":"Return all stations with coordinates as GeoJSON FeatureCollection (Points).","operationId":"get_station_points_api_v1_geo_points_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"State"},"description":"Filter by state"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"am or fm","title":"Service Type"},"description":"am or fm"},{"name":"min_power","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum power in kW","title":"Min Power"},"description":"Minimum power in kW"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"default":1000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/stations":{"get":{"tags":["cbradio-qa","geo"],"summary":"List stations with geo data","description":"Retrieve stations with FCC geo data (facility ID, coordinates, power, class).\n\n    Returns all stations with an FCC facility ID, which enables contour lookup.\n    Coordinates may be null for some stations but contours can still be fetched.\n\n    **Filters:**\n    - `state`: Two-letter state abbreviation\n    - `station_class`: FCC class (A, B, C, D, C0, C1, C2, C3)\n    - `service_type`: am or fm\n    - `min_power`/`max_power`: Power range in kW\n    - `has_coordinates`: Only stations with antenna coordinates\n    - `has_contour`: Only stations with (or without) FCC contour data cached","operationId":"list_geo_stations_api_v1_geo_stations_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"State"},"description":"Filter by state"},{"name":"station_class","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"FCC station class","title":"Station Class"},"description":"FCC station class"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"am or fm","title":"Service Type"},"description":"am or fm"},{"name":"min_power","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum power in kW","title":"Min Power"},"description":"Minimum power in kW"},{"name":"max_power","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Maximum power in kW","title":"Max Power"},"description":"Maximum power in kW"},{"name":"has_coordinates","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only stations with antenna coordinates","title":"Has Coordinates"},"description":"Only stations with antenna coordinates"},{"name":"has_contour","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only stations with (or without) FCC contour data cached upstream","title":"Has Contour"},"description":"Only stations with (or without) FCC contour data cached upstream"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/stations/{callsign}":{"get":{"tags":["cbradio-qa","geo"],"summary":"Get station geo details","description":"Get complete FCC/geo data for a single station.","operationId":"get_station_geo_api_v1_geo_stations__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/stations/{callsign}/contour":{"get":{"tags":["cbradio-qa","geo"],"summary":"Get station coverage contour","description":"Return coverage contour as GeoJSON Feature.\n\n    Post-cbdistricts#14 (#246): cbdistricts always returns a Feature for any\n    station in its registry — canonical FCC polygon when available, otherwise\n    a synthesized polygon walking a tier ladder. `properties.contour_source`\n    enumerates the source: `fcc`, `class_transmitter`, `default_transmitter`,\n    `class_city`, `default_city`.\n\n    **For AM stations**, you can specify the contour type:\n    - `day`: Daytime coverage (0.5 mV/m field strength) - larger coverage\n    - `night`: Nighttime coverage (2.0 mV/m field strength) - smaller coverage\n    - `service`: Default service contour\n\n    **For FM stations**, only `service` contour is available.\n\n    AM stations default to `day` contour if not specified.","operationId":"get_station_contour_api_v1_geo_stations__callsign__contour_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service (AM only). Defaults to day for AM.","title":"Contour Type"},"description":"Contour type: day, night, or service (AM only). Defaults to day for AM."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/geo/stats":{"get":{"tags":["cbradio-qa","geo"],"summary":"Get FCC data statistics","description":"Aggregate statistics about FCC data coverage.","operationId":"get_geo_stats_api_v1_geo_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/geo/within":{"get":{"tags":["cbradio-qa","geo"],"summary":"Find stations in bounding box","description":"Find stations within a geographic bounding box.","operationId":"find_stations_within_bbox_api_v1_geo_within_get","parameters":[{"name":"min_lat","in":"query","required":true,"schema":{"type":"number","maximum":90,"minimum":-90,"description":"South boundary","title":"Min Lat"},"description":"South boundary"},{"name":"max_lat","in":"query","required":true,"schema":{"type":"number","maximum":90,"minimum":-90,"description":"North boundary","title":"Max Lat"},"description":"North boundary"},{"name":"min_lon","in":"query","required":true,"schema":{"type":"number","maximum":180,"minimum":-180,"description":"West boundary","title":"Min Lon"},"description":"West boundary"},{"name":"max_lon","in":"query","required":true,"schema":{"type":"number","maximum":180,"minimum":-180,"description":"East boundary","title":"Max Lon"},"description":"East boundary"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/i360/county-report":{"post":{"tags":["cbradio-qa","i360"],"summary":"Generate county-level voter propensity report for a state","description":"Generate a full-state voter universe report broken down by county.\n\nFor each county, returns registered voter counts plus swing and base universe sizes\n(individuals and unique households) based on political affinity and voting propensity filters.\n\n**How it works:** Queries i360 Public API via cbworkflow. Runs 3 API calls per county\n(registered, swing, base), so a state with 67 counties makes ~201 API calls.\n\n**Performance:** Expect ~45-60 seconds for large states (FL, TX). Small states (NH = 10 counties) complete in ~7 seconds.\n\n**Default criteria (standard campaign targeting):**\n- Swing: affinity 40-55, propensity 1-4\n- Base: affinity 65-100, propensity 1-4\n\n**Example request:**\n```json\n{\"state\": \"NH\"}\n```\n\n**Example with custom criteria:**\n```json\n{\n  \"state\": \"FL\",\n  \"swing_affinity_min\": 35,\n  \"swing_affinity_max\": 60,\n  \"base_affinity_min\": 70,\n  \"voting_propensity_min\": 1,\n  \"voting_propensity_max\": 5\n}\n```","operationId":"county_report_api_v1_i360_county_report_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CountyReportRequest"}}},"required":true},"responses":{"200":{"description":"County report generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CountyReportResponse"}}}},"401":{"description":"Authentication required"},"422":{"description":"Invalid request parameters","content":{"application/json":{"example":{"detail":[{"msg":"String should have at most 2 characters","type":"string_too_long"}]}}}},"502":{"description":"Upstream i360 API or cbworkflow error","content":{"application/json":{"example":{"detail":"Workflow service error: Connection refused"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/i360/search-counts":{"post":{"tags":["cbradio-qa","i360"],"summary":"Query voter universe counts for a state or county","description":"Run a single voter universe count query against the i360 database.\n\nReturns aggregate counts: individuals, residences, apartments, mailing addresses,\nlandlines, and mobile phones matching the specified criteria.\n\n**Much faster than county-report** — single API call, typically responds in 1-3 seconds.\n\n**Use cases:**\n- Quick universe sizing: `{\"state\": \"NH\"}` → 1.3M individuals statewide\n- County-level drill-down: `{\"state\": \"FL\", \"county\": 306}` → Alachua County counts\n- Filtered targeting: add affinity + propensity ranges to narrow the universe\n\n**Important:** Always provide **both** min AND max for affinity/propensity filters.\nThe i360 API silently returns zeros if you send only one side of the range.\n\n**Example — statewide totals:**\n```json\n{\"state\": \"NH\"}\n```\n\n**Example — county with filters:**\n```json\n{\n  \"state\": \"FL\",\n  \"county\": 306,\n  \"affinity_min\": 40,\n  \"affinity_max\": 55,\n  \"propensity_min\": 1,\n  \"propensity_max\": 4\n}\n```","operationId":"search_counts_api_v1_i360_search_counts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SearchCountsRequest"}}},"required":true},"responses":{"200":{"description":"Counts returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SearchCountsResponse"}}}},"401":{"description":"Authentication required"},"422":{"description":"Invalid request parameters","content":{"application/json":{"example":{"detail":[{"msg":"String should have at most 2 characters","type":"string_too_long"}]}}}},"502":{"description":"Upstream i360 API or cbworkflow error","content":{"application/json":{"example":{"detail":"No counts returned from i360"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/import/batches":{"post":{"tags":["cbradio-qa","import"],"summary":"Create import batch","description":"Create a new import batch with records for validation.","operationId":"create_import_batch_api_v1_import_batches_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CreateBatchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"get":{"tags":["cbradio-qa","import"],"summary":"List import batches","description":"List all import batches with optional filters.","operationId":"list_import_batches_api_v1_import_batches_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by batch status","title":"Status"},"description":"Filter by batch status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/import/batches/{batch_id}":{"get":{"tags":["cbradio-qa","import"],"summary":"Get import batch status","description":"Get the current status of an import batch.","operationId":"get_import_batch_api_v1_import_batches__batch_id__get","parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string","title":"Batch Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/import/staged":{"get":{"tags":["cbradio-qa","import"],"summary":"List staged records","description":"List staged records with optional filters.","operationId":"list_staged_records_api_v1_import_staged_get","parameters":[{"name":"batch_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by batch ID","title":"Batch Id"},"description":"Filter by batch ID"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by validation status","title":"Status"},"description":"Filter by validation status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/import/staged/bulk-approve":{"post":{"tags":["cbradio-qa","import"],"summary":"Bulk approve staged records","description":"Approve multiple staged records at once.","operationId":"bulk_approve_staged_records_api_v1_import_staged_bulk_approve_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkApproveRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/import/staged/{record_id}":{"get":{"tags":["cbradio-qa","import"],"summary":"Get staged record","description":"Get a single staged record by ID.","operationId":"get_staged_record_api_v1_import_staged__record_id__get","parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/import/staged/{record_id}/approve":{"post":{"tags":["cbradio-qa","import"],"summary":"Approve staged record","description":"Approve a staged record for import.","operationId":"approve_staged_record_api_v1_import_staged__record_id__approve_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/import/staged/{record_id}/corrections":{"post":{"tags":["cbradio-qa","import"],"summary":"Apply correction to staged record","description":"Apply corrections to a staged record and re-validate.","operationId":"apply_correction_api_v1_import_staged__record_id__corrections_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ApplyCorrectionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/import/staged/{record_id}/reject":{"post":{"tags":["cbradio-qa","import"],"summary":"Reject staged record","description":"Reject a staged record.","operationId":"reject_staged_record_api_v1_import_staged__record_id__reject_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RejectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/internal/sync/documents":{"post":{"tags":["cbradio-qa","sync-internal"],"summary":"Batch create document records","description":"Create multiple document records in a single transaction. Each document\n    is assigned a new UUID and starts with extraction_status 'pending'.\n\n    Used by the sync pipeline to register newly discovered files (rate cards,\n    contracts, etc.) before extraction begins. Requires authentication.","operationId":"create_documents_api_v1_internal_sync_documents_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__DocumentCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__DocumentCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/internal/sync/documents/{document_id}/extraction-result":{"post":{"tags":["cbradio-qa","sync-internal"],"summary":"Store extraction result (OCR + rates + status) in one transaction","description":"Submit the complete extraction result for a document in a single\n    transaction. Updates the document's OCR text, extraction status,\n    confidence, and timing fields. If rates are provided with a valid\n    station_id, inserts them into the rate table and updates the document's\n    extracted_rates_count.\n\n    Also writes an extraction_log entry for audit purposes. Re-submissions\n    are safe — duplicate rate inserts are caught and skipped. Requires\n    authentication.","operationId":"submit_extraction_result_api_v1_internal_sync_documents__document_id__extraction_result_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ExtractionResultRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ExtractionResultResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/internal/sync/existing-doc-paths":{"get":{"tags":["cbradio-qa","sync-internal"],"summary":"Get existing document file paths for dedup","description":"Return all file_path values for documents matching the given source tag.\n\n    Used by the sync pipeline to skip documents that have already been\n    ingested. Requires authentication.","operationId":"get_existing_doc_paths_api_v1_internal_sync_existing_doc_paths_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"source","in":"query","required":true,"schema":{"type":"string","description":"Document source tag","title":"Source"},"description":"Document source tag"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Existing Doc Paths Api V1 Internal Sync Existing Doc Paths Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/internal/sync/gap-documents":{"get":{"tags":["cbradio-qa","sync-internal"],"summary":"Get documents with extracted rates but no rate rows in DB","description":"Find documents where extraction completed and counted rates, but no\n    corresponding rate rows were actually inserted into the database.\n\n    These gaps typically indicate rate INSERT failures during extraction.\n    Returns the document metadata and OCR text length to aid re-extraction.\n    Requires authentication.","operationId":"get_gap_documents_api_v1_internal_sync_gap_documents_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/internal/sync/orphaned-rates":{"get":{"tags":["cbradio-qa","sync-internal"],"summary":"Get stations with unlinked rates for a source","description":"Return stations that have rates linked to documents with the given source\n    tag but whose rate_card_id is NULL (i.e., rates not yet associated with\n    a rate_card).\n\n    Used by the backfill pipeline to identify stations needing rate_card\n    creation. Requires authentication.","operationId":"get_orphaned_rates_api_v1_internal_sync_orphaned_rates_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"source","in":"query","required":true,"schema":{"type":"string","description":"Document source tag","title":"Source"},"description":"Document source tag"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Orphaned Rates Api V1 Internal Sync Orphaned Rates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/internal/sync/pending-documents":{"get":{"tags":["cbradio-qa","sync-internal"],"summary":"Get documents pending extraction","description":"Return documents whose extraction_status is 'pending' and file_size > 0.\n\n    Results are ordered to prioritize images over other formats, and rate\n    cards over other document types. Use `images_only=true` to restrict to\n    PNG/JPG formats. Requires authentication.","operationId":"get_pending_documents_api_v1_internal_sync_pending_documents_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":10,"title":"Limit"}},{"name":"images_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Images Only"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Pending Documents Api V1 Internal Sync Pending Documents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/internal/sync/rate-card-exists":{"get":{"tags":["cbradio-qa","sync-internal"],"summary":"Check if a rate card exists for station/source/year","description":"Check whether a rate_card record already exists for the given station,\n    source, and year combination.\n\n    Returns `exists: true` with the rate_card_id if found, or\n    `exists: false` otherwise. Requires authentication.","operationId":"check_rate_card_exists_api_v1_internal_sync_rate_card_exists_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"station_id","in":"query","required":true,"schema":{"type":"string","title":"Station Id"}},{"name":"source","in":"query","required":true,"schema":{"type":"string","title":"Source"}},{"name":"year","in":"query","required":true,"schema":{"type":"integer","title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Check Rate Card Exists Api V1 Internal Sync Rate Card Exists Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/internal/sync/rate-cards/backfill":{"post":{"tags":["cbradio-qa","sync-internal"],"summary":"Create rate card and link orphaned rates for a station","description":"Create a rate_card for the given station/source/year if one does not\n    already exist, then link all orphaned rates (rate_card_id IS NULL) for\n    that station and source to the rate_card.\n\n    If the rate_card already exists, it is reused and its source_document_id\n    is backfilled if missing. Returns the rate_card_id, whether it was newly\n    created, and how many rates are now linked. Requires authentication.","operationId":"backfill_rate_card_api_v1_internal_sync_rate_cards_backfill_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCardBackfillRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCardBackfillResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/internal/sync/station-map":{"get":{"tags":["cbradio-qa","sync-internal"],"summary":"Get callsign to station ID/state mapping","description":"Return a mapping of every station callsign to its ID and state.\n\n    Used by the sync pipeline to resolve callsigns to station IDs without\n    opening a direct DB connection. Requires authentication.","operationId":"get_station_map_api_v1_internal_sync_station_map_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Station Map Api V1 Internal Sync Station Map Get"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/internal/sync_gdrive":{"post":{"tags":["cbradio-qa","sync-gdrive"],"summary":"Trigger Gdrive Sync","operationId":"trigger_gdrive_sync_api_v1_internal_sync_gdrive_post","parameters":[{"name":"x-cbradio-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cbradio-Signature"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues":{"get":{"tags":["cbradio-qa","issues"],"summary":"List issues for a project","description":"List issues for a specific cb* project with optional filters.\n\n    **Filter Options:**\n    - `state`: open, closed, all (default: open)\n    - `label`: Filter by label (can specify multiple)\n    - `assignee`: Filter by assignee login (@me for self)\n    - `author`: Filter by author login\n    - `milestone`: Filter by milestone name\n    - `search`: GitHub search query within the project\n\n    **Examples:**\n    - `/api/v1/issues?project=cbradio` - All open issues\n    - `/api/v1/issues?project=cbradio&state=closed&label=bug` - Closed bugs\n    - `/api/v1/issues?project=cbradio&assignee=@me` - My assigned issues","operationId":"list_issues_api_v1_issues_get","parameters":[{"name":"project","in":"query","required":true,"schema":{"type":"string","description":"Project slug (e.g., cbradio)","title":"Project"},"description":"Project slug (e.g., cbradio)"},{"name":"state","in":"query","required":false,"schema":{"type":"string","description":"State filter: open, closed, all","default":"open","title":"State"},"description":"State filter: open, closed, all"},{"name":"label","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by labels","title":"Label"},"description":"Filter by labels"},{"name":"assignee","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by assignee login","title":"Assignee"},"description":"Filter by assignee login"},{"name":"author","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by author login","title":"Author"},"description":"Filter by author login"},{"name":"milestone","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by milestone name","title":"Milestone"},"description":"Filter by milestone name"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"GitHub search query","title":"Search"},"description":"GitHub search query"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum results","default":30,"title":"Limit"},"description":"Maximum results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__IssueListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/bulk/close":{"post":{"tags":["cbradio-qa","issues"],"summary":"Bulk close issues","description":"Close multiple issues at once with optional comment and reason.","operationId":"bulk_close_api_v1_issues_bulk_close_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkCloseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkOperationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/bulk/label":{"post":{"tags":["cbradio-qa","issues"],"summary":"Bulk label operation","description":"Add or remove labels from multiple issues at once.","operationId":"bulk_label_api_v1_issues_bulk_label_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkLabelRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkOperationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/projects":{"get":{"tags":["cbradio-qa","issues"],"summary":"List all CB* projects","description":"Returns a list of all cb* projects with their GitHub repo paths and descriptions.\n    Use these slugs for filtering and targeting operations.","operationId":"list_projects_api_v1_issues_projects_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbradio-qa__Project"},"type":"array","title":"Response List Projects Api V1 Issues Projects Get"}}}}}}},"/cbradio-qa/api/v1/issues/search":{"get":{"tags":["cbradio-qa","issues"],"summary":"Search issues across projects","description":"Search for issues across multiple cb* projects using GitHub search syntax.\n\n    **Example queries:**\n    - `authentication error` - Find issues mentioning authentication errors\n    - `is:open label:bug` - Find open bugs\n    - `assignee:isenbek` - Issues assigned to a user","operationId":"search_issues_api_v1_issues_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","description":"Search query (GitHub search syntax)","title":"Q"},"description":"Search query (GitHub search syntax)"},{"name":"projects","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated project slugs (e.g., cbauth,cbapp)","title":"Projects"},"description":"Comma-separated project slugs (e.g., cbauth,cbapp)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum results","default":30,"title":"Limit"},"description":"Maximum results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/{project}":{"post":{"tags":["cbradio-qa","issues"],"summary":"Create a new issue","description":"Create a new issue in the specified project.\n\n    **Request Body:**\n    - `title` (required): Issue title\n    - `body`: Issue description (markdown supported)\n    - `labels`: List of label names to apply\n    - `assignees`: List of GitHub logins (use @me for self)\n    - `milestone`: Milestone name","operationId":"create_issue_api_v1_issues__project__post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__IssueCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/{project}/{number}":{"get":{"tags":["cbradio-qa","issues"],"summary":"Get a single issue","description":"Get detailed information about a specific issue by project and number.","operationId":"get_issue_api_v1_issues__project___number__get","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}},{"name":"comments","in":"query","required":false,"schema":{"type":"boolean","description":"Include comments in response","default":false,"title":"Comments"},"description":"Include comments in response"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","issues"],"summary":"Update an issue","description":"Update an existing issue. All fields are optional.\n\n    **Request Body:**\n    - `title`: New title\n    - `body`: New body\n    - `add_labels`: Labels to add\n    - `remove_labels`: Labels to remove\n    - `add_assignees`: Assignees to add\n    - `remove_assignees`: Assignees to remove\n    - `milestone`: Set milestone name\n    - `remove_milestone`: Remove milestone association","operationId":"update_issue_api_v1_issues__project___number__patch","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__IssueUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","issues"],"summary":"Delete an issue","description":"Permanently delete an issue. Requires admin permissions.","operationId":"delete_issue_api_v1_issues__project___number__delete","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/{project}/{number}/close":{"post":{"tags":["cbradio-qa","issues"],"summary":"Close an issue","description":"Close an open issue with optional comment and reason.\n\n    **Reasons:**\n    - `completed`: Issue was resolved\n    - `not_planned`: Issue won't be fixed","operationId":"close_issue_api_v1_issues__project___number__close_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__IssueClose"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/{project}/{number}/comments":{"get":{"tags":["cbradio-qa","issues"],"summary":"List issue comments","description":"Get all comments on an issue.","operationId":"list_comments_api_v1_issues__project___number__comments_get","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","issues"],"summary":"Add a comment","description":"Add a new comment to an issue.","operationId":"add_comment_api_v1_issues__project___number__comments_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CommentCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/{project}/{number}/lock":{"post":{"tags":["cbradio-qa","issues"],"summary":"Lock issue conversation","description":"Lock an issue to prevent further comments.\n\n    **Lock reasons:**\n    - `off_topic`: Discussion went off-topic\n    - `resolved`: Issue was resolved\n    - `spam`: Spam content\n    - `too_heated`: Discussion became heated","operationId":"lock_issue_api_v1_issues__project___number__lock_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__IssueLock"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/{project}/{number}/pin":{"post":{"tags":["cbradio-qa","issues"],"summary":"Pin an issue","description":"Pin an issue to the repository (limited to 3 pins).","operationId":"pin_issue_api_v1_issues__project___number__pin_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/{project}/{number}/reopen":{"post":{"tags":["cbradio-qa","issues"],"summary":"Reopen a closed issue","description":"Reopen a previously closed issue with optional comment.","operationId":"reopen_issue_api_v1_issues__project___number__reopen_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__IssueReopen"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/{project}/{number}/unlock":{"post":{"tags":["cbradio-qa","issues"],"summary":"Unlock issue conversation","description":"Unlock an issue to allow comments again.","operationId":"unlock_issue_api_v1_issues__project___number__unlock_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/issues/{project}/{number}/unpin":{"post":{"tags":["cbradio-qa","issues"],"summary":"Unpin an issue","description":"Remove pin from an issue.","operationId":"unpin_issue_api_v1_issues__project___number__unpin_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/make-good-requests":{"get":{"tags":["cbradio-qa","make-good-requests"],"summary":"List make-good requests","description":"List make-good requests with optional filtering.","operationId":"list_make_good_requests_api_v1_make_good_requests_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"order_packet_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by order packet","title":"Order Packet Id"},"description":"Filter by order packet"},{"name":"radio_buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by radio buy","title":"Radio Buy Id"},"description":"Filter by radio buy"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/make-good-requests/{make_good_id}":{"get":{"tags":["cbradio-qa","make-good-requests"],"summary":"Get make-good request","description":"Get a single make-good request by ID.","operationId":"get_make_good_request_api_v1_make_good_requests__make_good_id__get","parameters":[{"name":"make_good_id","in":"path","required":true,"schema":{"type":"string","title":"Make Good Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","make-good-requests"],"summary":"Resolve make-good request","description":"Resolve (approve/reject/complete) a make-good request.","operationId":"resolve_make_good_request_api_v1_make_good_requests__make_good_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"make_good_id","in":"path","required":true,"schema":{"type":"string","title":"Make Good Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodRequestResolve"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/network-exits":{"get":{"tags":["cbradio-qa","network-exits"],"summary":"List exit requests","description":"List network exit requests with filtering.","operationId":"list_exit_requests_api_v1_network_exits_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"station_callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station","title":"Station Callsign"},"description":"Filter by station"},{"name":"requested_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by requester","title":"Requested By"},"description":"Filter by requester"},{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by reason code","title":"Reason"},"description":"Filter by reason code"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ExitRequestListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","network-exits"],"summary":"Create exit request","description":"Request removal of a station from the network.","operationId":"create_exit_request_api_v1_network_exits_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ExitRequestCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ExitRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/network-exits/{exit_id}":{"get":{"tags":["cbradio-qa","network-exits"],"summary":"Get exit request detail","description":"Get full exit request details with audit trail.","operationId":"get_exit_request_api_v1_network_exits__exit_id__get","parameters":[{"name":"exit_id","in":"path","required":true,"schema":{"type":"string","title":"Exit Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ExitRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","network-exits"],"summary":"Cancel exit request","description":"Cancel a pending exit request.","operationId":"cancel_exit_request_api_v1_network_exits__exit_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"exit_id","in":"path","required":true,"schema":{"type":"string","title":"Exit Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/network-exits/{exit_id}/review":{"post":{"tags":["cbradio-qa","network-exits"],"summary":"Review exit request (Admin only)","description":"Approve or reject a network exit request.","operationId":"review_exit_request_api_v1_network_exits__exit_id__review_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"exit_id","in":"path","required":true,"schema":{"type":"string","title":"Exit Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ExitRequestReview"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ExitRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/nielsen/by-geography":{"get":{"tags":["cbradio-qa","nielsen"],"summary":"Get Nielsen ratings by state or market (bulk)","description":"Returns all rated stations in a state or market, with derived scoring fields.\n\nPowers the **Proposal Builder** station scoring and **SRM State View**. At least one of\n`state` or `market` is required.\n\n**Filters:**\n- `state` — two-letter state code (matches market_state, supports multi-state markets)\n- `market` — exact market name (e.g. \"Detroit MI\")\n- `format` — filter by Nielsen format (e.g. \"News/Talk\", \"Country\")\n- `min_share` — minimum AQH share threshold\n- `top_quartile_only` — if true, only top-25% stations per market\n\n**Examples:**\n- All Michigan stations: `GET /nielsen/by-geography?state=MI`\n- Detroit market only: `GET /nielsen/by-geography?market=Detroit MI`\n- Top Country stations in TX: `GET /nielsen/by-geography?state=TX&format=Country&top_quartile_only=true`","operationId":"by_geography_api_v1_nielsen_by_geography_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state code (e.g. MI, TX)","title":"State"},"description":"Two-letter state code (e.g. MI, TX)"},{"name":"market","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact Nielsen market name","title":"Market"},"description":"Exact Nielsen market name"},{"name":"format","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by Nielsen format","title":"Format"},"description":"Filter by Nielsen format"},{"name":"min_share","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Minimum AQH share","title":"Min Share"},"description":"Minimum AQH share"},{"name":"top_quartile_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only top-25% stations","default":false,"title":"Top Quartile Only"},"description":"Only top-25% stations"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"description":"Max results","default":500,"title":"Limit"},"description":"Max results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Stations found matching criteria","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__GeographyResponse"}}}},"422":{"description":"Must provide state or market","content":{"application/json":{"example":{"detail":"At least one of 'state' or 'market' is required"}}}}}}},"/cbradio-qa/api/v1/nielsen/formats":{"get":{"tags":["cbradio-qa","nielsen"],"summary":"Get format distribution with station counts and average ratings","description":"Returns aggregate statistics for each Nielsen format, sorted by station count descending.\n\nFor each format: how many stations use it, how many markets it appears in, and the average\nlatest rating (rating_4) across all stations.\n\n**Top formats by station count:** Country (~453), Christian CHR (~425), CHR (~300),\nSports (~290), News/Talk (~289), AC (~236), Classic Rock (~234).\n\n**Filters:**\n- `min_stations` — exclude rare formats (default 1). Use `min_stations=10` to focus on major formats.\n\n**Use cases:**\n- \"What are the most common station formats?\" → call with defaults\n- \"Which formats have the highest average ratings?\" → sort client-side by avg_latest_rating\n- \"How many Country stations are there?\" → find the Country row","operationId":"list_formats_api_v1_nielsen_formats_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"min_stations","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Minimum station count to include","default":1,"title":"Min Stations"},"description":"Minimum station count to include"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__FormatSummary"},"title":"Response List Formats Api V1 Nielsen Formats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/nielsen/markets":{"get":{"tags":["cbradio-qa","nielsen"],"summary":"List Nielsen markets with station counts and demographics","description":"Returns all Nielsen markets ordered by rank (1=New York, 2=Los Angeles, etc.),\nwith station counts and population demographics.\n\n**Filters:**\n- `state` — matches against market_state (supports multi-state markets like \"IA-IL\")\n- `min_rank` / `max_rank` — restrict to a rank range (e.g. top 50 markets)\n- `min_population` — filter small markets (e.g. only metros > 1M)\n\n**Pagination:** Use `limit` (default 300, max 500) and `offset` for paging.\n\n**Examples:**\n- All markets: `GET /nielsen/markets`\n- Texas markets: `GET /nielsen/markets?state=TX`\n- Top 10 markets: `GET /nielsen/markets?max_rank=10`\n- Large markets: `GET /nielsen/markets?min_population=1000000`","operationId":"list_markets_api_v1_nielsen_markets_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state code (e.g. NH, TX)","title":"State"},"description":"Filter by state code (e.g. NH, TX)"},{"name":"min_rank","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Minimum market rank","title":"Min Rank"},"description":"Minimum market rank"},{"name":"max_rank","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Maximum market rank","title":"Max Rank"},"description":"Maximum market rank"},{"name":"min_population","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Minimum population","title":"Min Population"},"description":"Minimum population"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max results","default":300,"title":"Limit"},"description":"Max results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Offset for pagination","default":0,"title":"Offset"},"description":"Offset for pagination"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__MarketSummary"},"title":"Response List Markets Api V1 Nielsen Markets Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/nielsen/markets/{market_rank}":{"get":{"tags":["cbradio-qa","nielsen"],"summary":"Get market detail with all station ratings","description":"Returns full detail for a specific market including all station ratings,\nsorted by latest rating (rating_4) descending — strongest performers first.\n\n**Filters:**\n- `nielsen_format` — only return stations with this format (exact match, e.g. \"Country\", \"AC\")\n- `min_rating` — only return stations with rating_4 >= this value (e.g. 5.0 for strong performers)\n\n**Examples:**\n- All stations in New York: `GET /nielsen/markets/1`\n- Country stations in market #50: `GET /nielsen/markets/50?nielsen_format=Country`\n- Strong performers in Chicago: `GET /nielsen/markets/3?min_rating=5.0`\n\n**Interpreting ratings:**\n- `rating_4` > 10.0 = dominant station in the market\n- `rating_4` > 5.0 = strong performer\n- `rating_4` > 2.0 = average\n- `rating_4` < 1.0 or null = minimal measurable audience","operationId":"get_market_api_v1_nielsen_markets__market_rank__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"market_rank","in":"path","required":true,"schema":{"type":"integer","title":"Market Rank"}},{"name":"nielsen_format","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by Nielsen format","title":"Nielsen Format"},"description":"Filter by Nielsen format"},{"name":"min_rating","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Minimum latest rating (period 4)","title":"Min Rating"},"description":"Minimum latest rating (period 4)"}],"responses":{"200":{"description":"Market detail with station ratings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__MarketDetail"}}}},"404":{"description":"Market rank not found","content":{"application/json":{"example":{"detail":"Market rank 9999 not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/nielsen/stations/{callsign}":{"get":{"tags":["cbradio-qa","nielsen"],"summary":"Get all Nielsen ratings for a station across markets","description":"Returns all Nielsen rating entries for a given callsign.\n\nStations often appear in **multiple overlapping markets** — for example, WLTW-FM (New York AC)\nappears in 8 markets: New York (#1), Nassau-Suffolk (#20), Hudson Valley (#41), etc.\nThis endpoint returns all of them, ordered by market rank.\n\n**Callsign is case-insensitive** — `wltw-fm` and `WLTW-FM` both work.\n\n**Examples:**\n- `GET /nielsen/stations/WLTW-FM` — 8 ratings across NY metro area markets\n- `GET /nielsen/stations/WGIR-FM` — Manchester NH Classic Rock station","operationId":"get_station_ratings_api_v1_nielsen_stations__callsign__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Station ratings found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__StationRatingsResponse"}}}},"404":{"description":"No Nielsen ratings for this callsign","content":{"application/json":{"example":{"detail":"No Nielsen ratings found for ZZZZ-FM"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/nielsen/stats":{"get":{"tags":["cbradio-qa","nielsen"],"summary":"Get Nielsen dataset summary statistics","description":"Returns high-level counts of the Nielsen ratings dataset.\n\nUse this to check data coverage and freshness, or to display on a dashboard.\n\n**Typical values:** ~5,151 ratings, ~4,638 callsigns, ~239 markets, ~66 formats.","operationId":"nielsen_stats_api_v1_nielsen_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__NielsenStatsResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/order-packets":{"get":{"tags":["cbradio-qa","order-packets"],"summary":"List all order packets","description":"List all order packets with optional filtering by status or radio buy.","operationId":"list_order_packets_api_v1_order_packets_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"radio_buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/order-packets/bulk-send":{"post":{"tags":["cbradio-qa","order-packets"],"summary":"Bulk send order packets","description":"Send multiple order packets at once via cbemail.","operationId":"bulk_send_order_packets_api_v1_order_packets_bulk_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkSendRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkSendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/order-packets/dashboard":{"get":{"tags":["cbradio-qa","order-packets"],"summary":"Dashboard status summary","description":"Get order packet status summary and alerts.","operationId":"get_order_dashboard_api_v1_order_packets_dashboard_get","parameters":[{"name":"radio_buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__DashboardResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/order-packets/{order_packet_id}":{"get":{"tags":["cbradio-qa","order-packets"],"summary":"Get order packet","description":"Get a single order packet with full details.","operationId":"get_order_packet_api_v1_order_packets__order_packet_id__get","parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OrderPacketDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","order-packets"],"summary":"Update order packet","description":"Update order packet details.","operationId":"update_order_packet_api_v1_order_packets__order_packet_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OrderPacketUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OrderPacketDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/order-packets/{order_packet_id}/audit-log":{"get":{"tags":["cbradio-qa","order-packets"],"summary":"Get order confirmation audit log","description":"Get the audit trail for an order packet.","operationId":"get_order_audit_log_api_v1_order_packets__order_packet_id__audit_log_get","parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AuditLogResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/order-packets/{order_packet_id}/cancel":{"post":{"tags":["cbradio-qa","order-packets"],"summary":"Cancel order packet","description":"Cancel an order packet.","operationId":"cancel_order_packet_api_v1_order_packets__order_packet_id__cancel_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CancelOrderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CancelOrderResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/order-packets/{order_packet_id}/confirm":{"post":{"tags":["cbradio-qa","order-packets"],"summary":"Manual confirm order","description":"Manually mark an order as confirmed (e.g., from email reply).","operationId":"manual_confirm_order_api_v1_order_packets__order_packet_id__confirm_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ManualConfirmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/order-packets/{order_packet_id}/generate-pdf":{"post":{"tags":["cbradio-qa","order-packets"],"summary":"Generate order sheet PDF","description":"Generate the order sheet PDF and upload to S3.","operationId":"generate_order_pdf_api_v1_order_packets__order_packet_id__generate_pdf_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__GeneratePDFResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/order-packets/{order_packet_id}/send":{"post":{"tags":["cbradio-qa","order-packets"],"summary":"Send order packet email","description":"Send the order email with attachments via cbemail.","operationId":"send_order_packet_api_v1_order_packets__order_packet_id__send_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SendOrderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SendOrderResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/ownership-groups":{"post":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Create ownership group","description":"Create a new ownership group (station owner, rep firm, etc.).","operationId":"create_ownership_group_api_v1_ownership_groups_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OwnershipGroupCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"get":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"List ownership groups","description":"Retrieve a paginated list of ownership groups with optional filters.","operationId":"list_ownership_groups_api_v1_ownership_groups_get","parameters":[{"name":"payment_method","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by payment method","title":"Payment Method"},"description":"Filter by payment method"},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active status","title":"Is Active"},"description":"Filter by active status"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name or legal name","title":"Search"},"description":"Search by name or legal name"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/ownership-groups/by-station/{callsign}":{"get":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Find ownership group by station","description":"Returns the ownership group for the specified station, or 404 if not assigned.","operationId":"get_ownership_group_by_station_api_v1_ownership_groups_by_station__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/ownership-groups/{recipient_id}":{"get":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Get ownership group","description":"Retrieve a single ownership group by ID.","operationId":"get_ownership_group_api_v1_ownership_groups__recipient_id__get","parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"put":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Update ownership group","description":"Update an existing ownership group.","operationId":"update_ownership_group_api_v1_ownership_groups__recipient_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OwnershipGroupUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/ownership-groups/{recipient_id}/stations":{"post":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Add station to recipient","description":"Add a station callsign to a payment recipient's station list.","operationId":"add_station_to_recipient_api_v1_ownership_groups__recipient_id__stations_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AddStationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/ownership-groups/{recipient_id}/stations/{callsign}":{"delete":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Remove station from recipient","description":"Remove a station callsign from a payment recipient's station list.","operationId":"remove_station_from_recipient_api_v1_ownership_groups__recipient_id__stations__callsign__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}},{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/payment-recipients":{"post":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Create ownership group","description":"Create a new ownership group (station owner, rep firm, etc.).","operationId":"create_ownership_group_api_v1_payment_recipients_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OwnershipGroupCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"get":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"List ownership groups","description":"Retrieve a paginated list of ownership groups with optional filters.","operationId":"list_ownership_groups_api_v1_payment_recipients_get","parameters":[{"name":"payment_method","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by payment method","title":"Payment Method"},"description":"Filter by payment method"},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active status","title":"Is Active"},"description":"Filter by active status"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name or legal name","title":"Search"},"description":"Search by name or legal name"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/payment-recipients/by-station/{callsign}":{"get":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Find ownership group by station","description":"Returns the ownership group for the specified station, or 404 if not assigned.","operationId":"get_ownership_group_by_station_api_v1_payment_recipients_by_station__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/payment-recipients/{recipient_id}":{"get":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Get ownership group","description":"Retrieve a single ownership group by ID.","operationId":"get_ownership_group_api_v1_payment_recipients__recipient_id__get","parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"put":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Update ownership group","description":"Update an existing ownership group.","operationId":"update_ownership_group_api_v1_payment_recipients__recipient_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OwnershipGroupUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/payment-recipients/{recipient_id}/stations":{"post":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Add station to recipient","description":"Add a station callsign to a payment recipient's station list.","operationId":"add_station_to_recipient_api_v1_payment_recipients__recipient_id__stations_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AddStationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/payment-recipients/{recipient_id}/stations/{callsign}":{"delete":{"tags":["cbradio-qa","ownership-groups","ownership-groups"],"summary":"Remove station from recipient","description":"Remove a station callsign from a payment recipient's station list.","operationId":"remove_station_from_recipient_api_v1_payment_recipients__recipient_id__stations__callsign__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}},{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/pipelines":{"get":{"tags":["cbradio-qa","Pipelines"],"summary":"Freshness of every cbradio data feed","description":"Per-feed: row count, last write, age, and whether that is acceptable.\n\nDeliberately unauthenticated and mesh-internal, matching the `/datasets`\ndriver posture — it exposes counts and timestamps, no row content.","operationId":"list_pipelines_api_v1_pipelines_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Pipelines Api V1 Pipelines Get"}}}}}}},"/cbradio-qa/api/v1/pipelines/{key}":{"get":{"tags":["cbradio-qa","Pipelines"],"summary":"Freshness of one feed","operationId":"get_pipeline_api_v1_pipelines__key__get","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Pipeline Api V1 Pipelines  Key  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/planning-budget/{plan_id}/pdf":{"get":{"tags":["cbradio-qa","planning-budget"],"summary":"Render the Planning Budget PDF (headless capture of the print route)","description":"Headless-captures the cbradio-ui print route for `plan_id`\n    (`{FRONTEND_URL}/print/planning-budget/{plan_id}`) to a branded PDF and streams\n    it back (`application/pdf`). The coverage map renders inline in the print route,\n    so the capture includes it; stations without contour polygons render as markers.\n\n    Returns **503** if the headless browser isn't provisioned on the host\n    (`playwright` + chromium). The frontend can wire a download affordance to this\n    now — it works the moment the print route ships.","operationId":"planning_budget_pdf_api_v1_planning_budget__plan_id__pdf_get","parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}},{"name":"wait_selector","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSS selector the print route sets once the inline map has settled; best-effort (a missing signal won't fail the render).","default":".pb-map-ready","title":"Wait Selector"},"description":"CSS selector the print route sets once the inline map has settled; best-effort (a missing signal won't fail the render)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals":{"get":{"tags":["cbradio-qa","proposals"],"summary":"List proposals","description":"Returns a paginated list of proposals with aggregate financial data.\n\n    **Filters:**\n    - `status`: Filter by proposal status (draft, sent, accepted, rejected, converted, etc.)\n    - `client_id`: Filter by client ID\n    - `start_after`: Only return proposals with start_date on or after this date (YYYY-MM-DD)\n\n    Each result includes computed totals (total_gross, total_net) and line_item_count\n    from the associated proposal_line_item records. Results are ordered by creation\n    date descending.","operationId":"list_proposals_api_v1_proposals_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by client","title":"Client Id"},"description":"Filter by client"},{"name":"start_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date after (YYYY-MM-DD)","title":"Start After"},"description":"Start date after (YYYY-MM-DD)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","proposals"],"summary":"Create proposal","description":"Create a new proposal with optional line items.\n\n    A unique proposal number (P-YYYY-NNNN) is auto-generated. The request body\n    accepts proposal metadata (client, dates, ad type, geography targets) and an\n    optional list of line items. For each line item, net rate is auto-calculated\n    at 85% of gross if not provided, and total_gross/total_net are computed from\n    spots_per_week * weeks * rate.\n\n    List fields (target_states, target_dmas) are serialized to JSON for storage.\n    The proposal is created inside a single transaction -- if any line item insert\n    fails, the entire operation is rolled back.","operationId":"create_proposal_api_v1_proposals_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ProposalCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/approval-queue":{"get":{"tags":["cbradio-qa","proposals"],"summary":"Get proposals pending approval","description":"Returns proposals that are pending approval, ordered by submission date.\n\n    **Filters:**\n    - `approver_id`: Filter by requested approver\n    - `status`: Filter by status (default: pending_approval)\n\n    Proposals enter the approval queue when submitted via submit-for-approval.","operationId":"get_approval_queue_api_v1_proposals_approval_queue_get","parameters":[{"name":"approver_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by requested approver ID","title":"Approver Id"},"description":"Filter by requested approver ID"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Maximum results","default":50,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/generate":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Generate proposal from criteria","description":"Auto-generate a complete proposal by selecting stations and rates matching criteria.\n\n    **Process:**\n    1. Queries rates matching target states, ad type, spot duration, and preemption level\n    2. Selects the best rate per station (lowest or highest based on `prefer_lowest_rates`)\n    3. Creates a proposal record with a generated proposal number (P-YYYY-NNNN)\n    4. Creates line items for each selected station with computed totals\n    5. Respects budget and max_stations constraints\n\n    Rate selection uses the RATE_SOURCE_PRIORITY_SQL ordering to prefer the most\n    authoritative rate source per station. Net rates default to 85% of gross when\n    not available. The entire operation is transactional -- any failure rolls back\n    all inserts.\n\n    Returns summary statistics including stations_matched, stations_included,\n    totals, and budget_remaining.","operationId":"generate_proposal_api_v1_proposals_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__GenerateProposalRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__GenerateProposalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/proposals/metrics":{"get":{"tags":["cbradio-qa","proposals"],"summary":"Get proposal metrics","description":"Returns aggregate metrics about proposals in the system.\n\n    **Included metrics:**\n    - Total proposals by status\n    - Proposal value statistics (total gross, net, average)\n    - Conversion rates (accepted/sent, converted/accepted)\n    - Time-based metrics (proposals created this month/quarter)\n    - Top clients by proposal value\n\n    This endpoint is useful for dashboards and reporting.","operationId":"get_proposal_metrics_api_v1_proposals_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/proposals/plan":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Solve a budget into a complete flighted media plan","description":"Turn campaign parameters and a strategy posture into a complete, priced,\n    flighted plan: every station, every daypart, every week, every spot count,\n    each carrying the rule that put it there.\n\n    **Writes nothing.** This is a solver, not a mutation — call it as often as\n    you like while a buyer moves the levers.\n\n    **How it allocates.** One greedy loop in which two moves compete on the same\n    value-per-dollar terms: *open* a station at the minimum viable weight, or\n    *deepen* an open one by a spot. Reach-versus-frequency therefore falls out\n    of the arithmetic rather than being hard-coded, and `posture.reach_bias`\n    moves it deliberately.\n\n    The objective is **target audience per client dollar**. Margin is a bounded\n    lever (`posture.network_preference`, default 0) with `posture.min_score` as\n    a floor beneath it, and its position is echoed back on the response.\n\n    **Every price declares its basis.** `card` means a real rate row off the\n    station's own card; `benchmark` means the median for its state and power\n    class. Set `posture.require_card_rates` to refuse estimates — the plan will\n    then be smaller rather than quietly estimated.\n\n    **Lock and re-solve.** `posture.locked_stations` holds stations the buyer\n    has already committed to; the solver spends what is left around them.\n    Locking a plan's own output and re-solving reproduces that plan exactly, so\n    a buyer can keep the three stations they know they want, move any other\n    lever, and see only what the lever changed. `posture.excluded_stations` is\n    the other half.\n\n    **Name the audience.** Pass `target_audience` when you know it —\n    `election_type` cannot express partisan lean, so a Democratic primary sent\n    as `primary` is scored against a Republican format profile. An unrecognised\n    `target_audience` is a 400 rather than a silent fall back to swing.\n\n    **Target states or districts.** Districts are the better mode and win when\n    both are given: sub-state targeting activates the coverage dimension via\n    geo-fit — district reach blended with the share of the station's own signal\n    that lands inside it — so a 100kW blowtorch that sprays mostly elsewhere no\n    longer outranks the station that actually covers the seat.\n\n    **Known limit.** A statewide query has no fit dimension to blend, so\n    coverage scores 0 and `score` caps at 8 of 16; a `min_score` above 8 will\n    empty a statewide plan. District targeting raises the ceiling to 12. Cost\n    per target voter, the fourth dimension, still needs i360 and is inactive in\n    both modes.\n\n    Daypart weights and the concentration curve are calibrated against 8,245\n    historical line items — see `scripts/replay_historical_buys.py`.","operationId":"plan_proposal_api_v1_proposals_plan_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PlanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PlanResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/proposals/suggest":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Get station suggestions for a proposal (non-committing)","description":"Return ranked station suggestions scored by format, coverage, Nielsen, and voter efficiency.\n\n    Scoring mirrors cbradio-ui/shared/scoring.ts with 4 dimensions (0-4 each, max 16).\n    Rates are NOT a ranking factor since many stations have missing or zero rates.\n    In-network stations sort first as a tiebreaker.\n\n    **Required:** `target_geography.states` must contain at least one state.\n\n    The response includes each suggestion's individual dimension scores, a confidence\n    value based on data completeness, and budget coverage tracking. Stations are\n    filtered by station_pool (network-only vs all) and constrained by budget and\n    max_stations limits. This endpoint does not create or modify any records.","operationId":"suggest_stations_api_v1_proposals_suggest_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SuggestStationsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/proposals/{proposal_id}":{"get":{"tags":["cbradio-qa","proposals"],"summary":"Get proposal with line items","description":"Retrieve a single proposal by ID with all associated line items and computed totals.\n\n    The response includes the full proposal metadata, all line items ordered by\n    station callsign, and computed aggregate fields: total_gross, total_net, and\n    total_spots. JSON fields (target_states, target_dmas, target_geography) are\n    automatically parsed from their stored string representation.\n\n    Returns 404 if the proposal does not exist.","operationId":"get_proposal_api_v1_proposals__proposal_id__get","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ProposalWithLineItems"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","proposals"],"summary":"Update proposal","description":"Update proposal metadata (not line items).\n\n    Only proposals in 'draft' or 'pending_review' status can be updated. Fields\n    that are not included in the request body are left unchanged (standard PATCH\n    semantics via `exclude_unset`). List/geography fields are serialized to JSON\n    before storage, and enum fields are stored by value.\n\n    Returns the full updated proposal with line items. Returns 400 if the proposal\n    is in a non-editable status (e.g. sent, accepted, converted).","operationId":"update_proposal_api_v1_proposals__proposal_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ProposalUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","proposals"],"summary":"Delete proposal","description":"Permanently delete a proposal and all its associated line items.\n\n    This is a cascading delete: all proposal_line_item records for this proposal\n    are removed first, followed by the proposal record itself. Both deletions\n    happen within a single transaction.\n\n    Returns 204 No Content on success. Returns 404 if the proposal does not exist.","operationId":"delete_proposal_api_v1_proposals__proposal_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/accept":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Mark proposal as accepted","description":"Mark a proposal as accepted by the client.\n\n    Sets the proposal status to 'accepted' and records the acceptance timestamp.\n    Optional notes can be attached to document the acceptance terms or conditions.\n\n    Only proposals in 'sent' status can be accepted. After acceptance, the proposal\n    can be converted to a radio buy via the /convert endpoint.","operationId":"accept_proposal_api_v1_proposals__proposal_id__accept_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AcceptProposal"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/convert":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Convert proposal to radio buy","description":"Convert an accepted proposal to a radio buy.\n\n    This creates:\n    1. A new radio_buy record with data from the proposal\n    2. radio_buy_station records for each line item with classification and commission\n    3. An initial version snapshot (v1) for payments tracking","operationId":"convert_proposal_api_v1_proposals__proposal_id__convert_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ConvertProposal"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/export":{"get":{"tags":["cbradio-qa","proposals"],"summary":"Export proposal to Excel or CSV","description":"Export a proposal as a downloadable CSV or Excel (.xlsx) file.\n\n    The export follows the standard radio buy format with columns for Client,\n    Station, Days, Time, Format, DMA, Length, Rate, weekly spot distribution\n    columns, Total Spots, and Total Spend. Stations are grouped with subtotals,\n    and a grand total row is appended at the bottom.\n\n    **Formats:**\n    - `csv`: Returns a text/csv stream (always available)\n    - `xlsx`: Returns an Excel workbook with formatted headers, merged title row,\n      and auto-adjusted column widths (requires openpyxl; returns 501 if not installed)\n\n    The response includes a Content-Disposition header for browser download.","operationId":"export_proposal_api_v1_proposals__proposal_id__export_get","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["csv","xlsx"],"default":"csv","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/line-items":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Add line items to proposal","description":"Add one or more line items to an existing proposal.\n\n    Each line item represents a station buy with callsign, spots_per_week, weeks,\n    and rate information. Net rate defaults to 85% of gross if not provided.\n    Total gross and total net are auto-calculated from spots * weeks * rate.\n\n    Line items can only be added to proposals in 'draft' or 'pending_review' status.\n    The proposal's updated_at timestamp is refreshed after the insert. Supports\n    AI-sourced items with optional confidence score, reason, and classification fields.\n\n    Returns the list of created IDs and a count.","operationId":"add_line_items_api_v1_proposals__proposal_id__line_items_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__LineItemCreate"},"title":"Items"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/line-items/{item_id}":{"patch":{"tags":["cbradio-qa","proposals"],"summary":"Update line item","description":"Update a specific line item within a proposal.\n\n    Accepts partial updates to spots_per_week, weeks, gross_rate_per_spot,\n    net_rate_per_spot, and notes. When any rate or quantity field changes,\n    total_gross and total_net are automatically recalculated. Unchanged fields\n    retain their current values.\n\n    Only line items belonging to proposals in 'draft' or 'pending_review' status\n    can be modified. The parent proposal's updated_at timestamp is also refreshed.\n\n    Returns the updated line item record.","operationId":"update_line_item_api_v1_proposals__proposal_id__line_items__item_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__LineItemUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","proposals"],"summary":"Delete line item","description":"Delete a specific line item from a proposal.\n\n    Removes the line item record and updates the parent proposal's updated_at\n    timestamp. Only line items belonging to proposals in 'draft' or 'pending_review'\n    status can be deleted.\n\n    Returns 204 No Content on success. Returns 404 if the line item does not exist\n    within the specified proposal, or 400 if the proposal is in a non-editable status.","operationId":"delete_line_item_api_v1_proposals__proposal_id__line_items__item_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/reject":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Mark proposal as rejected","description":"Mark a proposal as rejected by the client.\n\n    Sets the proposal status to 'rejected' and records the rejection timestamp\n    along with the client's rejection reason. Only proposals in 'sent' status\n    can be rejected.\n\n    The rejection reason is stored in the `rejection_reason` field for reporting\n    and future reference.","operationId":"reject_proposal_api_v1_proposals__proposal_id__reject_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RejectProposal"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/review-decision":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Submit approval decision for proposal","description":"Submit an approval decision for a proposal.\n\n    **Decision values:**\n    - `approved`: Proposal is approved\n    - `rejected`: Proposal is rejected\n    - `needs_revision`: Proposal needs changes before approval\n\n    **Optional fields:**\n    - `notes`: Decision notes\n    - `modifications`: JSON object describing any approved modifications\n\n    This creates an approval history record for audit purposes.","operationId":"review_decision_api_v1_proposals__proposal_id__review_decision_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"decision","in":"query","required":true,"schema":{"type":"string","description":"Approval decision: approved, rejected, needs_revision","title":"Decision"},"description":"Approval decision: approved, rejected, needs_revision"},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Decision notes","title":"Notes"},"description":"Decision notes"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"type":"object","additionalProperties":true},{"type":"null"}],"title":"Modifications"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/send":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Send proposal to client","description":"Mark a proposal as sent to the client, advancing its workflow status.\n\n    Sets the proposal status to 'sent' and records an expiration date calculated\n    from the provided `expires_in_days` value. Optional notes can be attached.\n\n    Only proposals in 'draft' or 'pending_review' status can be sent. Once sent,\n    the proposal can be accepted, rejected, or allowed to expire.","operationId":"send_proposal_api_v1_proposals__proposal_id__send_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SendProposal"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/share-link":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Create share link for proposal","description":"Generate a secure, shareable link for a proposal via CBLinks service.","operationId":"create_share_link_api_v1_proposals__proposal_id__share_link_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CreateShareLinkRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ShareLinkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","proposals"],"summary":"Revoke share link for proposal","description":"Revoke the active share link for a proposal.","operationId":"revoke_share_link_api_v1_proposals__proposal_id__share_link_delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/share-link/status":{"get":{"tags":["cbradio-qa","proposals"],"summary":"Get share link status","description":"Get the status and analytics for the active share link.","operationId":"get_share_link_status_api_v1_proposals__proposal_id__share_link_status_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ShareLinkStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/submit-for-approval":{"post":{"tags":["cbradio-qa","proposals"],"summary":"Submit proposal for approval","description":"Submit a proposal for approval review.\n\n    **Required fields:**\n    - `approver_id`: ID of the user who should review this proposal\n\n    **Optional fields:**\n    - `notes`: Notes for the approver\n    - `discount_percentage`: Requested discount percentage (0-100)\n\n    This changes the proposal status to 'pending_approval' and records\n    the submission in the approval history.","operationId":"submit_for_approval_api_v1_proposals__proposal_id__submit_for_approval_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"approver_id","in":"query","required":true,"schema":{"type":"string","description":"ID of the requested approver","title":"Approver Id"},"description":"ID of the requested approver"},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Notes for the approver","title":"Notes"},"description":"Notes for the approver"},{"name":"discount_percentage","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100,"minimum":0},{"type":"null"}],"description":"Requested discount percentage","title":"Discount Percentage"},"description":"Requested discount percentage"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/proposals/{proposal_id}/summary":{"get":{"tags":["cbradio-qa","proposals"],"summary":"Get proposal financial summary","description":"Returns a financial summary for a proposal.\n\n    Computes aggregate totals across all line items including total_gross,\n    total_net, total_spots, and commission_amount (based on the proposal's\n    commission_rate). Also returns the count of line items and a deduplicated\n    list of station callsigns.\n\n    This is a read-only computed view useful for dashboards and proposal\n    review screens.","operationId":"get_proposal_summary_api_v1_proposals__proposal_id__summary_get","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ProposalFinancialSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/public/contact":{"post":{"tags":["cbradio-qa","public-contact"],"summary":"Submit contact form","description":"Accept a public contact form submission from ruralamfm.com.\n\n    No authentication required. Includes honeypot spam prevention (the\n    `website` field should be left empty by real users) and IP-based rate\n    limiting (max 5 submissions per IP per hour). Silently accepts\n    honeypot-triggered submissions without storing them.","operationId":"submit_contact_form_api_v1_public_contact_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ContactFormRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ContactFormResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/public/orders/{confirmation_token}":{"get":{"tags":["cbradio-qa","public-orders"],"summary":"Get order by token (No Auth)","description":"Public endpoint for stations to view their order. No authentication required.","operationId":"get_public_order_api_v1_public_orders__confirmation_token__get","parameters":[{"name":"confirmation_token","in":"path","required":true,"schema":{"type":"string","title":"Confirmation Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PublicOrderResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/public/orders/{confirmation_token}/confirm":{"post":{"tags":["cbradio-qa","public-orders"],"summary":"Confirm order (No Auth)","description":"Public endpoint for stations to confirm their order.","operationId":"confirm_public_order_api_v1_public_orders__confirmation_token__confirm_post","parameters":[{"name":"confirmation_token","in":"path","required":true,"schema":{"type":"string","title":"Confirmation Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PublicConfirmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PublicConfirmResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/public/orders/{confirmation_token}/make-good":{"post":{"tags":["cbradio-qa","public-orders"],"summary":"Request make-good (No Auth)","description":"Public endpoint for stations to request make-good.","operationId":"request_public_make_good_api_v1_public_orders__confirmation_token__make_good_post","parameters":[{"name":"confirmation_token","in":"path","required":true,"schema":{"type":"string","title":"Confirmation Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodRequestCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PublicMakeGoodResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/public/proposals/{slug}":{"get":{"tags":["cbradio-qa","public-proposals"],"summary":"Get proposal by share link slug (No Auth)","description":"Public endpoint for clients to view proposal details. No authentication required.","operationId":"get_public_proposal_api_v1_public_proposals__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PublicProposalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/public/proposals/{slug}/approve":{"post":{"tags":["cbradio-qa","public-proposals"],"summary":"Approve proposal (No Auth)","description":"Public endpoint for clients to approve a proposal.","operationId":"approve_public_proposal_api_v1_public_proposals__slug__approve_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ClientApprovalRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ClientApprovalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buy-versions/{version_id}":{"get":{"tags":["cbradio-qa","versions"],"summary":"Get version details","description":"Get detailed information about a specific version including:\n    - Version metadata and totals\n    - Commission breakdown by station classification\n    - All payee groups with their payment status","operationId":"get_version_detail_api_v1_radio_buy_versions__version_id__get","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyVersionDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","versions"],"summary":"Delete a version","description":"Delete a version from a radio buy.\n\n    **Restrictions:**\n    - Cannot delete the only version for a buy (at least one must remain)\n    - Cannot delete versions with 'paid' or 'in_progress' payment status\n\n    Deleting a version removes:\n    - The version record\n    - All payee group snapshots\n    - All payment status records\n    - All payment status history","operationId":"delete_version_api_v1_radio_buy_versions__version_id__delete","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__DeleteVersionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buy-versions/{version_id}/delta/{from_version_id}":{"get":{"tags":["cbradio-qa","versions"],"summary":"Get delta between versions","description":"Compare two versions and return the differences.\n\n    The delta includes:\n    - Summary counts (stations/groups added, removed, modified)\n    - Financial deltas (gross, commission, station payments)\n    - Detailed payee group changes\n\n    **Note:** The `from_version_id` is the older version and `version_id` is the newer version.","operationId":"get_version_delta_api_v1_radio_buy_versions__version_id__delta__from_version_id__get","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}},{"name":"from_version_id","in":"path","required":true,"schema":{"type":"string","title":"From Version Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__VersionDelta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buy-versions/{version_id}/export":{"get":{"tags":["cbradio-qa","versions"],"summary":"Export accounting spreadsheet","description":"Export version data as a CSV or Excel file for accounting purposes.\n\n    The export includes all payee groups with:\n    - Pay to, payment method, email\n    - Station count, spot count\n    - Gross amount, station payments, commission\n    - Stations (comma-separated callsigns)\n    - Payment status, paid date, reference number\n\n    **Formats:**\n    - `csv`: Comma-separated values (default)\n    - `xlsx`: Excel spreadsheet (requires openpyxl)","operationId":"export_version_api_v1_radio_buy_versions__version_id__export_get","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}},{"name":"format","in":"query","required":false,"schema":{"$ref":"#/components/schemas/cbradio-qa__ExportFormat","description":"Export format","default":"csv"},"description":"Export format"},{"name":"include_payment_status","in":"query","required":false,"schema":{"type":"boolean","description":"Include payment status columns","default":true,"title":"Include Payment Status"},"description":"Include payment status columns"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buy-versions/{version_id}/payment-history":{"get":{"tags":["cbradio-qa","versions"],"summary":"Get payment status history","description":"Get the history of payment status changes for a version.\n\n    Returns all status changes across all payee groups, sorted by change time descending.","operationId":"get_payment_history_api_v1_radio_buy_versions__version_id__payment_history_get","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PaymentHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buy-versions/{version_id}/payments/{group_key}":{"patch":{"tags":["cbradio-qa","versions"],"summary":"Update payment status","description":"Update the payment status for a payee group in a version.\n\n    **Status values:**\n    - `not_started`: Payment not yet initiated\n    - `in_progress`: Payment being processed\n    - `paid`: Payment completed\n    - `issue`: Payment has an issue requiring attention\n\n    All changes are recorded in the payment history for audit purposes.","operationId":"update_payment_status_api_v1_radio_buy_versions__version_id__payments__group_key__patch","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}},{"name":"group_key","in":"path","required":true,"schema":{"type":"string","title":"Group Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__version__PaymentStatusUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PaymentStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys":{"get":{"tags":["cbradio-qa","radio-buys"],"summary":"List radio buys","description":"List radio ad buying campaigns with optional filters.\n\n    **Filters:**\n    - `client_id`: Filter by client ID\n    - `buyer_agency_id`: Filter by buyer agency ID\n    - `consultant_id`: Filter by consultant ID\n    - `ad_type`: Filter by ad type (candidate, issue)\n    - `election_type`: Filter by election type (primary, general)\n    - `is_complete`: Filter by completion status\n    - `start_date_from/to`: Filter by start date range\n    - `min_gross/max_gross`: Filter by gross amount range\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 500.","operationId":"list_radio_buys_api_v1_radio_buys_get","parameters":[{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by client ID","title":"Client Id"},"description":"Filter by client ID"},{"name":"buyer_agency_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by buyer agency ID","title":"Buyer Agency Id"},"description":"Filter by buyer agency ID"},{"name":"consultant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by consultant ID","title":"Consultant Id"},"description":"Filter by consultant ID"},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AdTypeEnum"},{"type":"null"}],"description":"Filter by ad type","title":"Ad Type"},"description":"Filter by ad type"},{"name":"election_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ElectionType"},{"type":"null"}],"description":"Filter by election type","title":"Election Type"},"description":"Filter by election type"},{"name":"is_complete","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by completion status","title":"Is Complete"},"description":"Filter by completion status"},{"name":"is_paid","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by payment status","title":"Is Paid"},"description":"Filter by payment status"},{"name":"cycle_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by political cycle ID","title":"Cycle Id"},"description":"Filter by political cycle ID"},{"name":"start_date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date from (YYYY-MM-DD)","title":"Start Date From"},"description":"Start date from (YYYY-MM-DD)"},{"name":"start_date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date to (YYYY-MM-DD)","title":"Start Date To"},"description":"Start date to (YYYY-MM-DD)"},{"name":"min_gross","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum gross amount","title":"Min Gross"},"description":"Minimum gross amount"},{"name":"max_gross","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Maximum gross amount","title":"Max Gross"},"description":"Maximum gross amount"},{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"Sort field with direction prefix: -created_at, -start_date, -gross_amount, name","default":"-start_date","title":"Sort"},"description":"Sort field with direction prefix: -created_at, -start_date, -gross_amount, name"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__RadioBuySummary"},"title":"Response List Radio Buys Api V1 Radio Buys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","radio-buys"],"summary":"Create radio buy","description":"Create a new radio buy campaign. Requires authentication.","operationId":"create_radio_buy_api_v1_radio_buys_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuy"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/count":{"get":{"tags":["cbradio-qa","radio-buys"],"summary":"Count radio buys","description":"Return the total number of radio buys matching the given filters.\n\n    Accepts the same filter parameters as the list endpoint (client_id,\n    buyer_agency_id, ad_type, election_type, is_complete) but returns only\n    a count instead of full records.","operationId":"count_radio_buys_api_v1_radio_buys_count_get","parameters":[{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"}},{"name":"buyer_agency_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"}},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AdTypeEnum"},{"type":"null"}],"title":"Ad Type"}},{"name":"election_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ElectionType"},{"type":"null"}],"title":"Election Type"}},{"name":"is_complete","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Complete"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Count Radio Buys Api V1 Radio Buys Count Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/summary":{"get":{"tags":["cbradio-qa","radio-buys"],"summary":"Get financial summary","description":"Get aggregated financial summary across all radio buys.","operationId":"get_radio_buy_summary_api_v1_radio_buys_summary_get","parameters":[{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"}},{"name":"buyer_agency_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"}},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AdTypeEnum"},{"type":"null"}],"title":"Ad Type"}},{"name":"election_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ElectionType"},{"type":"null"}],"title":"Election Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyFinancialSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{buy_id}/versions":{"post":{"tags":["cbradio-qa","versions"],"summary":"Create a new version","description":"Create a new version snapshot for a radio buy.\n\n    This creates:\n    - An immutable snapshot of the current radio buy state\n    - Payee group aggregations\n    - Initial payment status records (not_started)\n    - Commission breakdown by station classification\n\n    **Version numbering:** Versions are numbered sequentially starting from 1.\n    The first version (v1) is typically created automatically when a proposal\n    is converted to a radio buy.","operationId":"create_version_api_v1_radio_buys__buy_id__versions_post","parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyVersionCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyVersion"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"get":{"tags":["cbradio-qa","versions"],"summary":"List versions for a radio buy","description":"List all versions for a radio buy, sorted by version number descending.\n\n    Each version includes:\n    - Version ID and number\n    - Snapshot totals (gross, commission, station payments)\n    - Station count and spot count\n    - Whether the version has provisional stations","operationId":"list_versions_api_v1_radio_buys__buy_id__versions_get","parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__VersionListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}":{"get":{"tags":["cbradio-qa","radio-buys"],"summary":"Get radio buy details","description":"Get a radio buy by ID with client, agency, and consultant details.","operationId":"get_radio_buy_api_v1_radio_buys__radio_buy_id__get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyWithRelations"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","radio-buys"],"summary":"Update radio buy (partial)","description":"Partially update a radio buy. Requires authentication.","operationId":"update_radio_buy_api_v1_radio_buys__radio_buy_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuy"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"put":{"tags":["cbradio-qa","radio-buys"],"summary":"Update radio buy","description":"Update a radio buy. Requires authentication.","operationId":"update_radio_buy_api_v1_radio_buys__radio_buy_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuy"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","radio-buys"],"summary":"Delete radio buy","description":"Delete a radio buy. Requires authentication.","operationId":"delete_radio_buy_api_v1_radio_buys__radio_buy_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/commission-recipients":{"get":{"tags":["cbradio-qa","commissions"],"summary":"List commission recipients for a radio buy","description":"Return all commission recipients attached to a radio buy, ordered by\n    creation date.\n\n    Each recipient includes the split percentage, computed split amount,\n    and payment status.","operationId":"list_commission_recipients_api_v1_radio_buys__radio_buy_id__commission_recipients_get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","commissions"],"summary":"Add commission recipient","description":"Create a new commission recipient for a radio buy. The split amount is\n    automatically calculated from the recipient's split percentage and the\n    buy's net amount.\n\n    After creation, the buy's profit is recalculated. Requires authentication.","operationId":"create_commission_recipient_api_v1_radio_buys__radio_buy_id__commission_recipients_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CommissionRecipientCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/commission-recipients/{recipient_id}":{"put":{"tags":["cbradio-qa","commissions"],"summary":"Update commission recipient","description":"Partially update an existing commission recipient. If the split percentage\n    is changed, all split amounts for the buy are recalculated.\n\n    The buy's profit is always recalculated after the update. Requires\n    authentication.","operationId":"update_commission_recipient_api_v1_radio_buys__radio_buy_id__commission_recipients__recipient_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CommissionRecipientUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","commissions"],"summary":"Delete commission recipient","description":"Remove a commission recipient from a radio buy. The buy's profit is\n    recalculated after deletion. Requires authentication.","operationId":"delete_commission_recipient_api_v1_radio_buys__radio_buy_id__commission_recipients__recipient_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/commission-rules":{"get":{"tags":["cbradio-qa","commissions"],"summary":"List commission rules for a radio buy","description":"Return all commission rules attached to a radio buy, ordered by creation\n    date.\n\n    Each rule defines per-category commission rates (in-network, non-rural,\n    Salem, total gross, rural gross) and includes the computed payout amount\n    and hold status.","operationId":"list_commission_rules_api_v1_radio_buys__radio_buy_id__commission_rules_get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","commissions"],"summary":"Add commission rule","description":"Create a new commission rule for a radio buy. Rules define per-category\n    commission rates for a named recipient (consultant, agency, etc.).\n\n    Supports optional custom basis amount, hold status, and notes. Requires\n    authentication.","operationId":"create_commission_rule_api_v1_radio_buys__radio_buy_id__commission_rules_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CommissionRuleCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/commission-rules/{rule_id}":{"put":{"tags":["cbradio-qa","commissions"],"summary":"Update commission rule","description":"Partially update an existing commission rule. Any provided rate fields,\n    hold status, or custom basis amount are updated.\n\n    Decimal fields are converted to float for storage. Requires authentication.","operationId":"update_commission_rule_api_v1_radio_buys__radio_buy_id__commission_rules__rule_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CommissionRuleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","commissions"],"summary":"Delete commission rule","description":"Remove a commission rule from a radio buy. Requires authentication.","operationId":"delete_commission_rule_api_v1_radio_buys__radio_buy_id__commission_rules__rule_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/commission-splits":{"put":{"tags":["cbradio-qa","radio-buys"],"summary":"Update commission splits","description":"Update the commission splits for a radio buy.\n\n    Commission splits track how commission is distributed among agencies, consultants, etc.","operationId":"update_commission_splits_api_v1_radio_buys__radio_buy_id__commission_splits_put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__CommissionSplitsUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Commission Splits Api V1 Radio Buys  Radio Buy Id  Commission Splits Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/consultants/{consultant_id}":{"post":{"tags":["cbradio-qa","radio-buys"],"summary":"Add consultant to radio buy","description":"Associate a consultant with a radio buy. Requires authentication.","operationId":"add_consultant_to_radio_buy_api_v1_radio_buys__radio_buy_id__consultants__consultant_id__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Consultant To Radio Buy Api V1 Radio Buys  Radio Buy Id  Consultants  Consultant Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","radio-buys"],"summary":"Remove consultant from radio buy","description":"Remove a consultant from a radio buy. Requires authentication.","operationId":"remove_consultant_from_radio_buy_api_v1_radio_buys__radio_buy_id__consultants__consultant_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/network-items":{"put":{"tags":["cbradio-qa","radio-buys"],"summary":"Replace wired-network items for a radio buy","description":"Replace-all sync endpoint for wired-network (StateNets, etc.) buys. Deletes every existing `radio_buy_network_item` for the given buy and inserts the provided list atomically. cbradio-ui is the single writer — see #201 for design context.","operationId":"replace_radio_buy_network_items_api_v1_radio_buys__radio_buy_id__network_items_put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__NetworkItemsReplaceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__NetworkItemsReplaceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"get":{"tags":["cbradio-qa","radio-buys"],"summary":"List wired-network items for a radio buy","description":"Returns all `radio_buy_network_item` rows for the given buy.","operationId":"list_radio_buy_network_items_api_v1_radio_buys__radio_buy_id__network_items_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__NetworkItemOut"},"title":"Response List Radio Buy Network Items Api V1 Radio Buys  Radio Buy Id  Network Items Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/order-packets":{"get":{"tags":["cbradio-qa","order-packets"],"summary":"Get order packets for radio buy","description":"List all order packets for a specific radio buy.","operationId":"get_order_packets_for_radio_buy_api_v1_radio_buys__radio_buy_id__order_packets_get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OrderPacketListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/order-packets/generate":{"post":{"tags":["cbradio-qa","order-packets"],"summary":"Generate order packets","description":"Generate order packets for a radio buy, grouped by station owner/payee.","operationId":"generate_order_packets_api_v1_radio_buys__radio_buy_id__order_packets_generate_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__OrderPacketGenerate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__GenerateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/payment-status":{"patch":{"tags":["cbradio-qa","radio-buys"],"summary":"Update payment status","description":"Mark a radio buy as paid or unpaid. Auto-sets paid_date when marking paid.","operationId":"update_payment_status_api_v1_radio_buys__radio_buy_id__payment_status_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__src__api__routes__radio_buys__PaymentStatusUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/placements":{"get":{"tags":["cbradio-qa","radio-buys"],"summary":"List placements for a radio buy","description":"Get all station placements for a radio buy.","operationId":"list_placements_api_v1_radio_buys__radio_buy_id__placements_get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyPlacement"},"title":"Response List Placements Api V1 Radio Buys  Radio Buy Id  Placements Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","radio-buys"],"summary":"Add placement to radio buy","description":"Add a station placement to a radio buy. Requires authentication.","operationId":"add_placement_api_v1_radio_buys__radio_buy_id__placements_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PlacementCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyPlacement"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/placements/generate-defaults":{"post":{"tags":["cbradio-qa","radio-buys"],"summary":"Generate default placements from proposal or station data","description":"Auto-generate `radio_buy_placement` records from the proposal\nline items (if the buy was converted from a proposal) or from existing\n`radio_buy_station` records as a fallback.\n\n**Use this when:** `convert_proposal()` created the buy and its station records\nbut no placements exist yet, blocking order packet generation.\n\n**cbradio#251**: each row now persists `source_rate_card_id` (resolved by\nstation+year via the materializer's priority order) so the three-axis\norder_packets gates (cbradio#239 unapproved_planning_rate, cbradio#240\nstale_rate) have the snapshot they need. Also uses ISO-week enumeration\nfrom the buy's flight dates rather than a sequential `[1..N]` placeholder.\n\n**Guards:**\n- 409 if placements already exist (use `?replace=true` to override —\n  deletes existing placements first, for the admin recovery path)\n- 404 if no source data found","operationId":"generate_default_placements_api_v1_radio_buys__radio_buy_id__placements_generate_defaults_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"replace","in":"query","required":false,"schema":{"type":"boolean","description":"cbradio#251: when true, delete existing placements before inserting new ones. Default false (preserves the 409 guard). Used by the admin recovery flow.","default":false,"title":"Replace"},"description":"cbradio#251: when true, delete existing placements before inserting new ones. Default false (preserves the 409 guard). Used by the admin recovery flow."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__GenerateDefaultsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/placements/{placement_id}":{"delete":{"tags":["cbradio-qa","radio-buys"],"summary":"Remove placement from radio buy","description":"Remove a placement from a radio buy. Requires authentication.","operationId":"remove_placement_api_v1_radio_buys__radio_buy_id__placements__placement_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"placement_id","in":"path","required":true,"schema":{"type":"string","title":"Placement Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/recalculate-commissions":{"post":{"tags":["cbradio-qa","commissions"],"summary":"Recalculate all commission amounts using the engine","description":"Run the financial calculation engine on a radio buy to recompute all\n    commission amounts.\n\n    Reads the buy's placements and commission rules, classifies station\n    spend into rural / non-rural / in-network / Salem buckets, then applies\n    each rule's rates to produce per-recipient payouts. Updates the buy's\n    financial fields (rural_gross, rural_net, non_rural_gross, non_rural_net,\n    payments_to_stations, profit) and each rule's computed_amount in the\n    database.\n\n    Returns the full financial breakdown including recipient payouts, held\n    amounts, and final profit. Requires authentication.","operationId":"recalculate_commissions_api_v1_radio_buys__radio_buy_id__recalculate_commissions_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/traffic-assets":{"get":{"tags":["cbradio-qa","traffic-assets"],"summary":"List traffic assets for radio buy","description":"Get all audio spots and NAB form for a radio buy.","operationId":"list_traffic_assets_api_v1_radio_buys__radio_buy_id__traffic_assets_get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__TrafficAssetsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","traffic-assets"],"summary":"Create traffic asset","description":"Register a new traffic asset after file upload.","operationId":"create_traffic_asset_api_v1_radio_buys__radio_buy_id__traffic_assets_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__TrafficAssetCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__TrafficAssetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/traffic-assets/reorder":{"put":{"tags":["cbradio-qa","traffic-assets"],"summary":"Reorder audio spots","description":"Bulk update rotation order for all audio spots.","operationId":"reorder_audio_spots_api_v1_radio_buys__radio_buy_id__traffic_assets_reorder_put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ReorderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio-buys/{radio_buy_id}/traffic-assets/upload-url":{"post":{"tags":["cbradio-qa","traffic-assets"],"summary":"Get presigned upload URL","description":"Get a presigned URL for direct file upload to CBFiles.","operationId":"get_upload_url_api_v1_radio_buys__radio_buy_id__traffic_assets_upload_url_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__UploadUrlRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__UploadUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/radio/districts/{geoid}":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Get stations covering a district by geoid","description":"Returns all stations whose coverage contours intersect with a district,\nusing the raw geoid format from districts.campaignbrain.dev.\n\n**Path Parameters:**\n- `geoid` - District geoid (e.g., MI-07, MI-SD-24, 26065)\n\n**Query Parameters:**\n- `min_coverage` - Minimum coverage percentage (0-100)\n- `in_network_only` - Only return in-network stations","operationId":"get_district_stations_by_geoid_api_v1_radio_districts__geoid__get","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","title":"Geoid"}},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"default":0,"title":"Min Coverage"}},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only in-network stations","default":false,"title":"In Network Only"},"description":"Only in-network stations"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by am or fm","title":"Service Type"},"description":"Filter by am or fm"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"AM only: day, night, or service","title":"Contour Type"},"description":"AM only: day, night, or service"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"List rate cards","description":"List rate cards with optional filters.\n\n    **Filters:**\n    - `station_id`: Filter by station ID\n    - `year`: Filter by rate card year\n    - `rate_type`: Filter by rate type (e.g., 'Political', 'Gross')\n    - `source`: Filter by ingestion source (e.g., 'iheart', 'katz', 'amfm')\n    - `lifecycle`: Filter by `rate_card.lifecycle` — `planning`, `final`, or\n      `all` (default). `lifecycle` is the placement gate (cbradio#205,\n      cbradio#239): `planning` cards block order-packet generation until ops\n      approves via `POST /rate-cards/{id}/approve-planning`. Distinct from\n      `review_status` (parser-accuracy / QA axis).\n    - `staleness`: Filter by freshness — `fresh`, `stale`, or `all` (default).\n      Stale = not verified within 365 days (cbradio#240). Independent of\n      `lifecycle`; the order_packets gate blocks both.\n\n    **Response shape:**\n    Each row includes `review_status` (parser QA), `lifecycle` (placement\n    gate), and `is_stale` (freshness gate) as three independent fields.\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 500.","operationId":"list_rate_cards_api_v1_rate_cards_get","parameters":[{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station ID","title":"Station Id"},"description":"Filter by station ID"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station callsign(s), comma-separated and case-insensitive (cbradio#339). Use this instead of `station_id` when you have a callsign rather than an internal UUID.","examples":["KATK-AM"],"title":"Callsign"},"description":"Filter by station callsign(s), comma-separated and case-insensitive (cbradio#339). Use this instead of `station_id` when you have a callsign rather than an internal UUID."},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by year","title":"Year"},"description":"Filter by year"},{"name":"rate_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by rate type","title":"Rate Type"},"description":"Filter by rate type"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by ingestion source","title":"Source"},"description":"Filter by ingestion source"},{"name":"lifecycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(planning|final|all)$"},{"type":"null"}],"description":"Placement-gate filter on `rate_card.lifecycle`: 'planning' (blocks order packets until approved), 'final' (cleared for placement), or 'all' / omit for no filter. See cbradio#205 / cbradio#239 for the full axis definition.","title":"Lifecycle"},"description":"Placement-gate filter on `rate_card.lifecycle`: 'planning' (blocks order packets until approved), 'final' (cleared for placement), or 'all' / omit for no filter. See cbradio#205 / cbradio#239 for the full axis definition."},{"name":"staleness","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(fresh|stale|all)$"},{"type":"null"}],"description":"Freshness filter (cbradio#240): 'fresh' (verified within 365 days), 'stale' (>365 days since last verify), or 'all' / omit for no filter. Independent of `lifecycle`.","title":"Staleness"},"description":"Freshness filter (cbradio#240): 'fresh' (verified within 365 days), 'stale' (>365 days since last verify), or 'all' / omit for no filter. Independent of `lifecycle`."},{"name":"review_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Parser-QA filter on `rate_card.review_status` (cbradio#237 / cbradio#239): 'pending' / 'in_review' / 'needs_review' / 'needs_follow_up' / 'approved' / 'rejected' / 'needs_correction' / 'all' (or omit) for no filter. Independent of `lifecycle` and `staleness`.","title":"Review Status"},"description":"Parser-QA filter on `rate_card.review_status` (cbradio#237 / cbradio#239): 'pending' / 'in_review' / 'needs_review' / 'needs_follow_up' / 'approved' / 'rejected' / 'needs_correction' / 'all' (or omit) for no filter. Independent of `lifecycle` and `staleness`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","rate-cards"],"summary":"Create rate card with rates","description":"Create a new rate card with nested rates in a single atomic transaction.\n\n    **Auto-calculation:**\n    - If only `gross_rate` is provided, `net_rate` is calculated as `gross * 0.85`\n    - If only `net_rate` is provided, `gross_rate` is calculated as `net / 0.85`\n\n    **Denormalization:**\n    Station callsign and state are automatically populated on each rate.","operationId":"create_rate_card_api_v1_rate_cards_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCardCreateWithRates"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCardWithRates"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/active":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Get active rate cards","description":"Return rate cards that are currently active based on their effective and\n    expiration dates (effective_date <= today and expiration_date is null or\n    >= today).\n\n    Supports filtering by network membership and state.","operationId":"get_active_rate_cards_api_v1_rate_cards_active_get","parameters":[{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is In Network"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/bulk-review":{"post":{"tags":["cbradio-qa","rate-cards"],"summary":"Bulk review multiple rate cards","description":"Submit the same review decision for multiple rate cards at once.\n\n    **Use cases:**\n    - Approve multiple AI-extracted rate cards after verification\n    - Reject a batch of outdated rate cards\n    - Mark multiple cards as needing correction\n\n    Returns a summary of successes and failures.","operationId":"bulk_review_rate_cards_api_v1_rate_cards_bulk_review_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"decision","in":"query","required":true,"schema":{"type":"string","description":"Review decision: approved, rejected, needs_correction","title":"Decision"},"description":"Review decision: approved, rejected, needs_correction"},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Review notes for all cards","title":"Notes"},"description":"Review notes for all cards"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"title":"Rate Card Ids"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkOperationResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/by-market/{market}":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Get rate cards by market","description":"Return rate cards for a specific market or DMA. Matches against both\n    the rate card's market field and the station's Nielsen DMA using partial\n    matching.\n\n    Supports filtering by year and network membership.","operationId":"get_rate_cards_by_market_api_v1_rate_cards_by_market__market__get","parameters":[{"name":"market","in":"path","required":true,"schema":{"type":"string","title":"Market"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}},{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is In Network"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/corrections/export":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Export corrections for OCR training","description":"Returns all corrections made within a date range for OCR training purposes.\n\n    **Query Parameters:**\n    - `start_date`: Start of date range (ISO 8601)\n    - `end_date`: End of date range (ISO 8601)\n    - `format`: `json` (default) or `csv`","operationId":"export_corrections_for_ocr_api_v1_rate_cards_corrections_export_get","parameters":[{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"Start date (YYYY-MM-DD)","title":"Start Date"},"description":"Start date (YYYY-MM-DD)"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"End date (YYYY-MM-DD)","title":"End Date"},"description":"End date (YYYY-MM-DD)"},{"name":"format","in":"query","required":false,"schema":{"type":"string","description":"Export format: json or csv","default":"json","title":"Format"},"description":"Export format: json or csv"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/export":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Export rate cards in consumer format","description":"Export rate cards with their rates grouped by ad type and preemption\n    level.\n\n    Returns a consumer-friendly format with station info, network membership\n    status, and nested rate data. Supports filtering by network membership,\n    year, market, and active status.","operationId":"export_rate_cards_api_v1_rate_cards_export_get","parameters":[{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by network membership","title":"Is In Network"},"description":"Filter by network membership"},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by year","title":"Year"},"description":"Filter by year"},{"name":"market","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by market (partial match)","title":"Market"},"description":"Filter by market (partial match)"},{"name":"active_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only active rate cards","default":false,"title":"Active Only"},"description":"Only active rate cards"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/export/{callsign}":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Export rate card for a station","description":"Export rate card(s) for a specific station identified by callsign.\n\n    By default returns the most recent rate card. Use `all=true` to return\n    every rate card for the station. Optionally filter by year. Includes\n    inherited rate cards from parent stations when applicable.","operationId":"export_station_rate_card_api_v1_rate_cards_export__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by year","title":"Year"},"description":"Filter by year"},{"name":"all","in":"query","required":false,"schema":{"type":"boolean","description":"Return all rate cards","default":false,"title":"All"},"description":"Return all rate cards"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/my-claims":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Get my claimed rate cards","description":"Returns all rate cards currently claimed by the authenticated user.","operationId":"get_my_claimed_rate_cards_api_v1_rate_cards_my_claims_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/rate-cards/qa-stats":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Get QA queue statistics","description":"Returns aggregate statistics about the QA queue and reviewer activity.\n\n    **Query Parameters:**\n    - `period`: `day`, `week`, `month` (default: `week`)","operationId":"get_qa_statistics_api_v1_rate_cards_qa_stats_get","parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","description":"Period: day, week, or month","default":"week","title":"Period"},"description":"Period: day, week, or month"},{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by station network membership (includes Salem)","title":"Is In Network"},"description":"Filter by station network membership (includes Salem)"},{"name":"exclude_ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exclude these ownership groups (comma-separated)","examples":["salem,iheart,katz,cumulus,townsquare,audacy,beasley,saga"],"title":"Exclude Ownership Group"},"description":"Exclude these ownership groups (comma-separated)"},{"name":"review_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by review status: pending, approved, rejected, needs_follow_up","title":"Review Status"},"description":"Filter by review status: pending, approved, rejected, needs_follow_up"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/review-queue":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Get rate cards pending review","description":"Returns rate cards that are pending review, ordered by submission date.\n\n    **Filters:**\n    - `status`: Filter by review status (pending, approved, rejected, needs_correction)\n    - `source`: Filter by submission source (manual, ai_extracted, imported)\n\n    **Finding one station (cbradio#339):**\n    - `q`: free-text search over station callsign and Nielsen DMA,\n      case-insensitive substring. `%` and `_` are matched literally.\n    - `callsign`: exact callsign, or a comma-separated list. Case-insensitive.\n    - `state`: 2-letter state code, or a comma-separated list.\n    - `market`: case-insensitive substring of the Nielsen DMA.\n\n    All four are applied in SQL and are reflected in `total`, so paging a\n    filtered queue is correct. They combine with AND.\n\n    Rate cards enter the review queue when they have review_status='pending'.","operationId":"get_review_queue_api_v1_rate_cards_review_queue_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by review status","examples":["pending"],"title":"Status"},"description":"Filter by review status"},{"name":"review_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias for `status` (cbradio#339). The sibling `GET /rate-cards` spells this filter `review_status`, and callers — including cbradio-ui — were sending that name here, where it was silently ignored. `status` wins if both are given.","examples":["pending"],"title":"Review Status"},"description":"Alias for `status` (cbradio#339). The sibling `GET /rate-cards` spells this filter `review_status`, and callers — including cbradio-ui — were sending that name here, where it was silently ignored. `status` wins if both are given."},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by rate-card year (cbradio#339).","examples":[2026],"title":"Year"},"description":"Filter by rate-card year (cbradio#339)."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search callsign or Nielsen DMA (case-insensitive substring). Wildcards are matched literally.","examples":["KATK"],"title":"Q"},"description":"Search callsign or Nielsen DMA (case-insensitive substring). Wildcards are matched literally."},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact callsign(s), comma-separated. Case-insensitive.","examples":["KATK-AM"],"title":"Callsign"},"description":"Exact callsign(s), comma-separated. Case-insensitive."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"State code(s), comma-separated. Case-insensitive.","examples":["NM"],"title":"State"},"description":"State code(s), comma-separated. Case-insensitive."},{"name":"scope","in":"query","required":false,"schema":{"type":"string","description":"cbradio#377. `in_scope` (default) | `out_of_scope` | `all`.\n\n**The default is not a filter, it is the honest count.** The queue held 1,505 pending cards against 2 human verifications ever recorded; 1,154 of them (77%) are for stations we have never bought from and do not target, so counting them made the real work — 351 cards — invisible inside a number nobody could ever drain.\n\nOut of scope is not rejected and not low priority: nobody is expected to look at it. It says nothing about whether the rate can be quoted — an out-of-scope card still prices a plan and obeys every selection rule. Re-run `apply_review_scope.py` when the target states change.","examples":["in_scope"],"default":"in_scope","title":"Scope"},"description":"cbradio#377. `in_scope` (default) | `out_of_scope` | `all`.\n\n**The default is not a filter, it is the honest count.** The queue held 1,505 pending cards against 2 human verifications ever recorded; 1,154 of them (77%) are for stations we have never bought from and do not target, so counting them made the real work — 351 cards — invisible inside a number nobody could ever drain.\n\nOut of scope is not rejected and not low priority: nobody is expected to look at it. It says nothing about whether the rate can be quoted — an out-of-scope card still prices a plan and obeys every selection rule. Re-run `apply_review_scope.py` when the target states change."},{"name":"market","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Nielsen DMA substring, case-insensitive.","examples":["Albuquerque"],"title":"Market"},"description":"Nielsen DMA substring, case-insensitive."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Include only these sources (comma-separated)","examples":["ai_extracted"],"title":"Source"},"description":"Include only these sources (comma-separated)"},{"name":"exclude_source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exclude these sources (comma-separated)","examples":["salem,katz,iheart,cumulus,townsquare,audacy,beasley,saga"],"title":"Exclude Source"},"description":"Exclude these sources (comma-separated)"},{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by station network membership (includes Salem per #132)","title":"Is In Network"},"description":"Filter by station network membership (includes Salem per #132)"},{"name":"ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Include only these ownership groups (comma-separated)","examples":["salem,iheart"],"title":"Ownership Group"},"description":"Include only these ownership groups (comma-separated)"},{"name":"exclude_ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exclude these ownership groups (comma-separated)","examples":["salem,iheart,katz,cumulus,townsquare,audacy,beasley,saga"],"title":"Exclude Ownership Group"},"description":"Exclude these ownership groups (comma-separated)"},{"name":"order_by","in":"query","required":false,"schema":{"type":"string","description":"`submitted_at` (default, arrival order) or `risk` (cbradio#371). Risk ordering scores every card matching the filters against the same checks that keep a bad number out of a quote — zero and sentinel prices, rates a fraction of their class benchmark, :30/:60 conflicts, an unreadable source document, a card holding nothing buyable — and returns `risk_score` and `risk_findings` on each item. Opt-in because it reads every matching card's rates, which arrival order does not.","examples":["risk"],"default":"submitted_at","title":"Order By"},"description":"`submitted_at` (default, arrival order) or `risk` (cbradio#371). Risk ordering scores every card matching the filters against the same checks that keep a bad number out of a quote — zero and sentinel prices, rates a fraction of their class benchmark, :30/:60 conflicts, an unreadable source document, a card holding nothing buyable — and returns `risk_score` and `risk_findings` on each item. Opt-in because it reads every matching card's rates, which arrival order does not."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Maximum results","default":50,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/summary":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Get rate card summary statistics","description":"Returns aggregate statistics about rate cards in the system.\n\n    **Included metrics:**\n    - Total rate cards and rates\n    - Rate cards by year\n    - Rate cards by review status\n    - Rate range statistics (min/max/avg)\n    - Coverage statistics (stations with rate cards)\n\n    **`state`** (optional): restrict every aggregation to one 2-letter state.\n    cbradio-ui#135/#140: the SRM page calls this once per state; honoring `state`\n    turns the ~6.5s full-database scan into a fast indexed lookup and returns the\n    correct per-state numbers (previously every state got the global totals).\n    Omit `state` for the global summary (unchanged behavior).\n\n    Results are cached briefly (5 min) per state.","operationId":"get_rate_card_summary_api_v1_rate_cards_summary_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional 2-letter state code to scope the summary (e.g. MI).","title":"State"},"description":"Optional 2-letter state code to scope the summary (e.g. MI)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Get rate card with rates","description":"Get a rate card by ID with all associated rates embedded.\n\n    Returns the rate card metadata plus an array of rate objects.","operationId":"get_rate_card_api_v1_rate_cards__rate_card_id__get","parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCardWithRates"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","rate-cards"],"summary":"Partial update rate card","description":"Update rate card metadata (not rates). Only provided fields are updated.\n\n    To update rates, use the bulk rate endpoints:\n    - `PATCH /rate-cards/{id}/rates` - Update multiple rates\n    - `POST /rate-cards/{id}/rates` - Add rates\n    - `DELETE /rate-cards/{id}/rates` - Remove rates","operationId":"update_rate_card_api_v1_rate_cards__rate_card_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCardUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCardWithRates"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","rate-cards"],"summary":"Delete rate card","description":"Delete a rate card and all associated rates (cascade delete).\n\n    **Warning:** This operation is irreversible.","operationId":"delete_rate_card_api_v1_rate_cards__rate_card_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}/approve-planning":{"post":{"tags":["cbradio-qa","rate-cards"],"summary":"Approve a PLANNING rate card for committed pricing","description":"Flips a rate_card with lifecycle='planning' into the approved state, recording who approved it, when, and an optional note. Once approved, the card is treated as FINAL by the order-packet generate gate — placements priced from it no longer block packet generation. This is the ops-facing approval path per cbradio#205.","operationId":"approve_planning_rate_card_api_v1_rate_cards__rate_card_id__approve_planning_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__PlanningApprovalBody"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PlanningApprovalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}/claim":{"post":{"tags":["cbradio-qa","rate-cards"],"summary":"Claim rate card for review","description":"Assigns the rate card to the current user for review, preventing other users\n    from working on it simultaneously.\n\n    **Business Rules:**\n    - Only rate cards with `review_status: pending` or `in_review` (unclaimed) can be claimed\n    - A user can only have up to 5 rate cards claimed at once\n    - Claiming a rate card updates `review_status` to `in_review`","operationId":"claim_rate_card_api_v1_rate_cards__rate_card_id__claim_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}/qa-detail":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Get rate card detail for QA review","description":"Returns complete rate card data including extracted rates, source document info,\n    and any existing warnings or corrections for QA review.","operationId":"get_rate_card_qa_detail_api_v1_rate_cards__rate_card_id__qa_detail_get","parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}/rates":{"post":{"tags":["cbradio-qa","rate-cards"],"summary":"Add rates to rate card","description":"Add multiple rates to an existing rate card in a single atomic transaction.\n\n    All rates will inherit the station_id from the rate card.\n\n    **Auto-calculation:**\n    - If only `gross_rate` provided, `net_rate = gross * 0.85`\n    - If only `net_rate` provided, `gross_rate = net / 0.85`","operationId":"bulk_create_rates_api_v1_rate_cards__rate_card_id__rates_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkRateCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkOperationResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","rate-cards"],"summary":"Bulk update rates","description":"Update multiple rates by ID. Allows partial success - failed updates\n    are reported in the errors array.\n\n    Each update object must include the rate `id` plus fields to update.","operationId":"bulk_update_rates_api_v1_rate_cards__rate_card_id__rates_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkRateUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkOperationResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","rate-cards"],"summary":"Bulk delete rates","description":"Delete multiple rates by ID from a rate card.\n\n    Only rates belonging to the specified rate card will be deleted.","operationId":"bulk_delete_rates_api_v1_rate_cards__rate_card_id__rates_delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkRateDelete"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkOperationResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}/review":{"post":{"tags":["cbradio-qa","rate-cards"],"summary":"Submit review decision for rate card","description":"Submit a review decision for a rate card (approve, reject, or request corrections).\n\n    **Decision values:**\n    - `approved`: Rate card is approved for use\n    - `rejected`: Rate card is rejected (with reason in notes)\n    - `needs_correction`: Rate card needs corrections before approval\n\n    **What `decision='approved'` does (cbradio#243):**\n    Approval flips three axes atomically on the same human action — it's\n    one ops gesture, three signals:\n      - `review_status: → 'approved'` (parser QA confirmed)\n      - `lifecycle: 'planning' → 'final'` (placement gate cleared) — only\n        when currently `'planning'`. `'approved_planning'` is preserved so\n        the iHeart ops-approval flow isn't overwritten.\n      - `last_verified_at: → now()` (freshness timer reset per cbradio#240)\n    For `rejected` / `needs_correction`, only `review_status` changes.\n\n    **Corrections:**\n    When decision is `needs_correction`, you can provide a list of corrections\n    specifying which fields need to be fixed.\n\n    This endpoint creates a review history record for audit purposes.\n\n    **`rates_written` (cbradio#309):**\n    The response carries `rates_written` — the number of `rate` rows this call\n    created or updated from `approved_rates`. It is `0` for a decision other\n    than `approved`, and `0` when no `approved_rates` were supplied.\n\n    **Approved rates are validated BEFORE the decision is applied.** An unknown\n    `daypart_id` or an unresolvable station is a `400` that changes nothing —\n    the card keeps its previous review status and no rates are written. This\n    used to be swallowed, leaving the card marked `approved` with none of its\n    rates persisted and a `200` that looked like success. Validation covers the\n    whole payload, so one bad daypart rejects the entire approval; fix it and\n    re-submit.","operationId":"review_rate_card_api_v1_rate_cards__rate_card_id__review_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}},{"name":"decision","in":"query","required":true,"schema":{"type":"string","description":"Review decision: approved, rejected, needs_correction","examples":["approved"],"title":"Decision"},"description":"Review decision: approved, rejected, needs_correction"},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Review notes","title":"Notes"},"description":"Review notes"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ReviewApprovalBody"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}/review-context":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Structured review context for a rate card (cbradio#237)","description":"Return everything a human or MCP agent needs to make a review decision on\n    a single rate card, in one structured response:\n\n    - `card` — metadata (id, callsign, year, source, axis state, confidence)\n    - `rates` — full rate rows attached to this card\n    - `source_document` — OCR text + extraction status for the underlying doc\n    - `siblings.same_station_year` — other cards on the same (station, year)\n       with their rate counts and a sample of rates (most useful for diff\n       reasoning across re-extractions or cross-source comparisons)\n    - `siblings.same_document` — other cards extracted from the same document\n       (rare, but happens when extraction was re-run and produced a new card)\n    - `signals` — derived flags an agent can short-circuit on: rate_count,\n       extraction_confidence, all_rates_null_slot, any_zero_gross_rate,\n       siblings_available\n\n    Read-only. To act on the decision, call\n    `POST /api/v1/rate-cards/{rate_card_id}/review?decision=approved|rejected|needs_correction`\n    (cbradio#243 — atomic three-axis flip on approval).\n\n    Designed to feed an MCP review tool but works directly from curl too.","operationId":"get_rate_card_review_context_api_v1_rate_cards__rate_card_id__review_context_get","parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}/unclaim":{"post":{"tags":["cbradio-qa","rate-cards"],"summary":"Unclaim rate card","description":"Releases the rate card so another user can claim it.\n\n    **Business Rules:**\n    - Users can only unclaim rate cards they have claimed (unless admin)\n    - Unclaiming sets `review_status` back to `pending`\n    - The reason field is optional but recommended for audit purposes","operationId":"unclaim_rate_card_api_v1_rate_cards__rate_card_id__unclaim_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}},{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Reason for unclaiming","title":"Reason"},"description":"Reason for unclaiming"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}/verify":{"post":{"tags":["cbradio-qa","rate-cards"],"summary":"Mark a rate card as freshly verified","description":"Records that a human has confirmed the rate card is still accurate. Updates `last_verified_at` to now() and resets the staleness gate. Distinct from `/approve-planning` — verification is the freshness axis (cbradio#240), placement approval is the lifecycle axis (cbradio#205). The order_packets generator blocks both stale and unapproved-planning rates independently.","operationId":"verify_rate_card_api_v1_rate_cards__rate_card_id__verify_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__VerifyRateCardBody"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__VerifyRateCardResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}/warnings":{"get":{"tags":["cbradio-qa","rate-cards"],"summary":"Get warnings for rate card","description":"Returns all warnings for a rate card, including acknowledgment status.","operationId":"get_rate_card_warnings_api_v1_rate_cards__rate_card_id__warnings_get","parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-cards/{rate_card_id}/warnings/{warning_id}/acknowledge":{"post":{"tags":["cbradio-qa","rate-cards"],"summary":"Acknowledge warning","description":"Marks a warning as acknowledged by the reviewer.\n\n    **Business Rules:**\n    - Only the user who claimed the rate card can acknowledge warnings\n    - Error-level warnings must be acknowledged before the rate card can be approved\n    - Warning-level and info-level warnings can be acknowledged but are not required","operationId":"acknowledge_warning_api_v1_rate_cards__rate_card_id__warnings__warning_id__acknowledge_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}},{"name":"warning_id","in":"path","required":true,"schema":{"type":"string","title":"Warning Id"}},{"name":"note","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Acknowledgment note","title":"Note"},"description":"Acknowledgment note"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-corrections":{"post":{"tags":["cbradio-qa","Rate Corrections"],"summary":"Create a rate correction","description":"Create a new rate correction record.\n\n    All authenticated users can create corrections with status 'pending_review'.\n    Only admins can create corrections with status 'verified' (immediate approval).","operationId":"create_rate_correction_api_v1_rate_corrections_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCorrectionCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCorrection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"get":{"tags":["cbradio-qa","Rate Corrections"],"summary":"List rate corrections","description":"List rate corrections with optional filters.","operationId":"list_rate_corrections_api_v1_rate_corrections_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"station","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station callsign","title":"Station"},"description":"Filter by station callsign"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status: pending_review, verified, rejected","title":"Status"},"description":"Filter by status: pending_review, verified, rejected"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-corrections/{correction_id}":{"get":{"tags":["cbradio-qa","Rate Corrections"],"summary":"Get a rate correction","description":"Get details of a specific rate correction.","operationId":"get_rate_correction_api_v1_rate_corrections__correction_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"correction_id","in":"path","required":true,"schema":{"type":"string","title":"Correction Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-corrections/{correction_id}/reject":{"post":{"tags":["cbradio-qa","Rate Corrections"],"summary":"Reject a rate correction","description":"Reject a pending rate correction. Admin only.","operationId":"reject_rate_correction_api_v1_rate_corrections__correction_id__reject_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"correction_id","in":"path","required":true,"schema":{"type":"string","title":"Correction Id"}},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Rejection reason","title":"Notes"},"description":"Rejection reason"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rate-corrections/{correction_id}/verify":{"post":{"tags":["cbradio-qa","Rate Corrections"],"summary":"Verify a rate correction","description":"Verify (approve) a pending rate correction. Admin only.","operationId":"verify_rate_correction_api_v1_rate_corrections__correction_id__verify_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"correction_id","in":"path","required":true,"schema":{"type":"string","title":"Correction Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCorrectionVerify"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rates":{"get":{"tags":["cbradio-qa","rates"],"summary":"Query political advertising rates","description":"Search and filter political advertising rates across all stations.\n\n    \n**Rate Fields:**\n- `ad_type`: Type of ad - `candidate` (LUR protected), `issue` (PAC/advocacy), `all`\n- `rate_context`: `in_window` (during LUR period), `out_of_window`, `all`\n- `preemption`: `non_preemptible` (guaranteed), `preemptible` (can be bumped)\n- `slot_days`: Day pattern - `m-f`, `m-sat`, `sat-sun`, `sat`, `sun`, `all`\n- `duration_seconds`: Spot length - 15, 30, 60, 90, or 120 seconds\n- `gross_rate`: Rate before agency commission (15%)\n- `net_rate`: Rate after commission (gross * 0.85)\n\n\n    **Example queries:**\n    - `/api/v1/rates?state=TX&ad_type=candidate` - Texas candidate rates\n    - `/api/v1/rates?station=KAAA-AM&duration=60` - 60-second spots for KAAA-AM (exact match)\n    - `/api/v1/rates?callsign=KAAA&duration=60` - 60-second spots matching KAAA (partial match)\n    - `/api/v1/rates?year=2024&preemption=non_preemptible` - 2024 guaranteed rates\n    - `/api/v1/rates?min_rate=10&max_rate=50` - Rates between $10-$50\n\n    **Parameter Aliases:**\n    - `station` (exact match) or `callsign` (partial match)\n    - `campaign_type` or `ad_type`\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 1000.","operationId":"list_rates_api_v1_rates_get","parameters":[{"name":"station","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by exact station callsign","examples":["KAAA-AM"],"title":"Station"},"description":"Filter by exact station callsign"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station callsign (partial match)","examples":["KAAA"],"title":"Callsign"},"description":"Filter by station callsign (partial match)"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state abbreviation","examples":["TX"],"title":"State"},"description":"Filter by state abbreviation"},{"name":"dma","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by Nielsen DMA market (partial match)","examples":["Dallas"],"title":"Dma"},"description":"Filter by Nielsen DMA market (partial match)"},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Ad type: candidate, issue, or all","examples":["candidate"],"title":"Ad Type"},"description":"Ad type: candidate, issue, or all"},{"name":"campaign_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias for ad_type: candidate, issue","examples":["candidate"],"title":"Campaign Type"},"description":"Alias for ad_type: candidate, issue"},{"name":"duration","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Spot duration in seconds: 15, 30, 60, 90, 120","examples":[60],"title":"Duration"},"description":"Spot duration in seconds: 15, 30, 60, 90, 120"},{"name":"preemption","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Preemption status: non_preemptible, preemptible","examples":["non_preemptible"],"title":"Preemption"},"description":"Preemption status: non_preemptible, preemptible"},{"name":"pricing","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pricing type: gross, net (filters to rates with that value)","examples":["net"],"title":"Pricing"},"description":"Pricing type: gross, net (filters to rates with that value)"},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Rate card year","examples":[2024],"title":"Year"},"description":"Rate card year"},{"name":"min_rate","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum gross or net rate","examples":[10.0],"title":"Min Rate"},"description":"Minimum gross or net rate"},{"name":"max_rate","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Maximum gross or net rate","examples":[50.0],"title":"Max Rate"},"description":"Maximum gross or net rate"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum results to return","default":100,"title":"Limit"},"description":"Maximum results to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip","default":0,"title":"Offset"},"description":"Number of results to skip"},{"name":"include_diagnostics","in":"query","required":false,"schema":{"type":"boolean","description":"Include diagnostic info when results are empty","default":false,"title":"Include Diagnostics"},"description":"Include diagnostic info when results are empty"},{"name":"include_simulcast","in":"query","required":false,"schema":{"type":"boolean","description":"If station has no rates, include rates from simulcast partner","default":true,"title":"Include Simulcast"},"description":"If station has no rates, include rates from simulcast partner"}],"responses":{"200":{"description":"List of rate objects matching filters","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","rates"],"summary":"Create a single rate","description":"Create a new rate entry.\n\n    **Auto-calculation:**\n    - If only `gross_rate` provided, `net_rate = gross * 0.85`\n    - If only `net_rate` provided, `gross_rate = net / 0.85`\n\n    **Denormalization:**\n    Station callsign and state are automatically populated from the station.","operationId":"create_rate_api_v1_rates_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rates/benchmarks":{"get":{"tags":["cbradio-qa","rates"],"summary":"Power-class benchmark — median issue :60/:30 by (state, power bucket)","description":"Median ISSUE advertising rate (`:60` and `:30`), grouped by US state and power\n    bucket (`lt2kw` <2kW · `2to10kw` 2–10kW · `10to50kw` 10–50kW · `50kwplus` 50kW+),\n    computed live from the rate table. Feeds the RCI Planning Budget estimate mode\n    (`shared/planningBudget` `BenchmarkLookup`).\n\n    **Always resolves** a number for any real US state via per-metric progressive\n    widening: `state+bucket` → `state` → `bucket` (national) → `national`. `:60` and\n    `:30` widen independently (a bucket may have one but not the other). `basis`\n    reports which level each value came from; `n` is the state+bucket sample size.\n\n    `GET /api/v1/rates/benchmarks?states=AL,MS` →\n    `{ \"AL\": { \"lt2kw\": {\"sec60\": 23, \"sec30\": 17, \"basis\": {...}, \"n\": 12}, ... }, \"MS\": {...} }`","operationId":"rate_benchmarks_api_v1_rates_benchmarks_get","parameters":[{"name":"states","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated state abbreviations, e.g. 'AL,MS'.","title":"States"},"description":"Comma-separated state abbreviations, e.g. 'AL,MS'."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rates/by-ad-type":{"get":{"tags":["cbradio-qa","rates"],"summary":"List rates grouped by ad type","description":"Return rate statistics broken down by ad type (e.g., `candidate`, `issue`).\n\n    Each row includes the rate count, average gross/net rate, and the\n    min/max gross rate for that ad type.","operationId":"rates_by_ad_type_api_v1_rates_by_ad_type_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/rates/by-duration":{"get":{"tags":["cbradio-qa","rates"],"summary":"List rates grouped by spot duration","description":"Return rate statistics broken down by spot duration in seconds\n    (e.g., 15, 30, 60, 90, 120).\n\n    Each row includes the rate count and average gross/net rate for that\n    duration bucket.","operationId":"rates_by_duration_api_v1_rates_by_duration_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/rates/by-state":{"get":{"tags":["cbradio-qa","rates"],"summary":"List rates grouped by state","description":"Return rate counts and average gross/net rates for each US state.\n\n    Each row includes the number of rates, number of unique stations,\n    and the average gross and net rate for that state.\n\n    Results are sorted alphabetically by state abbreviation.","operationId":"rates_by_state_api_v1_rates_by_state_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/rates/count":{"get":{"tags":["cbradio-qa","rates"],"summary":"Count rates matching filters","description":"Return the total number of rate records that match the given filter criteria.\n\n    Supports filtering by station callsign (exact or partial match), state,\n    ad type (or its alias `campaign_type`), spot duration, and rate card year.","operationId":"count_rates_api_v1_rates_count_get","parameters":[{"name":"station","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by exact station callsign","title":"Station"},"description":"Filter by exact station callsign"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station callsign (partial match)","title":"Callsign"},"description":"Filter by station callsign (partial match)"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ad Type"}},{"name":"campaign_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias for ad_type","title":"Campaign Type"},"description":"Alias for ad_type"},{"name":"duration","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rates/coverage":{"get":{"tags":["cbradio-qa","rates"],"summary":"Political-rate coverage / do-not-quote roster (cbradio#309)","description":"The fall-through guard. Classifies each station's **political-rate\n    coverage** so the proposal / QA surfaces can flag stations that would\n    otherwise be priced off stale `ad_type='all'` blended ROS rows (the\n    KQFN-AM false-overcharge bug).\n\n    Per-station verdict (`src/lib/rate_coverage.py`):\n    - **`ok`** — a typed candidate/issue rate exists for the current cycle.\n    - **`stale`** — newest `rate_card_year` < `election_year` (do-not-quote).\n    - **`untyped`** — current-year rows but only `ad_type='all'` (verify first).\n    - **`no_rates`** — no quotable rate rows (do-not-quote).\n\n    Quotability mirrors the proposal builder: only rows with `gross_rate > 0`\n    count. This endpoint is **read-only** and does not change rate selection —\n    it is the signal the front end / backfill prioritization consume.\n\n    Returns portfolio counts, a stale-by-year breakdown, and a sample roster\n    (filter via `status`).","operationId":"rate_coverage_api_v1_rates_coverage_get","parameters":[{"name":"network","in":"query","required":false,"schema":{"type":"string","pattern":"^(in|out|all)$","description":"Network scope: 'in' (in-network, default), 'out', 'all'.","default":"in","title":"Network"},"description":"Network scope: 'in' (in-network, default), 'out', 'all'."},{"name":"election_year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":2100,"minimum":2000},{"type":"null"}],"description":"Freshness bar — a newest card year below this is 'stale'. Defaults to the current calendar year.","title":"Election Year"},"description":"Freshness bar — a newest card year below this is 'stale'. Defaults to the current calendar year."},{"name":"status","in":"query","required":false,"schema":{"type":"string","pattern":"^(do_not_quote|stale|untyped|no_rates|ok|all)$","description":"Which verdicts to include in the sample roster: 'do_not_quote' (stale + no_rates, default), 'stale', 'untyped', 'no_rates', 'ok', or 'all'. Counts always cover every station.","default":"do_not_quote","title":"Status"},"description":"Which verdicts to include in the sample roster: 'do_not_quote' (stale + no_rates, default), 'stale', 'untyped', 'no_rates', 'ok', or 'all'. Counts always cover every station."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional 2-letter state filter.","title":"State"},"description":"Optional 2-letter state filter."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"description":"Max stations in the sample.","default":100,"title":"Limit"},"description":"Max stations in the sample."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rates/summary":{"get":{"tags":["cbradio-qa","rates"],"summary":"Get rate statistics summary","description":"Returns aggregate statistics for all rates in the database.\n\n    **Included metrics:**\n    - Total rate count\n    - Unique stations and states\n    - Min/max/avg gross rates\n    - Min/max/avg net rates\n\n    This endpoint is useful for AI systems to quickly understand the rate data scope.","operationId":"rate_summary_api_v1_rates_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/rates/years":{"get":{"tags":["cbradio-qa","rates"],"summary":"List rate card years","description":"Return every rate card year present in the database along with the number\n    of rate records in each year.\n\n    Results are sorted by year descending (most recent first).","operationId":"list_years_api_v1_rates_years_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/rates/{rate_id}":{"get":{"tags":["cbradio-qa","rates"],"summary":"Get rate by ID","description":"Retrieve a single rate record by its unique ID.\n\n    The response includes all rate fields plus a computed `daypart_id`\n    derived from the slot start/end times or slot name.\n\n    Returns 404 if the rate ID does not exist.","operationId":"get_rate_api_v1_rates__rate_id__get","parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"put":{"tags":["cbradio-qa","rates"],"summary":"Replace a rate","description":"Replace a rate entirely with new data.\n\n    All fields must be provided (except optional fields).\n    For partial updates, use PATCH instead.","operationId":"replace_rate_api_v1_rates__rate_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","rates"],"summary":"Partial update a rate","description":"Update specific fields of a rate. Only provided fields are updated.\n\n    For specialized updates, consider using:\n    - `PATCH /rates/{id}/pricing` - Update only gross/net rates\n    - `PATCH /rates/{id}/time-slot` - Update only time slot fields\n    - `PATCH /rates/{id}/classification` - Update only ad_type, rate_context, preemption","operationId":"update_rate_api_v1_rates__rate_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__RateUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","rates"],"summary":"Delete a rate","description":"Delete a rate by ID. This operation is irreversible.","operationId":"delete_rate_api_v1_rates__rate_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rates/{rate_id}/classification":{"patch":{"tags":["cbradio-qa","rates"],"summary":"Adjust classification only","description":"Update only the classification fields (ad_type, rate_context, preemption).\n\n    This endpoint is optimized for AI correction workflows that need to fix\n    only the rate classification without touching pricing or time slots.\n\n    **Fields:**\n    - `ad_type`: candidate, issue, or all\n    - `rate_context`: in_window, out_of_window, or all\n    - `preemption`: non_preemptible, preemptible, preemptible_with_notice, or all","operationId":"adjust_classification_api_v1_rates__rate_id__classification_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ClassificationAdjustment"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rates/{rate_id}/pricing":{"patch":{"tags":["cbradio-qa","rates"],"summary":"Adjust pricing only","description":"Update only the pricing fields (gross_rate, net_rate).\n\n    **Auto-calculation:**\n    - If only `gross_rate` provided, `net_rate = gross * 0.85`\n    - If only `net_rate` provided, `gross_rate = net / 0.85`\n\n    This endpoint is optimized for AI correction workflows that need to fix\n    only the rate values without touching other fields.","operationId":"adjust_pricing_api_v1_rates__rate_id__pricing_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PricingAdjustment"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/rates/{rate_id}/time-slot":{"patch":{"tags":["cbradio-qa","rates"],"summary":"Adjust time slot only","description":"Update only the time slot fields (slot_days, slot_start_time, slot_end_time, slot_name).\n\n    This endpoint is optimized for AI correction workflows that need to fix\n    only the time slot information without touching pricing or classification.","operationId":"adjust_time_slot_api_v1_rates__rate_id__time_slot_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__TimeSlotAdjustment"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/simulcast-groups":{"post":{"tags":["cbradio-qa","simulcast-groups"],"summary":"Create simulcast group","description":"Create a new simulcast group for stations that share programming.","operationId":"create_simulcast_group_api_v1_simulcast_groups_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SimulcastGroupCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SimulcastGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"get":{"tags":["cbradio-qa","simulcast-groups"],"summary":"List simulcast groups","description":"Retrieve a paginated list of simulcast groups with optional search.","operationId":"list_simulcast_groups_api_v1_simulcast_groups_get","parameters":[{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name or callsign","title":"Search"},"description":"Search by name or callsign"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/simulcast-groups/by-callsign/{callsign}":{"get":{"tags":["cbradio-qa","simulcast-groups"],"summary":"Find simulcast group by callsign","description":"Returns the simulcast group containing the specified callsign, or 404 if not in any group.","operationId":"get_simulcast_group_by_callsign_api_v1_simulcast_groups_by_callsign__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/simulcast-groups/{group_id}":{"get":{"tags":["cbradio-qa","simulcast-groups"],"summary":"Get simulcast group","description":"Retrieve a single simulcast group by ID.","operationId":"get_simulcast_group_api_v1_simulcast_groups__group_id__get","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SimulcastGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"put":{"tags":["cbradio-qa","simulcast-groups"],"summary":"Update simulcast group","description":"Update an existing simulcast group.","operationId":"update_simulcast_group_api_v1_simulcast_groups__group_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SimulcastGroupUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SimulcastGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","simulcast-groups"],"summary":"Delete simulcast group","description":"Delete a simulcast group.","operationId":"delete_simulcast_group_api_v1_simulcast_groups__group_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/srm/alerts":{"get":{"tags":["cbradio-qa","srm"],"summary":"List SRM alerts","description":"List system-generated alerts for stations needing attention.\n\n    **Alert Types:**\n    - `ownership_change`: Station ownership changed recently\n    - `agreement_expiring`: Agreement expires within 30 days\n    - `no_contact`: Active station with no contact in 90+ days\n\n    **Filters:**\n    - `dismissed`: Include dismissed alerts (default: false)\n    - `alert_type`: Filter by specific alert type\n    - `station_callsign`: Filter by station","operationId":"list_alerts_api_v1_srm_alerts_get","parameters":[{"name":"dismissed","in":"query","required":false,"schema":{"type":"boolean","description":"Include dismissed alerts","default":false,"title":"Dismissed"},"description":"Include dismissed alerts"},{"name":"alert_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by alert type","title":"Alert Type"},"description":"Filter by alert type"},{"name":"station_callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station","title":"Station Callsign"},"description":"Filter by station"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Results per page","default":50,"title":"Limit"},"description":"Results per page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__AlertList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/srm/alerts/{alert_id}/dismiss":{"post":{"tags":["cbradio-qa","srm"],"summary":"Dismiss an alert","description":"Mark an alert as dismissed. Dismissed alerts won't appear in the default list.","operationId":"dismiss_alert_api_v1_srm_alerts__alert_id__dismiss_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SRMAlert"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/srm/dashboard/coverage":{"get":{"tags":["cbradio-qa","srm"],"summary":"Get coverage analytics by state","description":"Returns coverage analysis by state, showing:\n    - Total stations in each state\n    - In-network stations\n    - Coverage percentage\n    - Prospects available\n    - At-risk stations needing attention\n\n    Useful for identifying expansion opportunities and risk areas.","operationId":"get_coverage_by_state_api_v1_srm_dashboard_coverage_get","parameters":[{"name":"min_stations","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Minimum stations to include state","default":1,"title":"Min Stations"},"description":"Minimum stations to include state"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/srm/dashboard/coverage/dma":{"get":{"tags":["cbradio-qa","srm"],"summary":"Get coverage analytics by DMA","description":"Returns coverage analysis by Nielsen DMA (Designated Market Area).\n    Similar to state coverage but grouped by media market.","operationId":"get_coverage_by_dma_api_v1_srm_dashboard_coverage_dma_get","parameters":[{"name":"min_stations","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Minimum stations to include DMA","default":1,"title":"Min Stations"},"description":"Minimum stations to include DMA"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/srm/dashboard/metrics":{"get":{"tags":["cbradio-qa","srm"],"summary":"Get SRM dashboard metrics","description":"Returns summary metrics for the SRM dashboard.\n\n    **Station Metrics:**\n    - Total stations in system\n    - Breakdown by lifecycle state\n    - In-network vs out-of-network counts\n\n    **Task Metrics:**\n    - Total pending/in-progress tasks\n    - Tasks due today\n    - Overdue tasks\n    - Breakdown by task type\n\n    **Contact Metrics:**\n    - Contacts logged today\n    - Contacts logged this week\n    - Contacts logged this month","operationId":"get_dashboard_metrics_api_v1_srm_dashboard_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/srm/signals/{callsign}":{"get":{"tags":["cbradio-qa","srm"],"summary":"Get computed signals for a station","description":"Returns computed signals and health metrics for a specific station.\n\n    **Signals:**\n    - `priority_score`: Overall outreach priority (0-100, higher = more urgent)\n    - `relationship_health`: Health of relationship (good/warning/critical)\n    - `outreach_urgency`: How urgently this station needs contact\n    - `revenue_risk`: Risk of losing revenue from this station\n\n    Signals are computed on-demand based on current SRM data.","operationId":"get_station_signals_api_v1_srm_signals__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/statenets/network-buys":{"post":{"tags":["cbradio-qa","statenets"],"summary":"Create Network Buy","description":"Create a new StateNets network buy with commission calculations.","operationId":"create_network_buy_api_v1_statenets_network_buys_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__NetworkBuyCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"get":{"tags":["cbradio-qa","statenets"],"summary":"List Network Buys","description":"List StateNets network buys with optional filters.","operationId":"list_network_buys_api_v1_statenets_network_buys_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proposal Id"}},{"name":"radio_buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"}},{"name":"network_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Network Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/statenets/network-buys/{buy_id}":{"get":{"tags":["cbradio-qa","statenets"],"summary":"Get Network Buy","description":"Get a single StateNets network buy with network details.","operationId":"get_network_buy_api_v1_statenets_network_buys__buy_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","statenets"],"summary":"Update Network Buy","description":"Update status, flight_start, or flight_end on a network buy.","operationId":"update_network_buy_api_v1_statenets_network_buys__buy_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__NetworkBuyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/statenets/networks":{"get":{"tags":["cbradio-qa","statenets"],"summary":"List Networks","description":"List StateNets networks with optional filters.","operationId":"list_networks_api_v1_statenets_networks_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by 2-letter state code","title":"State"},"description":"Filter by 2-letter state code"},{"name":"network_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by network type (wired/unwired)","title":"Network Type"},"description":"Filter by network type (wired/unwired)"},{"name":"product_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by product type","title":"Product Type"},"description":"Filter by product type"},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active status","title":"Is Active"},"description":"Filter by active status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/statenets/networks/{network_id}":{"get":{"tags":["cbradio-qa","statenets"],"summary":"Get Network","description":"Get a single StateNets network with its affiliates inline.","operationId":"get_network_api_v1_statenets_networks__network_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"network_id","in":"path","required":true,"schema":{"type":"string","title":"Network Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"put":{"tags":["cbradio-qa","statenets"],"summary":"Update Network","description":"Update editable fields on a StateNets network.","operationId":"update_network_api_v1_statenets_networks__network_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"network_id","in":"path","required":true,"schema":{"type":"string","title":"Network Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__NetworkUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/statenets/networks/{network_id}/affiliates":{"get":{"tags":["cbradio-qa","statenets"],"summary":"List Affiliates","description":"List affiliates for a network (paginated).","operationId":"list_affiliates_api_v1_statenets_networks__network_id__affiliates_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"network_id","in":"path","required":true,"schema":{"type":"string","title":"Network Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/statenets/networks/{network_id}/overlaps":{"get":{"tags":["cbradio-qa","statenets"],"summary":"Get Network Overlaps","description":"Return affiliates that are matched to in-network stations or signed agreements.","operationId":"get_network_overlaps_api_v1_statenets_networks__network_id__overlaps_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"network_id","in":"path","required":true,"schema":{"type":"string","title":"Network Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/statenets/networks/{network_id}/scores":{"get":{"tags":["cbradio-qa","statenets"],"summary":"Score all affiliates in a StateNets network","description":"Scores all affiliates in a network using the same 0-16 scale as individual stations. Returns per-affiliate scores and aggregate averages. Affiliates with excluded formats (score=-1) are included in the response but excluded from aggregate averages. Unmatched affiliates get format-only scoring.","operationId":"get_network_scores_api_v1_statenets_networks__network_id__scores_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"network_id","in":"path","required":true,"schema":{"type":"string","title":"Network Id"}},{"name":"target_audience","in":"query","required":true,"schema":{"type":"string","description":"Audience profile: republican_primary, democratic_primary, or swing_general","title":"Target Audience"},"description":"Audience profile: republican_primary, democratic_primary, or swing_general"},{"name":"target_states","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated state codes for coverage calculation (e.g. 'MI,WI')","title":"Target States"},"description":"Comma-separated state codes for coverage calculation (e.g. 'MI,WI')"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__NetworkScoresResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/states/{state_code}/congressional-districts":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Get congressional districts for a state","description":"Returns all congressional districts for a state.\n\n**Query Parameters:**\n- `include_coverage_stats` - Include station coverage statistics\n- `include_representative` - Include current representative info","operationId":"get_state_congressional_districts_api_v1_states__state_code__congressional_districts_get","parameters":[{"name":"state_code","in":"path","required":true,"schema":{"type":"string","title":"State Code"}},{"name":"include_coverage_stats","in":"query","required":false,"schema":{"type":"boolean","description":"Include coverage statistics","default":true,"title":"Include Coverage Stats"},"description":"Include coverage statistics"},{"name":"include_representative","in":"query","required":false,"schema":{"type":"boolean","description":"Include representative info","default":true,"title":"Include Representative"},"description":"Include representative info"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/states/{state_code}/counties":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Get counties for a state","description":"Returns all counties for a state.","operationId":"get_state_counties_api_v1_states__state_code__counties_get","parameters":[{"name":"state_code","in":"path","required":true,"schema":{"type":"string","title":"State Code"}},{"name":"include_coverage_stats","in":"query","required":false,"schema":{"type":"boolean","description":"Include coverage statistics","default":true,"title":"Include Coverage Stats"},"description":"Include coverage statistics"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/states/{state_code}/coverage-summary":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Get coverage summary for a state","description":"Returns an aggregated coverage summary for a state.\n\n**Includes:**\n- Congressional district coverage stats\n- State legislative district coverage (if available)\n- County coverage stats\n- In-network station counts\n- Coverage gaps identified","operationId":"get_state_coverage_summary_api_v1_states__state_code__coverage_summary_get","parameters":[{"name":"state_code","in":"path","required":true,"schema":{"type":"string","title":"State Code"}},{"name":"include_legislative","in":"query","required":false,"schema":{"type":"boolean","description":"Include state legislative districts","default":true,"title":"Include Legislative"},"description":"Include state legislative districts"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/states/{state_code}/state-house-districts":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Get state house districts","description":"Returns state house districts for a state. Full boundary data available for some states; basic district list from coverage cache for others.","operationId":"get_state_house_districts_api_v1_states__state_code__state_house_districts_get","parameters":[{"name":"state_code","in":"path","required":true,"schema":{"type":"string","title":"State Code"}},{"name":"include_coverage_stats","in":"query","required":false,"schema":{"type":"boolean","description":"Include coverage statistics","default":true,"title":"Include Coverage Stats"},"description":"Include coverage statistics"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/states/{state_code}/state-senate-districts":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Get state senate districts","description":"Returns state senate districts for a state. Full boundary data available for some states; basic district list from coverage cache for others.","operationId":"get_state_senate_districts_api_v1_states__state_code__state_senate_districts_get","parameters":[{"name":"state_code","in":"path","required":true,"schema":{"type":"string","title":"State Code"}},{"name":"include_coverage_stats","in":"query","required":false,"schema":{"type":"boolean","description":"Include coverage statistics","default":true,"title":"Include Coverage Stats"},"description":"Include coverage statistics"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations":{"get":{"tags":["cbradio-qa","stations"],"summary":"List radio stations","description":"Retrieve a paginated list of radio stations with optional filters.\n\n    **Filters:**\n    - `state`: Two-letter state abbreviation (e.g., TX, CA, FL)\n    - `dma`: Nielsen DMA market name (partial match)\n    - `status`: Station network status (signed, pending, inactive, prospect)\n    - `in_network`: Whether station is in the Rural AM/FM network (see below)\n    - `tag`: Filter by status tag (e.g., salem, iheart, cumulus)\n    - `search`: Partial callsign match (e.g., \"KAA\" matches KAAA-AM, KAAB-FM)\n\n    **in_network Flag:**\n    - `true` (1,602 stations): Station has signed agreement with Rural AM/FM network.\n      These stations get priority in proposal generation and have negotiated rates.\n    - `false` (421 stations): Station is not in network (prospect, competitor, or inactive).\n      Rates for these stations may be estimated or from public filings.\n\n    **Example queries:**\n    - `/api/v1/stations?state=TX&status=signed` - Signed Texas stations\n    - `/api/v1/stations?dma=Dallas&limit=50` - Dallas market stations\n    - `/api/v1/stations?in_network=true&state=MI` - Michigan in-network stations\n    - `/api/v1/stations?tag=salem` - Salem Media stations\n    - `/api/v1/stations?search=KAAA` - Stations matching KAAA\n    - `/api/v1/stations?lifecycle_state=active` - Active lifecycle stations\n    - `/api/v1/stations?lifecycle_state=at_risk` - At-risk stations needing attention","operationId":"list_stations_api_v1_stations_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state abbreviation (e.g., TX, CA)","examples":["TX"],"title":"State"},"description":"Filter by state abbreviation (e.g., TX, CA)"},{"name":"dma","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by Nielsen DMA market (partial match)","examples":["Dallas"],"title":"Dma"},"description":"Filter by Nielsen DMA market (partial match)"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station status: signed, pending, inactive, prospect","examples":["signed"],"title":"Status"},"description":"Station status: signed, pending, inactive, prospect"},{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by network membership (in-network OR salem-tagged)","title":"Is In Network"},"description":"Filter by network membership (in-network OR salem-tagged)"},{"name":"is_salem","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by Salem status tag","title":"Is Salem"},"description":"Filter by Salem status tag"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status tag (e.g., salem, iheart)","examples":["salem"],"title":"Tag"},"description":"Filter by status tag (e.g., salem, iheart)"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search callsign (partial match)","examples":["KAAA"],"title":"Search"},"description":"Search callsign (partial match)"},{"name":"lifecycle_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by SRM lifecycle state: prospect, contacted, negotiating, active, at_risk, churned, inactive","examples":["active"],"title":"Lifecycle State"},"description":"Filter by SRM lifecycle state: prospect, contacted, negotiating, active, at_risk, churned, inactive"},{"name":"ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by ownership group (e.g., iheart, salem). Use 'null' for unassigned.","examples":["iheart"],"title":"Ownership Group"},"description":"Filter by ownership group (e.g., iheart, salem). Use 'null' for unassigned."},{"name":"has_email","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter stations that have (true) or lack (false) a contact email","title":"Has Email"},"description":"Filter stations that have (true) or lack (false) a contact email"},{"name":"has_mailing_address","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter stations that have (true) or lack (false) a structured mailing address","title":"Has Mailing Address"},"description":"Filter stations that have (true) or lack (false) a structured mailing address"},{"name":"format","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station format (e.g., talk, news_talk, country)","examples":["talk"],"title":"Format"},"description":"Filter by station format (e.g., talk, news_talk, country)"},{"name":"national_rep_firm","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by national rep firm (partial match)","examples":["katz"],"title":"National Rep Firm"},"description":"Filter by national rep firm (partial match)"},{"name":"exclude_non_commercial","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Exclude non-commercial stations (is_commercial=false)","title":"Exclude Non Commercial"},"description":"Exclude non-commercial stations (is_commercial=false)"},{"name":"station_pool","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station pool filter: 'network_only' (in-network + salem) or 'all'","examples":["network_only"],"title":"Station Pool"},"description":"Station pool filter: 'network_only' (in-network + salem) or 'all'"},{"name":"classification","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by classification: in_network, salem, out_of_network, provisional","examples":["in_network"],"title":"Classification"},"description":"Filter by classification: in_network, salem, out_of_network, provisional"},{"name":"valid_callsign_only","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only return stations with valid FCC callsigns (W/K/C/X prefix, 3-5 letters). Defaults to True — pass false to include junk/numeric callsigns.","default":true,"title":"Valid Callsign Only"},"description":"Only return stations with valid FCC callsigns (W/K/C/X prefix, 3-5 letters). Defaults to True — pass false to include junk/numeric callsigns."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum results to return","default":100,"title":"Limit"},"description":"Maximum results to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip","default":0,"title":"Offset"},"description":"Number of results to skip"}],"responses":{"200":{"description":"List of station objects","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/batch-coverage":{"post":{"tags":["cbradio-qa","coverage"],"summary":"Get coverage data for multiple stations","description":"Get district coverage data for multiple stations in a single request.\n\n**Request:** Comma-separated list of facility IDs (max 100). Optional `states`\nfilter (e.g. `MI,IN`) restricts the districts returned and selects which\nper-state overlap entries appear in `state_overlap`.\n\n**Response:**\n- `stations[].districts` — per-district coverage (existing shape)\n- `stations[].state_overlap` — per-state \"% of station's signal inside this\n  state\" (new, cbradio#217). Empty list if the station's contour is not\n  indexed or no states requested matched.\n- `errors` / `summary` — unchanged","operationId":"get_batch_station_coverage_api_v1_stations_batch_coverage_post","parameters":[{"name":"facility_ids","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated facility IDs (max 100)","title":"Facility Ids"},"description":"Comma-separated facility IDs (max 100)"},{"name":"district_types","in":"query","required":false,"schema":{"type":"string","description":"Filter district types","default":"all","title":"District Types"},"description":"Filter district types"},{"name":"district_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Singular alias of `district_types`. Accepts a single value (e.g. `state_senate`). Provided because the singular form was previously silently ignored — cbradio#227 surfaced this.","title":"District Type"},"description":"Singular alias of `district_types`. Accepts a single value (e.g. `state_senate`). Provided because the singular form was previously silently ignored — cbradio#227 surfaced this."},{"name":"states","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional comma-separated 2-letter state codes (e.g. 'MI,IN'). Filters districts to those inside the requested states and restricts the state_overlap array to matching states.","title":"States"},"description":"Optional comma-separated 2-letter state codes (e.g. 'MI,IN'). Filters districts to those inside the requested states and restricts the state_overlap array to matching states."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/bulk-contact-import":{"post":{"tags":["cbradio-qa","stations"],"summary":"Bulk import contact data","description":"Import contact data (email, name, mailing address) for multiple stations.\n\n    - Only updates NULL fields (does not overwrite existing values)\n    - Sets contact_source\n    - Audit logs each change","operationId":"bulk_contact_import_api_v1_stations_bulk_contact_import_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkContactImportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/stations/bulk-ownership-import":{"post":{"tags":["cbradio-qa","stations"],"summary":"Bulk import ownership groups","description":"Import ownership group assignments for multiple stations.\n\n    - Validates ownership_group values\n    - Skips stations with an existing ownership_group_override\n    - Audit logs each change","operationId":"bulk_ownership_import_api_v1_stations_bulk_ownership_import_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__BulkOwnershipImportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/stations/by-geography":{"post":{"tags":["cbradio-qa","coverage"],"summary":"Get stations covering selected geographies","description":"Returns stations that cover the specified geographies.\n\n**Used by proposal workflow** when user selects target districts.\n\n**Request Body:**\n- `geography_type` - Type of geography (congressional, state_senate, state_house, county)\n- `geography_ids` - List of district/county IDs to find coverage for (max 20)\n- `filters` - Optional filters (in_network_only, min_coverage_percentage, has_rate_card)\n\n**Response includes:**\n- Station details with cbradio enrichment (in_network, has_rate_card)\n- Coverage percentage for each selected geography\n- Recommendation score for ranking","operationId":"get_stations_by_geography_api_v1_stations_by_geography_post","parameters":[{"name":"geography_type","in":"query","required":true,"schema":{"type":"string","description":"Type: congressional, state_senate, state_house, county","title":"Geography Type"},"description":"Type: congressional, state_senate, state_house, county"},{"name":"geography_ids","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated list of district/county IDs","title":"Geography Ids"},"description":"Comma-separated list of district/county IDs"},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only return in-network stations","default":false,"title":"In Network Only"},"description":"Only return in-network stations"},{"name":"has_rate_card_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only return stations with rate cards","default":false,"title":"Has Rate Card Only"},"description":"Only return stations with rate cards"},{"name":"min_coverage_percentage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage %","default":0,"title":"Min Coverage Percentage"},"description":"Minimum coverage %"},{"name":"threshold_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only stations meeting 50% threshold","default":false,"title":"Threshold Only"},"description":"Only stations meeting 50% threshold"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort by: recommendation, coverage, callsign","default":"recommendation","title":"Sort By"},"description":"Sort by: recommendation, coverage, callsign"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by service type: 'am' or 'fm'","title":"Service Type"},"description":"Filter by service type: 'am' or 'fm'"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"For AM: 'day', 'night', or 'service'. FM only supports 'service'.","title":"Contour Type"},"description":"For AM: 'day', 'night', or 'service'. FM only supports 'service'."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/count":{"get":{"tags":["cbradio-qa","stations"],"summary":"Count stations matching filters","description":"Return the total number of stations that match the given filter criteria.\n\n    Supports filtering by state, DMA, status, network membership, status tags,\n    SRM lifecycle state, ownership group, and contact data presence (email,\n    mailing address).\n\n    Pass `ownership_group=null` to count stations with no ownership group assigned.","operationId":"count_stations_api_v1_stations_count_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"dma","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dma"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by network membership","title":"Is In Network"},"description":"Filter by network membership"},{"name":"is_salem","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by Salem status tag","title":"Is Salem"},"description":"Filter by Salem status tag"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status tag (e.g., salem)","title":"Tag"},"description":"Filter by status tag (e.g., salem)"},{"name":"lifecycle_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by SRM lifecycle state","title":"Lifecycle State"},"description":"Filter by SRM lifecycle state"},{"name":"ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by ownership group. Use 'null' for unassigned.","title":"Ownership Group"},"description":"Filter by ownership group. Use 'null' for unassigned."},{"name":"has_email","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by contact email presence","title":"Has Email"},"description":"Filter by contact email presence"},{"name":"has_mailing_address","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by structured mailing address presence","title":"Has Mailing Address"},"description":"Filter by structured mailing address presence"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/coverage-summary":{"get":{"tags":["cbradio-qa","stations"],"summary":"Get station coverage summary","description":"Returns aggregate statistics about station coverage across states and DMAs.\n\n    **Included metrics:**\n    - Total stations and in-network stations\n    - Coverage by state (top 10)\n    - Coverage by DMA (top 10)\n    - Format distribution\n    - Lifecycle state distribution\n\n    This endpoint is useful for dashboards and quick overviews of network coverage.","operationId":"get_coverage_summary_api_v1_stations_coverage_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/stations/dmas":{"get":{"tags":["cbradio-qa","stations"],"summary":"List DMAs with station counts","description":"Return every unique Nielsen DMA (Designated Market Area) that has at least\n    one station, along with the number of stations in each DMA.\n\n    Optionally filter to DMAs that have at least one station in a given `state`\n    (2-letter code, case-insensitive).\n\n    Results are sorted alphabetically by DMA name.","operationId":"list_dmas_api_v1_stations_dmas_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/facility-conflicts":{"get":{"tags":["cbradio-qa","stations"],"summary":"Stations sharing an fcc_facility_id (cbradio#330)","description":"Every `fcc_facility_id` held by more than one station. A facility ID is issued per licensed facility, so sharing one is a contradiction, not a tolerance — and since callsign text is already unreliable (#308), it leaves no trustworthy join key on the table at all.\n\nEach row is flagged `band_mismatch` when its own `frequency` and its `fcc_frequency` are in different broadcast bands. That is the decisive tell that the row is carrying a **sibling's** FCC identity rather than its own — the observed cause is enrichment copied from the AM sibling instead of looked up per licence. A bare uniqueness count says a facility is shared; this says which row is wrong.\n\n`resolution` is `recorded` when some row in the group names another via `parent_callsign` (how the WTKZ pair was closed on #334), `unresolved` otherwise. Use `parent_callsign` to retire a group from this report without deleting the evidence.","operationId":"facility_conflicts_api_v1_stations_facility_conflicts_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"Restrict to in-network stations (the money-relevant set)","default":true,"title":"In Network Only"},"description":"Restrict to in-network stations (the money-relevant set)"},{"name":"unresolved_only","in":"query","required":false,"schema":{"type":"boolean","description":"Hide groups already documented via parent_callsign","default":false,"title":"Unresolved Only"},"description":"Hide groups already documented via parent_callsign"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/import/precisiontrak":{"post":{"tags":["cbradio-qa","stations"],"summary":"Import PrecisionTrak enrichment data","description":"Import station enrichment data from PrecisionTrak CSV.\n\n    **Behavior:**\n    - Matches stations by callsign + band (e.g., \"WBRN\" + \"AM\" → \"WBRN-AM\")\n    - PT-specific fields (`national_rep_firm`, `is_commercial`, `county`) always overwrite\n    - Contact fields only fill NULL values (never overwrite existing)\n    - Returns counts: imported, skipped, not_found, errors","operationId":"import_precisiontrak_api_v1_stations_import_precisiontrak_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PrecisionTrakImportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/stations/network-drift":{"get":{"tags":["cbradio-qa","stations"],"summary":"Stations where is_in_network disagrees with Airtable (cbradio#318)","description":"Every station whose stored `is_in_network` contradicts what its persisted Airtable status implies. Airtable is the source of truth for whether an agreement exists, but nothing in cbradio recorded it before #318 — `airtable_id` was NULL on all 16,455 rows and the sync never wrote `is_in_network`, so the flag driving a 15%-vs-50% commission split (a 3.3x swing) drifted with no way to see it.\n\nThis makes the disagreement **visible, not authoritative**: it reports, it never rewrites. Applying a flip is a reviewed decision (cbradio#308's per-owner backfill), because `is_in_network` is also legitimately set by ops for stations Airtable does not cover.\n\n`direction` is what the flip WOULD be: `turn_on` = Airtable says ours but the flag says no; `turn_off` = the flag claims ours but Airtable disagrees (the overpaying direction).\n\nA station whose Airtable record has a BLANK Status is reported separately as `no_signal`, never as drift. 2,975 records are blank; an empty Status is an incomplete record, not Airtable asserting non-membership, and treating absence of evidence as evidence is how #308 over-counted in the first place.","operationId":"network_drift_api_v1_stations_network_drift_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional two-letter state filter","title":"State"},"description":"Optional two-letter state filter"},{"name":"direction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one direction: turn_on | turn_off","title":"Direction"},"description":"Filter to one direction: turn_on | turn_off"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/network-universe":{"get":{"tags":["cbradio-qa","stations"],"summary":"Tiered station universe for a state (cbradio#318)","description":"Every station in a state grouped by commission/payee **tier** (`rci_direct | salem | oon | prospect`) — the queryable replacement for the hand-maintained CSV exports the MI Caucus plan relied on. Consumers read `tier`, never the raw `is_in_network` boolean (which flattens Salem and prospects — the source of WDTK's wrong in-network 2x boost). Optional `tier` filter (comma-separated).","operationId":"network_universe_api_v1_stations_network_universe_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":true,"schema":{"type":"string","description":"Two-letter state code, e.g. MI","title":"State"},"description":"Two-letter state code, e.g. MI"},{"name":"tier","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated tier filter: rci_direct,salem,oon,prospect","title":"Tier"},"description":"Comma-separated tier filter: rci_direct,salem,oon,prospect"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/ownership-summary":{"get":{"tags":["cbradio-qa","stations"],"summary":"Get ownership group summary","description":"Aggregate stations by ownership group with counts.\n\n    Returns total stations, in-network count, stations with email,\n    and stations with structured mailing address per group.","operationId":"get_ownership_summary_api_v1_stations_ownership_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/stations/ownership-summary/{group_id}/stations":{"get":{"tags":["cbradio-qa","stations"],"summary":"List stations in ownership group","description":"Returns stations belonging to a specific ownership group.","operationId":"get_ownership_group_stations_api_v1_stations_ownership_summary__group_id__stations_get","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/score":{"post":{"tags":["cbradio-qa","stations"],"summary":"Score stations (format, coverage, Nielsen, voter efficiency)","description":"Score a list of stations across 4 dimensions (max 16 points).\n\n    **Dimensions (0-4 each):**\n    - **Format**: audience-specific tier mapping (talk=4 for republican_primary)\n    - **Coverage**: % of station footprint in target states\n    - **Nielsen**: AQH share thresholds (≥5%→4, ≥3%→3, ≥1.5%→2, ≥0.5%→1)\n    - **Voter efficiency**: Nielsen-derived market percentile\n\n    Stations with excluded formats (e.g., urban for republican_primary) are omitted.","operationId":"score_stations_api_v1_stations_score_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ScoreRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio-qa/api/v1/stations/states":{"get":{"tags":["cbradio-qa","stations"],"summary":"List states with station counts","description":"Return every unique US state that has at least one station, along with the\n    number of stations in each state.\n\n    Results are sorted alphabetically by state abbreviation.","operationId":"list_states_api_v1_stations_states_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/stations/tags":{"get":{"tags":["cbradio-qa","stations"],"summary":"List status tags with station counts","description":"Return every unique status tag and the number of stations carrying that tag.\n\n    Tags are extracted from the JSON `status_tags` array on each station.\n    Common tags include `salem`, `iheart`, and `cumulus`.\n\n    Useful for understanding network composition and building filter UIs.","operationId":"list_tags_api_v1_stations_tags_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio-qa/api/v1/stations/unbacked-network":{"get":{"tags":["cbradio-qa","stations"],"summary":"is_in_network asserted with no Airtable Status behind it (cbradio#336)","description":"Stations flagged `is_in_network = true` whose `airtable_status` is empty or NULL — in-network, at 50% (or 30% on a `salem` tag), on **no recorded authority at all**. Airtable is the authority for whether an agreement exists (#318), so an empty Status means nothing is asserting one.\n\n**Why this needs its own endpoint rather than /network-drift.** That report filters `airtable_status IS NOT NULL`, so a station the sync never reached is invisible to it, and a blank Status collapses into a bare `no_signal` integer with no callsigns. Neither is inspectable. This one enumerates.\n\n`group` separates the two causes, which need different remedies:\n\n- `linked` — has an `airtable_id`, so the record exists and its Status was **emptied**. Self-contradictory rows result: the sync defaults `status` to `prospect` on an empty Status and never touches `is_in_network`, so a row reads `status=prospect` and `is_in_network=true` at once. Nothing records who emptied it or when.\n- `unlinked` — `airtable_id` IS NULL, so the empty Status is an **absence**, not a removal. These were never matched to an Airtable record, and a `status` of `signed` on them came from something other than the sync.\n\n**Reports, never rewrites** — the same posture as `/network-drift` and `/tier-drift`. Deliberately NOT a hard reconciliation failure: `is_in_network` is also legitimately set by ops for stations Airtable does not cover, so until it is ruled what an emptied Status *means* (#336 ask 2 — ended agreement, or incomplete record?), a station here is a question and not yet a defect.\n\n`ownership_group_source` is included because it is where a wrong rate comes from: `WBTK-AM` carries a `salem` tag derived by pattern-match from a stale `owner` string, holding it at 30% — and Salem's own roster names a different owner. A tag three inferences deep from stale data is indistinguishable here from a recorded fact, which is the #331 provenance argument.\n\nNot covered, and worth knowing: cbradio syncs the Airtable **Stations** table, not the **Call Signs** table where agreement documents hang. A station can therefore be absent here in cbradio's view while an `Agreement` document exists upstream — 9 of these 15 are exactly that.","operationId":"unbacked_network_api_v1_stations_unbacked_network_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional two-letter state filter","title":"State"},"description":"Optional two-letter state filter"},{"name":"group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: linked | unlinked","title":"Group"},"description":"Filter: linked | unlinked"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/{callsign}":{"get":{"tags":["cbradio-qa","stations"],"summary":"Get station by callsign","description":"Retrieve a single station by its callsign, including embedded SRM\n    (Station Relationship Management) data such as lifecycle state, last\n    contact outcome, and agreement dates.\n\n    If the station belongs to a simulcast group, sibling stations are\n    resolved and included in the response.\n\n    Returns 404 if the callsign does not match any station.","operationId":"get_station_api_v1_stations__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"put":{"tags":["cbradio-qa","stations"],"summary":"Update station status","description":"Update a station's status field. Requires authentication.\n\n    **Valid statuses:**\n    - `signed`: Station has signed agreement with network\n    - `pending`: Station is in negotiation\n    - `inactive`: Station is disabled/not active\n    - `prospect`: Station is a potential lead","operationId":"update_station_status_api_v1_stations__callsign__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__StationStatusUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","stations"],"summary":"Update station fields","description":"Partially update a station's fields. Only provided fields will be updated.\n\n    **Use Cases:**\n    - Airtable sync updates (contact info, status, tags)\n    - Manual corrections to station data\n    - Bulk updates via API\n\n    **Updatable Fields:**\n    - Contact info: primary_contact (name, email, phone)\n    - Location: mailing_address, nielsen_dma, nielsen_msa\n    - Status: status, status_tags, is_in_network\n    - Ownership: owner, commission_rate\n    - Other: website, notes, format","operationId":"patch_station_api_v1_stations__callsign__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__StationUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/{callsign}/contacts":{"get":{"tags":["cbradio-qa","srm"],"summary":"List contact history for a station","description":"Retrieve contact history for a station, ordered by most recent first.","operationId":"list_station_contacts_api_v1_stations__callsign__contacts_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Results per page","default":20,"title":"Limit"},"description":"Results per page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ContactLogList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","srm"],"summary":"Log a new contact","description":"Log a new contact interaction with a station.\n\n    **Side Effects:**\n    1. Updates `station_srm.last_contacted_at`\n    2. Updates `station_srm.last_contact_outcome`\n    3. Updates `station_srm.last_contact_by_user_id`\n    4. Auto-advances lifecycle state based on outcome (see rules below)\n\n    **Auto-Advance Rules:**\n    | Current State | Outcome | New State |\n    |---------------|---------|-----------|\n    | prospect | Any positive/neutral | contacted |\n    | contacted | meeting_scheduled, agreement_sent | negotiating |\n    | negotiating | agreement_signed | active |\n    | at_risk | Any positive | active |\n    | churned | interested, meeting_scheduled | contacted |\n\n    **Positive Outcomes:** interested, meeting_scheduled, agreement_sent, agreement_signed","operationId":"create_station_contact_api_v1_stations__callsign__contacts_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ContactLogCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ContactLog"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/{callsign}/documents":{"get":{"tags":["cbradio-qa","stations"],"summary":"Get documents for a station","description":"Return all documents associated with the specified station, with optional\n    filtering by document type and year.\n\n    Document metadata includes filename, type, format, rate card type, year,\n    extraction status, and extracted rate count.\n\n    Results are ordered by year descending, then by filename.","operationId":"get_station_documents_api_v1_stations__callsign__documents_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/{callsign}/rate-history":{"get":{"tags":["cbradio-qa","stations"],"summary":"Get rate change history for a station","description":"Return a chronological history of rate changes for the specified station,\n    including rate card approvals and corrections (pending, verified, rejected).\n\n    Optionally filter by daypart name to narrow the history to a specific\n    time slot.\n\n    Useful for auditing rate changes over time and tracking review activity.","operationId":"get_station_rate_history_api_v1_stations__callsign__rate_history_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"daypart","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by daypart name","title":"Daypart"},"description":"Filter by daypart name"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/{callsign}/rates":{"post":{"tags":["cbradio-qa","stations"],"summary":"Write rates for a station","description":"Write or update rates for a station. Used by the proposal builder's\n    \"Update System Rate\" feature and QA correction flows.\n\n    Rates are upserted by (callsign, daypart_id, spot_length, ad_type, year) —\n    posting the same combination again updates the existing rate.","operationId":"write_station_rates_api_v1_stations__callsign__rates_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__ManualRateWriteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"get":{"tags":["cbradio-qa","stations"],"summary":"Get rates for a station","description":"Return all advertising rates for the specified station, with optional\n    filtering by ad type, spot duration, and rate card year.\n\n    Each rate includes provenance information from the parent rate card:\n    source, review status, and last verification timestamp.\n\n    `rate_card_lifecycle` reads `rate_card.lifecycle` directly — the placement\n    gate axis (cbradio#205, cbradio#239). `review_status` is a separate axis\n    (parser-accuracy / QA review) and is not collapsed onto lifecycle.\n\n    | rate_card.lifecycle  | rate_card_lifecycle | meaning                                      |\n    |---|---|---|\n    | `planning`           | `planning`          | needs ops `/approve-planning` before placement |\n    | `approved_planning`  | `approved_planning` | ops-approved planning rate, cleared for placement |\n    | `final`              | `final`             | full placement-cleared rate                    |\n    | `rejected` review    | (rate omitted)      | parser QA failed — rate suppressed             |\n\n    `is_stale` (cbradio#240) signals that the parent rate_card hasn't been\n    verified within 365 days. Hard-blocks order_packet generation. Re-verify\n    via `POST /api/v1/rate-cards/{id}/verify`. NULL `last_verified_at` falls\n    back to the card's `created_at` for the staleness calculation.\n\n    Results are ordered by effective date descending, then by duration and\n    ad type.\n\n    **cbradio#241**: pass `?proposal_class=candidate_in_window|candidate_out_of_window|issue`\n    to filter rates by the proposal/buy's classification. Combines an\n    `ad_type` filter (candidate/all or issue/all) with a `rate_context`\n    filter (in_window/out_of_window/all/standard) so political-window-aware\n    consumers get just the rows that apply. When set, the standalone\n    `?ad_type=` param is ignored.","operationId":"get_station_rates_api_v1_stations__callsign__rates_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ad Type"}},{"name":"proposal_class","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(candidate_in_window|candidate_out_of_window|issue)$"},{"type":"null"}],"description":"Proposal classification (cbradio#241). One of: `candidate_in_window`, `candidate_out_of_window`, `issue`. Overrides `ad_type` when set.","title":"Proposal Class"},"description":"Proposal classification (cbradio#241). One of: `candidate_in_window`, `candidate_out_of_window`, `issue`. Overrides `ad_type` when set."},{"name":"duration","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/{callsign}/srm":{"get":{"tags":["cbradio-qa","srm"],"summary":"Get SRM data for a station","description":"Retrieve SRM (Station Relationship Management) data for a station.\n\n    Returns lifecycle state, contact history, agreement dates, and ownership tracking.\n    Creates an SRM record automatically if one doesn't exist.","operationId":"get_station_srm_api_v1_stations__callsign__srm_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__StationSRM"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio-qa","srm"],"summary":"Update SRM data for a station","description":"Update SRM data for a station.\n\n    **Lifecycle State Transitions:**\n    State transitions are validated. Invalid transitions will return a 400 error.\n\n    Valid transitions:\n    - prospect → contacted\n    - contacted → negotiating, inactive\n    - negotiating → active, churned, inactive\n    - active → at_risk, churned\n    - at_risk → active, churned\n    - churned → contacted, inactive\n    - inactive → prospect, contacted\n\n    **Side Effects:**\n    - Lifecycle state changes update `lifecycle_state_changed_at`","operationId":"update_station_srm_api_v1_stations__callsign__srm_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__StationSRMUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__StationSRM"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/{callsign}/voters":{"get":{"tags":["cbradio-qa","voters"],"summary":"Get voter data for a station's coverage area","description":"Returns pre-aggregated voter universe data for a station by combining\nits broadcast coverage contour with i360 voter propensity data.\n\n**How it works:**\n1. Looks up the station's county coverage from the coverage cache\n2. Joins with county-level voter data from i360\n3. Aggregates voter counts weighted by coverage percentage\n4. Applies 15% radio listening penetration rate for listener estimates\n\n**Coverage weighting:** If a station covers 60% of a county with 10,000 swing voters,\nthe weighted contribution is 6,000 swing voters.\n\n**Estimated listeners:** Total voters * 0.15 (industry-standard radio penetration rate).\n\n**Data freshness:** Coverage cache and voter data are refreshed monthly.\nCheck `fetched_at` timestamps for currency.\n\n**Use this endpoint when:**\n- Evaluating a station for a media buy\n- Computing Cost Per Ears (CPE) metrics\n- Comparing voter reach across stations in a market","operationId":"get_station_voters_api_v1_stations__callsign__voters_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage percentage to include a county (filters tiny slivers).","default":0.5,"title":"Min Coverage"},"description":"Minimum coverage percentage to include a county (filters tiny slivers)."}],"responses":{"200":{"description":"Voter data returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__StationVoterResponse"}}}},"404":{"description":"Station not found or no coverage data","content":{"application/json":{"example":{"detail":"No county coverage data for WXYZ-FM"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/stations/{facility_id}/district-coverage":{"get":{"tags":["cbradio-qa","coverage"],"summary":"Get district coverage for a station","description":"Returns all districts that a station's coverage contour intersects with.\n\n**District Types:**\n- `congressional` - US Congressional districts (all states)\n- `state_senate` - State senate districts (MI, NC, AZ, GA, WI, PA, NV)\n- `state_house` - State house districts (MI, NC, AZ, GA, WI, PA, NV)\n- `county` - US counties (all states)\n\n**Filters:**\n- `district_types` - Comma-separated list to filter (default: all)\n- `min_coverage` - Minimum coverage percentage to include (0-100)","operationId":"get_station_district_coverage_api_v1_stations__facility_id__district_coverage_get","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","title":"Facility Id"}},{"name":"district_types","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated: congressional,state_senate,state_house,county","default":"all","title":"District Types"},"description":"Comma-separated: congressional,state_senate,state_house,county"},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage percentage","default":0,"title":"Min Coverage"},"description":"Minimum coverage percentage"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"AM only: day, night, or service. Defaults to day for AM stations.","title":"Contour Type"},"description":"AM only: day, night, or service. Defaults to day for AM stations."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/tasks":{"get":{"tags":["cbradio-qa","srm"],"summary":"List SRM tasks","description":"List SRM tasks with filtering and pagination.\n\n    **Filters:**\n    - `status`: Filter by task status (pending, in_progress, completed, cancelled)\n    - `task_type`: Filter by task type\n    - `owner_user_id`: Filter by assigned user (use 'me' for current user)\n    - `station_callsign`: Filter by specific station\n    - `state`: Filter by station state (e.g., TX, CA)\n    - `due_before`: Show tasks due before this date\n    - `due_after`: Show tasks due after this date\n    - `include_completed`: Include completed/cancelled tasks (default: false)\n\n    **Sorting:**\n    - Default: priority ASC, due_date ASC\n    - Returns tasks ordered by urgency\n\n    **Response includes:**\n    - Task list with optional embedded station data\n    - Total count for pagination\n    - Summary stats (due_today, overdue)","operationId":"list_tasks_api_v1_tasks_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__TaskStatus"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"task_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__TaskType"},{"type":"null"}],"description":"Filter by task type","title":"Task Type"},"description":"Filter by task type"},{"name":"owner_user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by owner (use 'me' for self)","title":"Owner User Id"},"description":"Filter by owner (use 'me' for self)"},{"name":"station_callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station","title":"Station Callsign"},"description":"Filter by station"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station state","title":"State"},"description":"Filter by station state"},{"name":"due_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Tasks due before date","title":"Due Before"},"description":"Tasks due before date"},{"name":"due_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Tasks due after date","title":"Due After"},"description":"Tasks due after date"},{"name":"include_completed","in":"query","required":false,"schema":{"type":"boolean","description":"Include completed tasks","default":false,"title":"Include Completed"},"description":"Include completed tasks"},{"name":"include_station","in":"query","required":false,"schema":{"type":"boolean","description":"Include station details","default":true,"title":"Include Station"},"description":"Include station details"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Results per page","default":50,"title":"Limit"},"description":"Results per page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__TaskList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"post":{"tags":["cbradio-qa","srm"],"summary":"Create a manual task","description":"Create a task manually. Most tasks are auto-generated by the system,\n    but this endpoint allows creating custom tasks.\n\n    **Required fields:**\n    - station_callsign: The station this task is for\n    - task_type: Type of task\n    - title: Task title\n    - due_date: When the task is due\n\n    **Optional fields:**\n    - reason: Why this task was created\n    - priority: 0 (highest) to 3 (lowest), default 1","operationId":"create_task_api_v1_tasks_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"station_callsign","in":"query","required":true,"schema":{"type":"string","title":"Station Callsign"}},{"name":"task_type","in":"query","required":true,"schema":{"$ref":"#/components/schemas/cbradio-qa__TaskType"}},{"name":"title","in":"query","required":true,"schema":{"type":"string","title":"Title"}},{"name":"due_date","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Due Date"}},{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},{"name":"priority","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Priority"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/tasks/{task_id}":{"get":{"tags":["cbradio-qa","srm"],"summary":"Get task details","description":"Get detailed information about a specific task.","operationId":"get_task_api_v1_tasks__task_id__get","parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/tasks/{task_id}/cancel":{"post":{"tags":["cbradio-qa","srm"],"summary":"Cancel a task","description":"Cancel a task that is no longer needed.\n\n    **Requirements:**\n    - Task must be 'pending' or 'in_progress'\n    - Requires authentication\n\n    **Side Effects:**\n    - Sets task `status` to 'cancelled'","operationId":"cancel_task_api_v1_tasks__task_id__cancel_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/tasks/{task_id}/claim":{"post":{"tags":["cbradio-qa","srm"],"summary":"Claim a task","description":"Claim a task for yourself. Only pending tasks can be claimed.\n\n    **Side Effects:**\n    - Sets `owner_user_id` to current user\n    - Sets `status` to 'in_progress'\n    - Updates `updated_at`","operationId":"claim_task_api_v1_tasks__task_id__claim_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/tasks/{task_id}/complete":{"post":{"tags":["cbradio-qa","srm"],"summary":"Complete a task","description":"Complete a task with an outcome. This also logs a contact for the station.\n\n    **Requirements:**\n    - Task must be in 'in_progress' status\n    - You must be the current owner (or admin)\n\n    **Side Effects:**\n    1. Sets task `status` to 'completed'\n    2. Sets `completed_at` and `completed_by_user_id`\n    3. Creates a contact log entry for the station\n    4. Updates station SRM data (last_contacted_at, etc.)\n    5. May auto-advance lifecycle state based on outcome","operationId":"complete_task_api_v1_tasks__task_id__complete_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__TaskCompleteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/tasks/{task_id}/unclaim":{"post":{"tags":["cbradio-qa","srm"],"summary":"Release a claimed task","description":"Release a task you have claimed. Returns it to the pending queue.\n\n    **Requirements:**\n    - Task must be in 'in_progress' status\n    - You must be the current owner (or admin)\n\n    **Side Effects:**\n    - Clears `owner_user_id` and `owner_name`\n    - Sets `status` back to 'pending'","operationId":"unclaim_task_api_v1_tasks__task_id__unclaim_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/traffic-assets/{asset_id}":{"patch":{"tags":["cbradio-qa","traffic-assets"],"summary":"Update traffic asset","description":"Update asset metadata (label, rotation order/weight).","operationId":"update_traffic_asset_api_v1_traffic_assets__asset_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","title":"Asset Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__TrafficAssetUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__TrafficAssetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio-qa","traffic-assets"],"summary":"Delete traffic asset","description":"Remove a traffic asset.","operationId":"delete_traffic_asset_api_v1_traffic_assets__asset_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","title":"Asset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/year-proposals":{"get":{"tags":["cbradio-qa","year-proposals"],"summary":"Documents with no year, and a proposed one where the text supports it","description":"Year-less documents in the re-extraction cohort, each with a candidate year derived from its OCR text and the evidence behind it. Proposals are ~80% precise, which is why nothing here is written until a human confirms it — see the module docstring for why a wrong year is worse than a missing one.","operationId":"list_proposals_api_v1_year_proposals_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"only_proposed","in":"query","required":false,"schema":{"type":"boolean","description":"Hide documents where the text supports no candidate","default":true,"title":"Only Proposed"},"description":"Hide documents where the text supports no candidate"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Proposals Api V1 Year Proposals Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/api/v1/year-proposals/{document_id}":{"post":{"tags":["cbradio-qa","year-proposals"],"summary":"Accept a year for one document (or reject the proposal)","description":"Writes `document.year`. `year: null` records a rejection by leaving the row untouched, so a reviewer can move past a bad proposal without the queue re-offering it as unreviewed work.","operationId":"decide_api_v1_year_proposals__document_id__post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__Body_decide_api_v1_year_proposals__document_id__post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Decide Api V1 Year Proposals  Document Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HTTPValidationError"}}}}}}},"/cbradio-qa/health":{"get":{"tags":["cbradio-qa","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HealthResponse"}}}}}}},"/cbradio-qa/health/detailed":{"get":{"tags":["cbradio-qa","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__HealthResponse"}}}}}}},"/cbradio-qa/prime":{"get":{"tags":["cbradio-qa","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__PrimeResponse"}}}}}}},"/cbradio-qa/status":{"get":{"tags":["cbradio-qa","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio-qa__StatusResponse"}}}}}}},"/cbradio/api":{"get":{"summary":"Api Root","description":"Root API endpoint with version and status information.","operationId":"api_root_api_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["cbradio"]}},"/cbradio/api/auth/me":{"get":{"tags":["cbradio","authentication"],"summary":"Get current user","description":"Return the profile of the currently authenticated user.\n\n    Requires a valid Bearer token. Returns user ID, username, and role.","operationId":"read_users_me_api_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__User"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/auth/token":{"post":{"tags":["cbradio","authentication"],"summary":"Login for access token","description":"Authenticate with username and password to obtain a JWT access token.\n\n    The token is valid for the configured expiration period and must be\n    included as a Bearer token in subsequent authenticated requests.","operationId":"login_for_access_token_api_auth_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbradio__Body_login_for_access_token_api_auth_token_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Token"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/health":{"get":{"summary":"Api Health Compat","description":"Legacy health endpoint — redirects to CBApp /health.","operationId":"api_health_compat_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["cbradio"]}},"/cbradio/api/protected":{"get":{"summary":"Protected Route","description":"Example of a protected route.","operationId":"protected_route_api_protected_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}],"tags":["cbradio"]}},"/cbradio/api/v1/admin/reextract-gaps":{"post":{"tags":["cbradio","admin"],"summary":"Reprocess gap documents through cbingest","description":"Finds documents with stored OCR text but zero extracted rates, and sends them through cbingest for re-extraction.\n\n**Bounded by `limit`.** Every document is a synchronous LLM round trip, and the live gap pool is 3,579 — an unbounded run is a request that cannot finish, which is why this endpoint had never been run. `remaining` in the response says how many are left.\n\nDocuments are taken in order of what recovering them is worth: current cycle first, then rate cards, then in-network stations. A 2020 agreement is not worth an LLM call while a 2026 rate card for an in-network station is waiting.","operationId":"reextract_gaps_api_v1_admin_reextract_gaps_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"How many documents to re-extract in this call.","default":25,"title":"Limit"},"description":"How many documents to re-extract in this call."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ReextractGapsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/admin/sync-airtable-rates":{"post":{"tags":["cbradio","admin"],"summary":"Trigger 2026 rate card sync pipeline","description":"Spawns the unified sync pipeline as a background subprocess.\n\nThe pipeline:\n1. Fetches 2026 rate card attachments from Airtable\n2. Downloads files to disk\n3. Creates document records\n4. Runs OCR + rate extraction\n5. Backfills rate_card records\n\nReturns 409 if a sync is already running.","operationId":"trigger_sync_api_v1_admin_sync_airtable_rates_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__SyncTriggerRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SyncTriggerResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/admin/sync-airtable-rates/logs/{job_id}":{"get":{"tags":["cbradio","admin"],"summary":"Get sync pipeline log","description":"Returns the full log file content for a given job.","operationId":"get_sync_log_api_v1_admin_sync_airtable_rates_logs__job_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/admin/sync-airtable-rates/status":{"get":{"tags":["cbradio","admin"],"summary":"Get sync pipeline status","description":"Returns the current or last run status and summary counts.","operationId":"get_sync_status_api_v1_admin_sync_airtable_rates_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SyncStatusResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/agreements":{"get":{"tags":["cbradio","agreements"],"summary":"List agreements","description":"Retrieve a paginated list of agreements with optional filters.","operationId":"list_agreements_api_v1_agreements_get","parameters":[{"name":"station_callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station","title":"Station Callsign"},"description":"Filter by station"},{"name":"lifecycle_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state (comma-separated for multiple)","title":"Lifecycle State"},"description":"Filter by state (comma-separated for multiple)"},{"name":"agreement_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by type","title":"Agreement Type"},"description":"Filter by type"},{"name":"created_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by creator","title":"Created By"},"description":"Filter by creator"},{"name":"expiring_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Find agreements expiring before","title":"Expiring Before"},"description":"Find agreements expiring before"},{"name":"expiring_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Find agreements expiring after","title":"Expiring After"},"description":"Find agreements expiring after"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"Sort field","default":"created_at","title":"Sort"},"description":"Sort field"},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AgreementListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","agreements"],"summary":"Create agreement","description":"Create a new agreement (starts in draft state).","operationId":"create_agreement_api_v1_agreements_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AgreementCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AgreementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/agreements/expiring":{"get":{"tags":["cbradio","agreements"],"summary":"Get expiring agreements","description":"Get agreements expiring within a time window (for renewal alerts).","operationId":"get_expiring_agreements_api_v1_agreements_expiring_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Days until expiration","default":30,"title":"Days"},"description":"Days until expiration"},{"name":"lifecycle_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"Lifecycle State"},"description":"Filter by state"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/agreements/{agreement_id}":{"get":{"tags":["cbradio","agreements"],"summary":"Get agreement detail","description":"Get full agreement details including documents and audit trail.","operationId":"get_agreement_api_v1_agreements__agreement_id__get","parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AgreementDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","agreements"],"summary":"Update agreement","description":"Update agreement fields (not lifecycle state).","operationId":"update_agreement_api_v1_agreements__agreement_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AgreementUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AgreementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/agreements/{agreement_id}/documents":{"post":{"tags":["cbradio","agreements"],"summary":"Upload agreement document","description":"Upload a document to an agreement.","operationId":"upload_document_api_v1_agreements__agreement_id__documents_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}},{"name":"document_type","in":"query","required":true,"schema":{"type":"string","description":"Type of document","title":"Document Type"},"description":"Type of document"},{"name":"file_name","in":"query","required":true,"schema":{"type":"string","description":"Original filename","title":"File Name"},"description":"Original filename"},{"name":"file_url","in":"query","required":true,"schema":{"type":"string","description":"CBFiles URL after upload","title":"File Url"},"description":"CBFiles URL after upload"},{"name":"file_size","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"File size in bytes","title":"File Size"},"description":"File size in bytes"},{"name":"mime_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"MIME type","title":"Mime Type"},"description":"MIME type"},{"name":"description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional description","title":"Description"},"description":"Optional description"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__DocumentUploadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/agreements/{agreement_id}/documents/{document_id}":{"delete":{"tags":["cbradio","agreements"],"summary":"Delete agreement document","description":"Soft delete a document from an agreement.","operationId":"delete_document_api_v1_agreements__agreement_id__documents__document_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}},{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/agreements/{agreement_id}/transition":{"post":{"tags":["cbradio","agreements"],"summary":"Transition agreement state","description":"Move agreement through lifecycle states.","operationId":"transition_agreement_api_v1_agreements__agreement_id__transition_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agreement_id","in":"path","required":true,"schema":{"type":"string","title":"Agreement Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AgreementTransition"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AgreementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/buyer-agencies":{"get":{"tags":["cbradio","buyer-agencies"],"summary":"List buyer agencies","description":"List media buying agencies with optional filters.\n\n    **Filters:**\n    - `status`: Filter by agency status (active, inactive)\n    - `search`: Search by name (partial match)\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 500.","operationId":"list_buyer_agencies_api_v1_buyer_agencies_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AgencyStatus"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name (partial match)","title":"Search"},"description":"Search by name (partial match)"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Political cycle filter (e.g. '2025-2026'). When provided, aggregate fields (total_buys, total_volume, client_count, last_buy_date) scope to buys in that cycle only. Absent = all-time totals.","title":"Cycle"},"description":"Political cycle filter (e.g. '2025-2026'). When provided, aggregate fields (total_buys, total_volume, client_count, last_buy_date) scope to buys in that cycle only. Absent = all-time totals."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__BuyerAgencySummary"},"title":"Response List Buyer Agencies Api V1 Buyer Agencies Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","buyer-agencies"],"summary":"Create buyer agency","description":"Create a new buyer agency. Requires authentication.","operationId":"create_buyer_agency_api_v1_buyer_agencies_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BuyerAgencyCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BuyerAgency"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/buyer-agencies/count":{"get":{"tags":["cbradio","buyer-agencies"],"summary":"Count buyer agencies","description":"Return the total number of buyer agencies matching the given filters.\n\n    Accepts the same filter parameters as the list endpoint (status, search)\n    but returns only a count instead of full records.","operationId":"count_buyer_agencies_api_v1_buyer_agencies_count_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AgencyStatus"},{"type":"null"}],"title":"Status"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Count Buyer Agencies Api V1 Buyer Agencies Count Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/buyer-agencies/{agency_id}":{"get":{"tags":["cbradio","buyer-agencies"],"summary":"Get buyer agency details","description":"Get a buyer agency by ID with associated radio buys.","operationId":"get_buyer_agency_api_v1_buyer_agencies__agency_id__get","parameters":[{"name":"agency_id","in":"path","required":true,"schema":{"type":"string","title":"Agency Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BuyerAgencyWithRadioBuys"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","buyer-agencies"],"summary":"Update buyer agency","description":"Partially update a buyer agency. Requires authentication.","operationId":"update_buyer_agency_api_v1_buyer_agencies__agency_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agency_id","in":"path","required":true,"schema":{"type":"string","title":"Agency Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BuyerAgencyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BuyerAgency"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","buyer-agencies"],"summary":"Delete buyer agency","description":"Delete a buyer agency. Requires authentication. Fails if agency has associated radio buys.","operationId":"delete_buyer_agency_api_v1_buyer_agencies__agency_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"agency_id","in":"path","required":true,"schema":{"type":"string","title":"Agency Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/clients":{"get":{"tags":["cbradio","clients"],"summary":"List clients","description":"List political clients (candidates, PACs, campaigns) with optional filters.\n\n    **Filters:**\n    - `state`: Filter by state abbreviation (e.g., TX, CA)\n    - `search`: Search by name (partial match)\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 500.","operationId":"list_clients_api_v1_clients_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state abbreviation","title":"State"},"description":"Filter by state abbreviation"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name (partial match)","title":"Search"},"description":"Search by name (partial match)"},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientType"},{"type":"null"}],"description":"Filter by client type","title":"Type"},"description":"Filter by client type"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientStatus"},{"type":"null"}],"description":"Filter by client status","title":"Status"},"description":"Filter by client status"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Political cycle filter (e.g. '2025-2026'). When provided, aggregate fields (total_buys, total_spend, last_buy_date) scope to buys in that cycle only. Absent = all-time totals.","title":"Cycle"},"description":"Political cycle filter (e.g. '2025-2026'). When provided, aggregate fields (total_buys, total_spend, last_buy_date) scope to buys in that cycle only. Absent = all-time totals."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__ClientSummary"},"title":"Response List Clients Api V1 Clients Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","clients"],"summary":"Create client","description":"Create a new political client. Requires authentication.","operationId":"create_client_api_v1_clients_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ClientCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Client"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/clients/count":{"get":{"tags":["cbradio","clients"],"summary":"Count clients","description":"Return the total number of clients matching the given filters.\n\n    Accepts the same filter parameters as the list endpoint (state, search,\n    type, status) but returns only a count instead of full records.","operationId":"count_clients_api_v1_clients_count_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientType"},{"type":"null"}],"title":"Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientStatus"},{"type":"null"}],"title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Count Clients Api V1 Clients Count Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/clients/{client_id}":{"get":{"tags":["cbradio","clients"],"summary":"Get client details","description":"Get a client by ID with associated radio buys.","operationId":"get_client_api_v1_clients__client_id__get","parameters":[{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ClientWithRadioBuys"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","clients"],"summary":"Update client","description":"Partially update a client. Requires authentication.","operationId":"update_client_api_v1_clients__client_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ClientUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Client"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","clients"],"summary":"Delete client","description":"Delete a client. Requires authentication. Fails if client has associated radio buys.","operationId":"delete_client_api_v1_clients__client_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/competitive/detected-airings":{"get":{"tags":["cbradio","competitive"],"summary":"Verified radio airings detected by cbtuner (read-only passthrough)","description":"Recurring radio spots cbtuner has captured + transcribed across its monitored\n    stations, proxied from cbintel over the mesh for the RCI competitive panel\n    (cbradio#293). cbradio-ui can't reach cbintel directly; this is the bridge.\n\n    Returns **recurring-spot clusters** (cbtuner's `/discover`): each is one spot\n    detected looping across stations, with `n_times` (temporal recurrence),\n    `n_stations` (reach), the labelled `kind` (ad|program|unknown), the `stations`\n    it aired on, and the distinct `egresses` used to capture them.\n\n    **Tiering:** these are `source=cbtuner, confidence=verified` — a context strip\n    only. Never blend with modeled (AdImpact) or actual (OPIF) spend.\n\n    **Filters:** `callsigns` narrows to clusters that aired on the requested\n    stations; `state` is informational (cbtuner's monitored corpus is the MI set).\n    `kind=all` returns ad + program + unknown.\n\n    **Degrades gracefully:** if cbtuner/cbintel is unavailable, returns\n    `available=false` with an empty `airings` list (never errors the panel).","operationId":"detected_airings_api_v1_competitive_detected_airings_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Informational geography tag (cbtuner corpus is MI).","title":"State"},"description":"Informational geography tag (cbtuner corpus is MI)."},{"name":"callsigns","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated callsigns to narrow to (e.g. WJR-AM,WWJ-AM).","title":"Callsigns"},"description":"Comma-separated callsigns to narrow to (e.g. WJR-AM,WWJ-AM)."},{"name":"kind","in":"query","required":false,"schema":{"type":"string","description":"ad | program | unknown | all","default":"ad","title":"Kind"},"description":"ad | program | unknown | all"},{"name":"min_stations","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Min distinct stations a spot must air on.","default":1,"title":"Min Stations"},"description":"Min distinct stations a spot must air on."},{"name":"window","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":100,"description":"Most-recent transcript rows to cluster.","default":1500,"title":"Window"},"description":"Most-recent transcript rows to cluster."}],"responses":{"200":{"description":"Detected-airing clusters with source/confidence tiering","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/competitive/opif-ingest":{"post":{"tags":["cbradio","competitive"],"summary":"Trigger an OPIF political-file ingest run (HMAC-authed, in-process)","description":"Runs the FCC OPIF crawl **inside the cbradio process** (cbradio#294) — it\n    reads its own station table and writes `opif_filing` via the service's own\n    data layer (no external process touches the DB), and reaches cbintel +\n    cbingest over the mesh. Designed to be fired by **cbcron** on a schedule.\n\n    Auth: HMAC-SHA256 over the raw body with `OPIF_INGEST_HMAC_SECRET`, hex in\n    `X-Cbradio-Signature` (`sha256=` prefix accepted) — same pattern as\n    `/internal/sync_gdrive`. cbcron signs its fixed payload.\n\n    `dry_run=true` runs synchronously and returns a parsed sample (no write).\n    Otherwise the crawl runs as a background task (returns immediately; results\n    are logged) — incremental, since already-ingested orders are dedup-skipped.","operationId":"opif_ingest_api_v1_competitive_opif_ingest_post","parameters":[{"name":"x-cbradio-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cbradio-Signature"}}],"responses":{"200":{"description":"Run summary (sync sample for dry_run; started ack otherwise)","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/competitive/opif-spend":{"get":{"tags":["cbradio","competitive"],"summary":"FCC OPIF political-file spend (radio ground-truth, source=opif)","description":"Structured political-ad spend parsed from FCC OPIF political files\n    (cbradio#294), keyed on `fcc_facility_id`. These are **actual** filings —\n    `source=opif, confidence=actual` — and are the radio ground-truth backbone\n    for the RCI competitive panel.\n\n    Returns `competitive_spend`-shaped rows the panel consumes unchanged:\n    advertiser (name/party/type), office, market_or_station, flight window,\n    `amount_est` (gross), `occurrences` (spots), `raw_ref` (link to the filing),\n    plus `needs_review` for low-confidence extractions.\n\n    **Tiering:** never blend with modeled (AdImpact) or verified (cbtuner) — each\n    is segmented by source/confidence.\n\n    **Filters:** `state`, `callsigns`, `campaign_year`, `since` (ISO flight-start).\n    Degrades gracefully to `available=false` with empty `rows` if the store is\n    empty / the ingestion hasn't run.","operationId":"opif_spend_api_v1_competitive_opif_spend_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state filter.","title":"State"},"description":"Two-letter state filter."},{"name":"callsigns","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated callsigns to narrow to.","title":"Callsigns"},"description":"Comma-separated callsigns to narrow to."},{"name":"campaign_year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filing/campaign year.","title":"Campaign Year"},"description":"Filing/campaign year."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only flights on/after this ISO date (YYYY-MM-DD).","title":"Since"},"description":"Only flights on/after this ISO date (YYYY-MM-DD)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}}],"responses":{"200":{"description":"OPIF competitive_spend rows (source=opif/confidence=actual)","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/consultants":{"get":{"tags":["cbradio","consultants"],"summary":"List consultants","description":"List sales consultants with optional filters.\n\n    **Filters:**\n    - `status`: Filter by consultant status (active, inactive)\n    - `search`: Search by name (partial match)\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 500.","operationId":"list_consultants_api_v1_consultants_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ConsultantStatus"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name (partial match)","title":"Search"},"description":"Search by name (partial match)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__ConsultantSummary"},"title":"Response List Consultants Api V1 Consultants Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","consultants"],"summary":"Create consultant","description":"Create a new consultant. Requires authentication.","operationId":"create_consultant_api_v1_consultants_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ConsultantCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Consultant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/consultants/count":{"get":{"tags":["cbradio","consultants"],"summary":"Count consultants","description":"Return the total number of consultants matching the given filters.\n\n    Accepts the same filter parameters as the list endpoint (status, search)\n    but returns only a count instead of full records.","operationId":"count_consultants_api_v1_consultants_count_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ConsultantStatus"},{"type":"null"}],"title":"Status"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Count Consultants Api V1 Consultants Count Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/consultants/{consultant_id}":{"get":{"tags":["cbradio","consultants"],"summary":"Get consultant details","description":"Get a consultant by ID with associated clients and radio buys.","operationId":"get_consultant_api_v1_consultants__consultant_id__get","parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ConsultantWithRelations"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","consultants"],"summary":"Update consultant","description":"Partially update a consultant. Requires authentication.","operationId":"update_consultant_api_v1_consultants__consultant_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ConsultantUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Consultant"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","consultants"],"summary":"Delete consultant","description":"Delete a consultant. Requires authentication.","operationId":"delete_consultant_api_v1_consultants__consultant_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/consultants/{consultant_id}/clients/{client_id}":{"post":{"tags":["cbradio","consultants"],"summary":"Associate consultant with client","description":"Create a consultant-client association. Requires authentication.","operationId":"add_client_to_consultant_api_v1_consultants__consultant_id__clients__client_id__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}},{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Client To Consultant Api V1 Consultants  Consultant Id  Clients  Client Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","consultants"],"summary":"Remove client from consultant","description":"Remove a consultant-client association. Requires authentication.","operationId":"remove_client_from_consultant_api_v1_consultants__consultant_id__clients__client_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}},{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/consultants/{consultant_id}/performance":{"get":{"tags":["cbradio","consultants"],"summary":"Get consultant commission performance metrics","description":"Return aggregated commission performance metrics for a consultant across\n    all radio buys.\n\n    Merges data from commission rules and the radio_buy_consultant junction\n    table, preferring commission_rule data when both exist. Returns totals\n    for gross revenue, commissions earned, profit, average commission rate,\n    and per-buy breakdowns including hold status.","operationId":"get_consultant_performance_api_v1_consultants__consultant_id__performance_get","parameters":[{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/coverage-gaps":{"get":{"tags":["cbradio","coverage"],"summary":"Identify coverage gaps","description":"Identifies districts without adequate network coverage.\n\n**Filters:**\n- `state_code` - Filter to a specific state\n- `district_type` - Filter to specific district type\n- `threshold` - Coverage threshold percentage (default 50)\n\n**Returns:**\nDistricts where no in-network station meets the coverage threshold.","operationId":"get_coverage_gaps_api_v1_coverage_gaps_get","parameters":[{"name":"state_code","in":"query","required":false,"schema":{"type":"string","description":"Filter to state (two-letter code)","title":"State Code"},"description":"Filter to state (two-letter code)"},{"name":"district_type","in":"query","required":false,"schema":{"type":"string","description":"District type to analyze","default":"congressional","title":"District Type"},"description":"District type to analyze"},{"name":"threshold","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Coverage threshold %","default":50,"title":"Threshold"},"description":"Coverage threshold %"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/coverage/listenable/districts/{geoid}/stations":{"get":{"tags":["cbradio","coverage"],"summary":"Listenable stations covering a district (Geo%)","description":"Stations that **listenably** cover a district, valued in DataSoup Geo% and\nordered Geo% desc. This is the first-party per-district coverage surface\n(cbradio#317). Coverage is read from listenable Geo% ONLY — FCC contour\nenclosure and Nielsen DMA membership are never used (they are banned proxies,\ncbradio#319). A station covers only when its listenable Geo% exceeds\n`min_geo_pct`; measured non-covering (geo_pct=0) stations are excluded.\n\n**Path Parameters:**\n- `geoid` - District id, e.g. `MI-HD-042` (case-insensitive)\n\n**Query Parameters:**\n- `min_geo_pct` - Minimum listenable Geo% to count as covering (default 0 = any positive)\n- `in_network_only` - Only in-network / Salem stations","operationId":"get_listenable_district_stations_api_v1_coverage_listenable_districts__geoid__stations_get","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","title":"Geoid"}},{"name":"min_geo_pct","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum listenable Geo% to count as covering","default":0.0,"title":"Min Geo Pct"},"description":"Minimum listenable Geo% to count as covering"},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only in-network stations","default":false,"title":"In Network Only"},"description":"Only in-network stations"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/coverage/listenable/stations/{callsign}/districts":{"get":{"tags":["cbradio","coverage"],"summary":"Listenable districts covered by a station (Geo%)","description":"Districts a station **listenably** covers, valued in DataSoup Geo% and ordered\nGeo% desc — the station→district direction of the first-party coverage surface\n(cbradio#317). Same trust rule: listenable Geo% only, never contour enclosure or\nDMA membership.\n\n**Path Parameters:**\n- `callsign` - Station callsign, e.g. `WDTK` (case-insensitive)\n\n**Query Parameters:**\n- `min_geo_pct` - Minimum listenable Geo% to count as covering (default 0 = any positive)\n- `district_type` - Optional filter: congressional, state_senate, state_house, county","operationId":"get_listenable_station_districts_api_v1_coverage_listenable_stations__callsign__districts_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"min_geo_pct","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum listenable Geo% to count as covering","default":0.0,"title":"Min Geo Pct"},"description":"Minimum listenable Geo% to count as covering"},{"name":"district_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional district-type filter","title":"District Type"},"description":"Optional district-type filter"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/cycles":{"get":{"tags":["cbradio","cycles"],"summary":"List political cycles","description":"Return all political cycles ordered by start year descending, then name.","operationId":"list_cycles_api_v1_cycles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["cbradio","cycles"],"summary":"Create a political cycle","description":"Create a new political cycle (e.g., \"2026 Midterms\"). A cycle groups\n    radio buys by election period for reporting and CSV export.\n\n    Requires authentication.","operationId":"create_cycle_api_v1_cycles_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CycleCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/cycles/{cycle_id}":{"get":{"tags":["cbradio","cycles"],"summary":"Get a political cycle","description":"Return a single political cycle by ID. Returns 404 if not found.","operationId":"get_cycle_api_v1_cycles__cycle_id__get","parameters":[{"name":"cycle_id","in":"path","required":true,"schema":{"type":"string","title":"Cycle Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"put":{"tags":["cbradio","cycles"],"summary":"Update a political cycle","description":"Partially update a political cycle. Only provided fields are changed.\n\n    Requires authentication. Returns 404 if the cycle does not exist, or\n    400 if no fields are provided.","operationId":"update_cycle_api_v1_cycles__cycle_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"cycle_id","in":"path","required":true,"schema":{"type":"string","title":"Cycle Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CycleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","cycles"],"summary":"Delete a political cycle","description":"Delete a political cycle. Any radio buys linked to the cycle are unlinked\n    (their cycle_id is set to NULL) before the cycle is removed.\n\n    Requires authentication.","operationId":"delete_cycle_api_v1_cycles__cycle_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"cycle_id","in":"path","required":true,"schema":{"type":"string","title":"Cycle Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/cycles/{cycle_id}/export":{"get":{"tags":["cbradio","cycles"],"summary":"Export commission data as CSV","description":"Export commission data for a cycle.\n\n**Formats:**\n- `detailed` — one row per commission rule per buy\n- `summary` — one row per buy\n- `accounting` — grouped by recipient across all buys","operationId":"export_cycle_csv_api_v1_cycles__cycle_id__export_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"cycle_id","in":"path","required":true,"schema":{"type":"string","title":"Cycle Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","description":"Export format: detailed, summary, accounting","default":"summary","title":"Format"},"description":"Export format: detailed, summary, accounting"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/cycles/{cycle_id}/summary":{"get":{"tags":["cbradio","cycles"],"summary":"Get aggregated totals for a cycle","description":"Return aggregated financial totals for all radio buys in a cycle.\n\n    Includes total gross, total profit, commission totals (active vs. held),\n    and paid/unpaid buy counts.","operationId":"get_cycle_summary_api_v1_cycles__cycle_id__summary_get","parameters":[{"name":"cycle_id","in":"path","required":true,"schema":{"type":"string","title":"Cycle Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/datasets":{"get":{"tags":["cbradio","Datasets (Fabric)"],"summary":"List datasets exposed by this driver","operationId":"list_datasets_api_v1_datasets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Datasets Api V1 Datasets Get"}}}}}}},"/cbradio/api/v1/datasets/{dataset_id}/rows":{"get":{"tags":["cbradio","Datasets (Fabric)"],"summary":"Stream dataset rows as ndjson","description":"Stream all rows for a dataset as newline-delimited JSON.\n\nConsumed by the cbmodels fabric materializer. Only whitelisted columns\nleave this endpoint. radio.rates is ~500K rows — streamed in pages so\nthe response never holds the full table in memory.","operationId":"stream_rows_api_v1_datasets__dataset_id__rows_get","parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","title":"Dataset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/districts/batch-boundaries":{"post":{"tags":["cbradio","coverage"],"summary":"Get boundaries for multiple districts","description":"Get GeoJSON boundaries for multiple districts in a single request.\n\n**Request:** district_type and comma-separated district IDs (max 50)\n\n**Response:** GeoJSON FeatureCollection with all boundaries.","operationId":"get_batch_district_boundaries_api_v1_districts_batch_boundaries_post","parameters":[{"name":"district_type","in":"query","required":true,"schema":{"type":"string","description":"Type: congressional, state_senate, state_house, county","title":"District Type"},"description":"Type: congressional, state_senate, state_house, county"},{"name":"district_ids","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated district IDs (max 50)","title":"District Ids"},"description":"Comma-separated district IDs (max 50)"},{"name":"simplify","in":"query","required":false,"schema":{"type":"boolean","description":"Return simplified geometries","default":false,"title":"Simplify"},"description":"Return simplified geometries"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/districts/counts":{"get":{"tags":["cbradio","coverage"],"summary":"Get district counts per state","description":"Returns the number of congressional, state senate, and state house districts\nfor a given state (or all states). Used by the frontend to generate district\nID lists for coverage map queries.","operationId":"get_district_counts_api_v1_districts_counts_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state code (e.g. MI). Omit for all states.","title":"State"},"description":"Two-letter state code (e.g. MI). Omit for all states."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/districts/county/list":{"get":{"tags":["cbradio","coverage"],"summary":"List counties by state (lightweight)","description":"Returns county FIPS codes and names from local data. No external API call.","operationId":"list_counties_by_state_api_v1_districts_county_list_get","parameters":[{"name":"state","in":"query","required":true,"schema":{"type":"string","description":"Two-letter state code","title":"State"},"description":"Two-letter state code"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/districts/{district_id}/voter-totals":{"get":{"tags":["cbradio","voters"],"summary":"Aggregated voter totals for any district type","description":"Returns total voter counts across a district, suitable as the\ndenominator for in-district CPE calculations (cbradio#228).\n\n**District types supported:**\n- `county` — direct lookup in `county_voter_data` by 5-digit FIPS.\n- `state_senate` / `state_house` / `congressional` — sums `county_voter_data`\n  weighted by the fraction of each constituent county that lies inside the\n  district. Constituent-county mapping is precomputed in cbdistricts.\n\n**Use this endpoint when:**\n- Computing in-district voter reach for CPE (sub-state proposals).\n- Showing total voter universe for a target district in the UI.\n\n**Pairs with `/stations/batch-coverage?district_id=...&district_type=...`** —\nmultiply this endpoint's `total_registered_voters` by each station's\n`station_in_district_pct` to get per-station in-district voter reach.","operationId":"get_district_voter_totals_api_v1_districts__district_id__voter_totals_get","parameters":[{"name":"district_id","in":"path","required":true,"schema":{"type":"string","title":"District Id"}},{"name":"district_type","in":"query","required":false,"schema":{"type":"string","pattern":"^(county|state_senate|state_house|congressional)$","description":"One of: county, state_senate, state_house, congressional.","default":"county","title":"District Type"},"description":"One of: county, state_senate, state_house, congressional."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__DistrictVoterTotalsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/districts/{district_type}/{district_id}/boundary":{"get":{"tags":["cbradio","coverage"],"summary":"Get district boundary GeoJSON","description":"Returns the GeoJSON boundary polygon for a district.\n\n**Path Parameters:**\n- `district_type` - One of: congressional, state_senate, state_house, county, state\n- `district_id` - District identifier (e.g., MI-07, MI-SD-24, 26065, MI)\n\n**Query Parameters:**\n- `simplify` - Return simplified geometry for faster rendering","operationId":"get_district_boundary_api_v1_districts__district_type___district_id__boundary_get","parameters":[{"name":"district_type","in":"path","required":true,"schema":{"type":"string","title":"District Type"}},{"name":"district_id","in":"path","required":true,"schema":{"type":"string","title":"District Id"}},{"name":"simplify","in":"query","required":false,"schema":{"type":"boolean","description":"Return simplified geometry","default":false,"title":"Simplify"},"description":"Return simplified geometry"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/districts/{district_type}/{district_id}/stations":{"get":{"tags":["cbradio","coverage"],"summary":"Get stations covering a district","description":"Returns all stations whose coverage contours intersect with a specific district.\n\n**Path Parameters:**\n- `district_type` - One of: congressional, state_senate, state_house, county\n- `district_id` - District identifier (e.g., MI-07, MI-SD-24, 26065)\n\n**Query Parameters:**\n- `min_coverage` - Minimum coverage percentage (0-100)\n- `threshold_only` - Only return stations meeting 50% threshold\n- `in_network_only` - Only return in-network stations\n- `sort_by` - Sort by: coverage, callsign, frequency","operationId":"get_district_stations_api_v1_districts__district_type___district_id__stations_get","parameters":[{"name":"district_type","in":"path","required":true,"schema":{"type":"string","title":"District Type"}},{"name":"district_id","in":"path","required":true,"schema":{"type":"string","title":"District Id"}},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"default":0,"title":"Min Coverage"}},{"name":"threshold_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only stations meeting 50% threshold","default":false,"title":"Threshold Only"},"description":"Only stations meeting 50% threshold"},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only in-network stations","default":false,"title":"In Network Only"},"description":"Only in-network stations"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by am or fm","title":"Service Type"},"description":"Filter by am or fm"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"AM only: day, night, or service","title":"Contour Type"},"description":"AM only: day, night, or service"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort by: coverage, callsign, frequency","default":"coverage","title":"Sort By"},"description":"Sort by: coverage, callsign, frequency"},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","description":"Sort order: asc, desc","default":"desc","title":"Sort Order"},"description":"Sort order: asc, desc"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/districts/{geoid}/voters":{"get":{"tags":["cbradio","voters"],"summary":"Get voter data and covering stations for a district","description":"Returns voter data for a county (by FIPS code) along with all stations\nwhose broadcast contour covers it.\n\n**Reverse of the station endpoint:** given a county, find all stations that reach it.\n\n**Use this endpoint when:**\n- Evaluating which stations can reach a target county\n- Assessing voter universe for a geographic area\n- Planning multi-station buys to maximize county coverage","operationId":"get_district_voters_api_v1_districts__geoid__voters_get","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","title":"Geoid"}},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage percentage to include a station.","default":0.5,"title":"Min Coverage"},"description":"Minimum coverage percentage to include a station."},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"If true, only return in-network stations.","default":false,"title":"In Network Only"},"description":"If true, only return in-network stations."}],"responses":{"200":{"description":"District voter data returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__DistrictVoterResponse"}}}},"404":{"description":"No coverage data for this district","content":{"application/json":{"example":{"detail":"No station coverage data for district 99999"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/documents":{"get":{"tags":["cbradio","documents"],"summary":"List documents","description":"Retrieve a paginated list of documents with optional filters.\n\n    Supports filtering by station callsign (partial match), document type,\n    file format, year, extraction status, and rate card type. Results are\n    ordered by callsign, then year descending, then filename.\n\n    **Filters:**\n    - `callsign`: Partial match on station callsign (case-insensitive)\n    - `type`: Document type (e.g. rate_card)\n    - `format`: File format (e.g. pdf, xlsx)\n    - `year`: Filing year\n    - `status`: Extraction status (pending, completed, failed, needs_review)\n    - `rate_card_type`: Rate card subtype\n\n    **Response:** Paginated object with `items`, `total`, `limit`, and `offset`.\n    Each item includes document metadata, extraction status, and confidence score.","operationId":"list_documents_api_v1_documents_get","parameters":[{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station callsign","title":"Callsign"},"description":"Filter by station callsign"},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by document type","title":"Type"},"description":"Filter by document type"},{"name":"format","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by file format","title":"Format"},"description":"Filter by file format"},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by year","title":"Year"},"description":"Filter by year"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by extraction status","title":"Status"},"description":"Filter by extraction status"},{"name":"rate_card_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by rate card type","title":"Rate Card Type"},"description":"Filter by rate card type"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/documents/by-format":{"get":{"tags":["cbradio","documents"],"summary":"Get document counts by format","description":"Return document counts grouped by file format (e.g. pdf, xlsx, doc).\n\n    Results are ordered by count descending. Each row includes the\n    format name and its total count.","operationId":"documents_by_format_api_v1_documents_by_format_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/documents/by-status":{"get":{"tags":["cbradio","documents"],"summary":"Get document counts by extraction status","description":"Return document counts grouped by extraction status.\n\n    Possible statuses are pending, completed, failed, and needs_review.\n    Results are ordered by count descending.","operationId":"documents_by_status_api_v1_documents_by_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/documents/by-type":{"get":{"tags":["cbradio","documents"],"summary":"Get document counts by type","description":"Return document counts grouped by document type.\n\n    Each row includes the document type, total count, and the number of\n    documents with a completed extraction. Results are ordered by count\n    descending.","operationId":"documents_by_type_api_v1_documents_by_type_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/documents/by-year":{"get":{"tags":["cbradio","documents"],"summary":"Get document counts by year","description":"Return document counts grouped by filing year.\n\n    Only documents with a non-null year are included. Results are ordered\n    by year descending (most recent first).","operationId":"documents_by_year_api_v1_documents_by_year_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/documents/count":{"get":{"tags":["cbradio","documents"],"summary":"Count documents","description":"Return the total number of documents matching the given filters.\n\n    Accepts the same filter parameters as the list endpoint: callsign\n    (partial, case-insensitive), document type, extraction status, and year.\n\n    **Response:** Object with a single `count` field.","operationId":"count_documents_api_v1_documents_count_get","parameters":[{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callsign"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/documents/pending":{"get":{"tags":["cbradio","documents"],"summary":"List pending documents","description":"Retrieve documents that are awaiting extraction, prioritized for processing.\n\n    Rate card documents are returned first, then other types. Within each\n    group, results are ordered by year descending and callsign.\n\n    **Response:** Array of document objects including file path information\n    needed for extraction processing. Limited to 500 results maximum.","operationId":"get_pending_documents_api_v1_documents_pending_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/documents/summary":{"get":{"tags":["cbradio","documents"],"summary":"Get document summary statistics","description":"Return aggregate statistics across all documents in the database.\n\n    **Response fields:**\n    - `total_documents`: Total document count\n    - `unique_stations`: Number of distinct station callsigns\n    - `pending`, `completed`, `failed`, `needs_review`: Counts by extraction status\n    - `total_extracted_rates`: Sum of all extracted rate entries","operationId":"document_summary_api_v1_documents_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/documents/{document_id}":{"get":{"tags":["cbradio","documents"],"summary":"Get document by ID","description":"Retrieve a single document record by its unique ID.\n\n    Returns all stored fields for the document, including metadata,\n    extraction status, confidence score, and file path information.\n\n    Returns 404 if the document ID does not exist.","operationId":"get_document_api_v1_documents__document_id__get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/documents/{document_id}/extracted-rates":{"get":{"tags":["cbradio","documents"],"summary":"Get extracted rates for document","description":"Retrieve all advertising rates that were extracted from this document.\n\n    Returns rate entries from the rate table linked by source_document_id,\n    ordered by station callsign and duration. Numeric fields (gross_rate,\n    net_rate) are converted to floats for JSON serialization.\n\n    Returns an empty array if no rates have been extracted yet.","operationId":"get_document_extracted_rates_api_v1_documents__document_id__extracted_rates_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/documents/{document_id}/file":{"head":{"tags":["cbradio","documents"],"summary":"Check document file metadata","description":"Return HTTP headers for the document file without downloading the body.\n\n    Useful for checking content type, file size, and filename before\n    initiating a full download. The response includes Content-Type,\n    Content-Length, and Content-Disposition headers.\n\n    Returns 404 if the document does not exist or the file is not found on disk.","operationId":"head_document_file_api_v1_documents__document_id__file_head","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"get":{"tags":["cbradio","documents"],"summary":"Download document file","description":"Download the actual document file from disk.\n\n    Returns the file with the appropriate media type (PDF, image, Word,\n    Excel, etc.) and the original filename in the Content-Disposition header.\n    File paths are resolved using configurable rewrites to handle relocated\n    storage directories.\n\n    Returns 404 if the document does not exist or the file is not found on disk.","operationId":"get_document_file_api_v1_documents__document_id__file_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/fcc/lookup/{callsign}":{"get":{"tags":["cbradio","fcc"],"summary":"Look up FCC station by callsign","description":"Look up FCC station data by callsign.\n\nHandles common callsign formats:\n- WABC (returns both AM and FM if they exist)\n- WABC-AM (returns specific service type)\n- WABC-FM","operationId":"lookup_by_callsign_api_v1_fcc_lookup__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__FCCStation"},"title":"Response Lookup By Callsign Api V1 Fcc Lookup  Callsign  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/fcc/partners":{"get":{"tags":["cbradio","fcc"],"summary":"Discover potential partners","description":"List FCC stations that are NOT in our database - potential new partners.\n\nThese are licensed AM/FM stations from the FCC database that we don't\ncurrently have in our system. Useful for outreach and expanding the network.","operationId":"discover_partners_api_v1_fcc_partners_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"State"},"description":"Filter by state"},{"name":"service_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by AM or FM","title":"Service Code"},"description":"Filter by AM or FM"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Max results","default":100,"title":"Limit"},"description":"Max results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Skip N results","default":0,"title":"Offset"},"description":"Skip N results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__FCCStation"},"title":"Response Discover Partners Api V1 Fcc Partners Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/fcc/states":{"get":{"tags":["cbradio","fcc"],"summary":"List states with FCC station counts","description":"Get count of FCC stations by state.","operationId":"get_states_summary_api_v1_fcc_states_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/fcc/stations":{"get":{"tags":["cbradio","fcc"],"summary":"List FCC stations","description":"List all stations from the FCC LMS database with optional filters.\n\nThis is the complete registry of all licensed AM/FM stations in the US\nfrom the FCC's authoritative database.\n\n**Filters:**\n- `state`: Two-letter state code\n- `service_code`: AM or FM\n- `unmatched_only`: Only show stations not matched to our database (for partner discovery)\n- `callsign`: Search by callsign (partial match)","operationId":"list_fcc_stations_api_v1_fcc_stations_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state (e.g., TX, CA)","title":"State"},"description":"Filter by state (e.g., TX, CA)"},{"name":"service_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by AM or FM","title":"Service Code"},"description":"Filter by AM or FM"},{"name":"unmatched_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only show unmatched stations","default":false,"title":"Unmatched Only"},"description":"Only show unmatched stations"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search callsign (partial)","title":"Callsign"},"description":"Search callsign (partial)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Max results","default":100,"title":"Limit"},"description":"Max results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Skip N results","default":0,"title":"Offset"},"description":"Skip N results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__FCCStation"},"title":"Response List Fcc Stations Api V1 Fcc Stations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/fcc/stations/{facility_id}":{"get":{"tags":["cbradio","fcc"],"summary":"Get FCC station by facility ID","description":"Get a specific FCC station by its FCC facility ID.","operationId":"get_fcc_station_api_v1_fcc_stations__facility_id__get","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","title":"Facility Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__FCCStation"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/fcc/stats":{"get":{"tags":["cbradio","fcc"],"summary":"FCC import statistics","description":"Get statistics about FCC data import and matching.","operationId":"get_fcc_stats_api_v1_fcc_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__FCCStatsResponse"}}}}}}},"/cbradio/api/v1/financials/import-legacy":{"post":{"tags":["cbradio","financials"],"summary":"Import legacy buy records","description":"Accepts array of historical buy records with financial data. Idempotent — updates existing buys by radio_buy_id. The cycle query param sets political_cycle on records that don't specify their own.","operationId":"import_legacy_api_v1_financials_import_legacy_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Default political cycle for records without one","title":"Cycle"},"description":"Default political cycle for records without one"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__LegacyBuyImport"},"title":"Records"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/financials/reconciliation":{"get":{"tags":["cbradio","financials"],"summary":"Financial reconciliation report","description":"Flags buys with mismatched splits, missing recipients, or unpaid status.","operationId":"get_reconciliation_api_v1_financials_reconciliation_get","parameters":[{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Political cycle filter","title":"Cycle"},"description":"Political cycle filter"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/financials/summary":{"get":{"tags":["cbradio","financials"],"summary":"Financial summary across radio buys","description":"Returns aggregated P&L, paid/unpaid counts, rural/non-rural breakdown, and per-buy details.\n\n**Source filter (cbradio#230):** defaults to `tracker` to surface only the canonical Buy Tracker spreadsheet rows (`id LIKE 'tracker-%'`). Set `source=ui` for the UI-conversion UUID rows alone, or `source=all` to see both (debugging). Until the UI conversion path stops emitting placeholder dates, `tracker` is the operator-facing canonical view.","operationId":"get_financial_summary_api_v1_financials_summary_get","parameters":[{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Political cycle filter, e.g. '2025-2026'","title":"Cycle"},"description":"Political cycle filter, e.g. '2025-2026'"},{"name":"source","in":"query","required":false,"schema":{"type":"string","pattern":"^(tracker|ui|all)$","description":"Buy source filter: 'tracker' (default, canonical), 'ui', or 'all'.","default":"tracker","title":"Source"},"description":"Buy source filter: 'tracker' (default, canonical), 'ui', or 'all'."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/contours":{"post":{"tags":["cbradio","geo"],"summary":"Batch-fetch coverage contours for multiple stations","description":"Return coverage contours for many stations as a single GeoJSON\n    FeatureCollection.\n\n    Collapses the proposal coverage map's per-station contour fan-out into ONE\n    request (cbradio-ui#139). Cache hits are served from a single bulk read;\n    misses are filled from cbdistricts bounded to a small concurrency, so a\n    large statewide map (~50-150 stations) can never exhaust the API's DB pool\n    the way the old unbounded per-station browser fan-out did (cbradio#304).\n\n    AM stations default to the `day` contour unless `contour_type` is given.\n    FM stations only have `service` contours.\n\n    Stations whose contour can't be resolved (unknown callsign, or synthesis\n    impossible upstream) are listed in `properties.missing` — the response is\n    never a 404 for partial misses.\n\n    **Maximum**: 250 callsigns per request.","operationId":"get_contours_batch_api_v1_geo_contours_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ContoursBatchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/contours/stats":{"get":{"tags":["cbradio","geo"],"summary":"Get contour data statistics from districts service","description":"Get statistics about available contour data from districts.campaignbrain.dev.\n\n    Returns counts by service type (AM/FM) and contour type (day/night/service).","operationId":"get_contour_stats_api_v1_geo_contours_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/geo/coverage":{"get":{"tags":["cbradio","geo"],"summary":"Get coverage contours","description":"Return coverage contours as GeoJSON FeatureCollection.\n\n    **For AM stations**, you can specify the contour type:\n    - `day`: Daytime coverage (default for AM)\n    - `night`: Nighttime coverage\n    - `service`: Default service contour\n\n    **For FM stations**, only `service` contour is available.\n\n    **cbradio#246**: when `state` is provided AND `in_network_only=true`, takes a\n    fast batched path through cbdistricts' `/states/{state}/coverage` (a single\n    call instead of N per-station calls) and includes synthesized polygons\n    (tier 2–4 from cbdistricts#14). Each Feature carries\n    `properties.contour_source` ∈ {`fcc`, `class_transmitter`,\n    `default_transmitter`, `class_city`, `default_city`}, and the\n    FeatureCollection's top-level `properties` includes `feature_count`,\n    `in_network_total`, `source_counts`, and `synthesis_failed`. This is the\n    customer-facing coverage map path.\n\n    Without `in_network_only=true`, falls back to the legacy per-station fetch\n    path (no synthesis, filtered by `has fcc_facility_id`).","operationId":"get_coverage_contours_api_v1_geo_coverage_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"State"},"description":"Filter by state"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"am or fm","title":"Service Type"},"description":"am or fm"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service (AM only). Defaults to day for AM.","title":"Contour Type"},"description":"Contour type: day, night, or service (AM only). Defaults to day for AM."},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"cbradio#246: when true with state, returns a batched FeatureCollection filtered to in-network stations with synthesized-or-canonical contours.","default":false,"title":"In Network Only"},"description":"cbradio#246: when true with state, returns a batched FeatureCollection filtered to in-network stations with synthesized-or-canonical contours."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/facilities/bulk":{"post":{"tags":["cbradio","geo"],"summary":"Bulk lookup stations by facility IDs","description":"Get FCC/geo data for multiple stations by their facility IDs.\n\n    Returns results for each requested facility ID. Stations not found\n    will have `error: \"Facility not found\"` in their result entry.\n\n    **Maximum**: 100 facility IDs per request.","operationId":"bulk_lookup_facilities_api_v1_geo_facilities_bulk_post","requestBody":{"content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Facility Ids"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/facilities/{facility_id}":{"get":{"tags":["cbradio","geo"],"summary":"Get station by FCC facility ID","description":"Get complete FCC/geo data for a station by its FCC facility ID.\n\n    The facility_id is the canonical identifier in FCC databases.\n    This endpoint enables lookup without callsign ambiguity.","operationId":"get_station_by_facility_id_api_v1_geo_facilities__facility_id__get","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","title":"Facility Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/facilities/{facility_id}/contour":{"get":{"tags":["cbradio","geo"],"summary":"Get station coverage contour by facility ID","description":"Return coverage contour as GeoJSON Feature using FCC facility ID.\n\n    **For AM stations**, you can specify the contour type:\n    - `day`: Daytime coverage (0.5 mV/m field strength)\n    - `night`: Nighttime coverage (2.0 mV/m field strength)\n    - `service`: Default service contour\n\n    **For FM stations**, only `service` contour is available.","operationId":"get_station_contour_by_facility_api_v1_geo_facilities__facility_id__contour_get","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","title":"Facility Id"}},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service (AM only)","title":"Contour Type"},"description":"Contour type: day, night, or service (AM only)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/nearby":{"get":{"tags":["cbradio","geo"],"summary":"Find nearby stations","description":"Find stations within a radius of a point using Haversine distance.\n\n    Returns stations sorted by distance from the query point.","operationId":"find_nearby_stations_api_v1_geo_nearby_get","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","maximum":90,"minimum":-90,"description":"Center latitude","title":"Lat"},"description":"Center latitude"},{"name":"lon","in":"query","required":true,"schema":{"type":"number","maximum":180,"minimum":-180,"description":"Center longitude","title":"Lon"},"description":"Center longitude"},{"name":"radius_km","in":"query","required":false,"schema":{"type":"number","maximum":500,"minimum":1,"description":"Search radius in km","default":50,"title":"Radius Km"},"description":"Search radius in km"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/points":{"get":{"tags":["cbradio","geo"],"summary":"Get stations as points","description":"Return all stations with coordinates as GeoJSON FeatureCollection (Points).","operationId":"get_station_points_api_v1_geo_points_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"State"},"description":"Filter by state"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"am or fm","title":"Service Type"},"description":"am or fm"},{"name":"min_power","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum power in kW","title":"Min Power"},"description":"Minimum power in kW"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"default":1000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/stations":{"get":{"tags":["cbradio","geo"],"summary":"List stations with geo data","description":"Retrieve stations with FCC geo data (facility ID, coordinates, power, class).\n\n    Returns all stations with an FCC facility ID, which enables contour lookup.\n    Coordinates may be null for some stations but contours can still be fetched.\n\n    **Filters:**\n    - `state`: Two-letter state abbreviation\n    - `station_class`: FCC class (A, B, C, D, C0, C1, C2, C3)\n    - `service_type`: am or fm\n    - `min_power`/`max_power`: Power range in kW\n    - `has_coordinates`: Only stations with antenna coordinates\n    - `has_contour`: Only stations with (or without) FCC contour data cached","operationId":"list_geo_stations_api_v1_geo_stations_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state","title":"State"},"description":"Filter by state"},{"name":"station_class","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"FCC station class","title":"Station Class"},"description":"FCC station class"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"am or fm","title":"Service Type"},"description":"am or fm"},{"name":"min_power","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum power in kW","title":"Min Power"},"description":"Minimum power in kW"},{"name":"max_power","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Maximum power in kW","title":"Max Power"},"description":"Maximum power in kW"},{"name":"has_coordinates","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only stations with antenna coordinates","title":"Has Coordinates"},"description":"Only stations with antenna coordinates"},{"name":"has_contour","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only stations with (or without) FCC contour data cached upstream","title":"Has Contour"},"description":"Only stations with (or without) FCC contour data cached upstream"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/stations/{callsign}":{"get":{"tags":["cbradio","geo"],"summary":"Get station geo details","description":"Get complete FCC/geo data for a single station.","operationId":"get_station_geo_api_v1_geo_stations__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/stations/{callsign}/contour":{"get":{"tags":["cbradio","geo"],"summary":"Get station coverage contour","description":"Return coverage contour as GeoJSON Feature.\n\n    Post-cbdistricts#14 (#246): cbdistricts always returns a Feature for any\n    station in its registry — canonical FCC polygon when available, otherwise\n    a synthesized polygon walking a tier ladder. `properties.contour_source`\n    enumerates the source: `fcc`, `class_transmitter`, `default_transmitter`,\n    `class_city`, `default_city`.\n\n    **For AM stations**, you can specify the contour type:\n    - `day`: Daytime coverage (0.5 mV/m field strength) - larger coverage\n    - `night`: Nighttime coverage (2.0 mV/m field strength) - smaller coverage\n    - `service`: Default service contour\n\n    **For FM stations**, only `service` contour is available.\n\n    AM stations default to `day` contour if not specified.","operationId":"get_station_contour_api_v1_geo_stations__callsign__contour_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contour type: day, night, or service (AM only). Defaults to day for AM.","title":"Contour Type"},"description":"Contour type: day, night, or service (AM only). Defaults to day for AM."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/geo/stats":{"get":{"tags":["cbradio","geo"],"summary":"Get FCC data statistics","description":"Aggregate statistics about FCC data coverage.","operationId":"get_geo_stats_api_v1_geo_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/geo/within":{"get":{"tags":["cbradio","geo"],"summary":"Find stations in bounding box","description":"Find stations within a geographic bounding box.","operationId":"find_stations_within_bbox_api_v1_geo_within_get","parameters":[{"name":"min_lat","in":"query","required":true,"schema":{"type":"number","maximum":90,"minimum":-90,"description":"South boundary","title":"Min Lat"},"description":"South boundary"},{"name":"max_lat","in":"query","required":true,"schema":{"type":"number","maximum":90,"minimum":-90,"description":"North boundary","title":"Max Lat"},"description":"North boundary"},{"name":"min_lon","in":"query","required":true,"schema":{"type":"number","maximum":180,"minimum":-180,"description":"West boundary","title":"Min Lon"},"description":"West boundary"},{"name":"max_lon","in":"query","required":true,"schema":{"type":"number","maximum":180,"minimum":-180,"description":"East boundary","title":"Max Lon"},"description":"East boundary"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/i360/county-report":{"post":{"tags":["cbradio","i360"],"summary":"Generate county-level voter propensity report for a state","description":"Generate a full-state voter universe report broken down by county.\n\nFor each county, returns registered voter counts plus swing and base universe sizes\n(individuals and unique households) based on political affinity and voting propensity filters.\n\n**How it works:** Queries i360 Public API via cbworkflow. Runs 3 API calls per county\n(registered, swing, base), so a state with 67 counties makes ~201 API calls.\n\n**Performance:** Expect ~45-60 seconds for large states (FL, TX). Small states (NH = 10 counties) complete in ~7 seconds.\n\n**Default criteria (standard campaign targeting):**\n- Swing: affinity 40-55, propensity 1-4\n- Base: affinity 65-100, propensity 1-4\n\n**Example request:**\n```json\n{\"state\": \"NH\"}\n```\n\n**Example with custom criteria:**\n```json\n{\n  \"state\": \"FL\",\n  \"swing_affinity_min\": 35,\n  \"swing_affinity_max\": 60,\n  \"base_affinity_min\": 70,\n  \"voting_propensity_min\": 1,\n  \"voting_propensity_max\": 5\n}\n```","operationId":"county_report_api_v1_i360_county_report_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CountyReportRequest"}}},"required":true},"responses":{"200":{"description":"County report generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CountyReportResponse"}}}},"401":{"description":"Authentication required"},"422":{"description":"Invalid request parameters","content":{"application/json":{"example":{"detail":[{"msg":"String should have at most 2 characters","type":"string_too_long"}]}}}},"502":{"description":"Upstream i360 API or cbworkflow error","content":{"application/json":{"example":{"detail":"Workflow service error: Connection refused"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/i360/search-counts":{"post":{"tags":["cbradio","i360"],"summary":"Query voter universe counts for a state or county","description":"Run a single voter universe count query against the i360 database.\n\nReturns aggregate counts: individuals, residences, apartments, mailing addresses,\nlandlines, and mobile phones matching the specified criteria.\n\n**Much faster than county-report** — single API call, typically responds in 1-3 seconds.\n\n**Use cases:**\n- Quick universe sizing: `{\"state\": \"NH\"}` → 1.3M individuals statewide\n- County-level drill-down: `{\"state\": \"FL\", \"county\": 306}` → Alachua County counts\n- Filtered targeting: add affinity + propensity ranges to narrow the universe\n\n**Important:** Always provide **both** min AND max for affinity/propensity filters.\nThe i360 API silently returns zeros if you send only one side of the range.\n\n**Example — statewide totals:**\n```json\n{\"state\": \"NH\"}\n```\n\n**Example — county with filters:**\n```json\n{\n  \"state\": \"FL\",\n  \"county\": 306,\n  \"affinity_min\": 40,\n  \"affinity_max\": 55,\n  \"propensity_min\": 1,\n  \"propensity_max\": 4\n}\n```","operationId":"search_counts_api_v1_i360_search_counts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SearchCountsRequest"}}},"required":true},"responses":{"200":{"description":"Counts returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SearchCountsResponse"}}}},"401":{"description":"Authentication required"},"422":{"description":"Invalid request parameters","content":{"application/json":{"example":{"detail":[{"msg":"String should have at most 2 characters","type":"string_too_long"}]}}}},"502":{"description":"Upstream i360 API or cbworkflow error","content":{"application/json":{"example":{"detail":"No counts returned from i360"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/import/batches":{"post":{"tags":["cbradio","import"],"summary":"Create import batch","description":"Create a new import batch with records for validation.","operationId":"create_import_batch_api_v1_import_batches_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CreateBatchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"get":{"tags":["cbradio","import"],"summary":"List import batches","description":"List all import batches with optional filters.","operationId":"list_import_batches_api_v1_import_batches_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by batch status","title":"Status"},"description":"Filter by batch status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/import/batches/{batch_id}":{"get":{"tags":["cbradio","import"],"summary":"Get import batch status","description":"Get the current status of an import batch.","operationId":"get_import_batch_api_v1_import_batches__batch_id__get","parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string","title":"Batch Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/import/staged":{"get":{"tags":["cbradio","import"],"summary":"List staged records","description":"List staged records with optional filters.","operationId":"list_staged_records_api_v1_import_staged_get","parameters":[{"name":"batch_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by batch ID","title":"Batch Id"},"description":"Filter by batch ID"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by validation status","title":"Status"},"description":"Filter by validation status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/import/staged/bulk-approve":{"post":{"tags":["cbradio","import"],"summary":"Bulk approve staged records","description":"Approve multiple staged records at once.","operationId":"bulk_approve_staged_records_api_v1_import_staged_bulk_approve_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkApproveRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/import/staged/{record_id}":{"get":{"tags":["cbradio","import"],"summary":"Get staged record","description":"Get a single staged record by ID.","operationId":"get_staged_record_api_v1_import_staged__record_id__get","parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/import/staged/{record_id}/approve":{"post":{"tags":["cbradio","import"],"summary":"Approve staged record","description":"Approve a staged record for import.","operationId":"approve_staged_record_api_v1_import_staged__record_id__approve_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/import/staged/{record_id}/corrections":{"post":{"tags":["cbradio","import"],"summary":"Apply correction to staged record","description":"Apply corrections to a staged record and re-validate.","operationId":"apply_correction_api_v1_import_staged__record_id__corrections_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ApplyCorrectionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/import/staged/{record_id}/reject":{"post":{"tags":["cbradio","import"],"summary":"Reject staged record","description":"Reject a staged record.","operationId":"reject_staged_record_api_v1_import_staged__record_id__reject_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RejectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/internal/sync/documents":{"post":{"tags":["cbradio","sync-internal"],"summary":"Batch create document records","description":"Create multiple document records in a single transaction. Each document\n    is assigned a new UUID and starts with extraction_status 'pending'.\n\n    Used by the sync pipeline to register newly discovered files (rate cards,\n    contracts, etc.) before extraction begins. Requires authentication.","operationId":"create_documents_api_v1_internal_sync_documents_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__DocumentCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__DocumentCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/internal/sync/documents/{document_id}/extraction-result":{"post":{"tags":["cbradio","sync-internal"],"summary":"Store extraction result (OCR + rates + status) in one transaction","description":"Submit the complete extraction result for a document in a single\n    transaction. Updates the document's OCR text, extraction status,\n    confidence, and timing fields. If rates are provided with a valid\n    station_id, inserts them into the rate table and updates the document's\n    extracted_rates_count.\n\n    Also writes an extraction_log entry for audit purposes. Re-submissions\n    are safe — duplicate rate inserts are caught and skipped. Requires\n    authentication.","operationId":"submit_extraction_result_api_v1_internal_sync_documents__document_id__extraction_result_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ExtractionResultRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ExtractionResultResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/internal/sync/existing-doc-paths":{"get":{"tags":["cbradio","sync-internal"],"summary":"Get existing document file paths for dedup","description":"Return all file_path values for documents matching the given source tag.\n\n    Used by the sync pipeline to skip documents that have already been\n    ingested. Requires authentication.","operationId":"get_existing_doc_paths_api_v1_internal_sync_existing_doc_paths_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"source","in":"query","required":true,"schema":{"type":"string","description":"Document source tag","title":"Source"},"description":"Document source tag"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Existing Doc Paths Api V1 Internal Sync Existing Doc Paths Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/internal/sync/gap-documents":{"get":{"tags":["cbradio","sync-internal"],"summary":"Get documents with extracted rates but no rate rows in DB","description":"Find documents where extraction completed and counted rates, but no\n    corresponding rate rows were actually inserted into the database.\n\n    These gaps typically indicate rate INSERT failures during extraction.\n    Returns the document metadata and OCR text length to aid re-extraction.\n    Requires authentication.","operationId":"get_gap_documents_api_v1_internal_sync_gap_documents_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/internal/sync/orphaned-rates":{"get":{"tags":["cbradio","sync-internal"],"summary":"Get stations with unlinked rates for a source","description":"Return stations that have rates linked to documents with the given source\n    tag but whose rate_card_id is NULL (i.e., rates not yet associated with\n    a rate_card).\n\n    Used by the backfill pipeline to identify stations needing rate_card\n    creation. Requires authentication.","operationId":"get_orphaned_rates_api_v1_internal_sync_orphaned_rates_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"source","in":"query","required":true,"schema":{"type":"string","description":"Document source tag","title":"Source"},"description":"Document source tag"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Orphaned Rates Api V1 Internal Sync Orphaned Rates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/internal/sync/pending-documents":{"get":{"tags":["cbradio","sync-internal"],"summary":"Get documents pending extraction","description":"Return documents whose extraction_status is 'pending' and file_size > 0.\n\n    Results are ordered to prioritize images over other formats, and rate\n    cards over other document types. Use `images_only=true` to restrict to\n    PNG/JPG formats. Requires authentication.","operationId":"get_pending_documents_api_v1_internal_sync_pending_documents_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":10,"title":"Limit"}},{"name":"images_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Images Only"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Pending Documents Api V1 Internal Sync Pending Documents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/internal/sync/rate-card-exists":{"get":{"tags":["cbradio","sync-internal"],"summary":"Check if a rate card exists for station/source/year","description":"Check whether a rate_card record already exists for the given station,\n    source, and year combination.\n\n    Returns `exists: true` with the rate_card_id if found, or\n    `exists: false` otherwise. Requires authentication.","operationId":"check_rate_card_exists_api_v1_internal_sync_rate_card_exists_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"station_id","in":"query","required":true,"schema":{"type":"string","title":"Station Id"}},{"name":"source","in":"query","required":true,"schema":{"type":"string","title":"Source"}},{"name":"year","in":"query","required":true,"schema":{"type":"integer","title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Check Rate Card Exists Api V1 Internal Sync Rate Card Exists Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/internal/sync/rate-cards/backfill":{"post":{"tags":["cbradio","sync-internal"],"summary":"Create rate card and link orphaned rates for a station","description":"Create a rate_card for the given station/source/year if one does not\n    already exist, then link all orphaned rates (rate_card_id IS NULL) for\n    that station and source to the rate_card.\n\n    If the rate_card already exists, it is reused and its source_document_id\n    is backfilled if missing. Returns the rate_card_id, whether it was newly\n    created, and how many rates are now linked. Requires authentication.","operationId":"backfill_rate_card_api_v1_internal_sync_rate_cards_backfill_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCardBackfillRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCardBackfillResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/internal/sync/station-map":{"get":{"tags":["cbradio","sync-internal"],"summary":"Get callsign to station ID/state mapping","description":"Return a mapping of every station callsign to its ID and state.\n\n    Used by the sync pipeline to resolve callsigns to station IDs without\n    opening a direct DB connection. Requires authentication.","operationId":"get_station_map_api_v1_internal_sync_station_map_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Station Map Api V1 Internal Sync Station Map Get"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/internal/sync_gdrive":{"post":{"tags":["cbradio","sync-gdrive"],"summary":"Trigger Gdrive Sync","operationId":"trigger_gdrive_sync_api_v1_internal_sync_gdrive_post","parameters":[{"name":"x-cbradio-signature","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cbradio-Signature"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues":{"get":{"tags":["cbradio","issues"],"summary":"List issues for a project","description":"List issues for a specific cb* project with optional filters.\n\n    **Filter Options:**\n    - `state`: open, closed, all (default: open)\n    - `label`: Filter by label (can specify multiple)\n    - `assignee`: Filter by assignee login (@me for self)\n    - `author`: Filter by author login\n    - `milestone`: Filter by milestone name\n    - `search`: GitHub search query within the project\n\n    **Examples:**\n    - `/api/v1/issues?project=cbradio` - All open issues\n    - `/api/v1/issues?project=cbradio&state=closed&label=bug` - Closed bugs\n    - `/api/v1/issues?project=cbradio&assignee=@me` - My assigned issues","operationId":"list_issues_api_v1_issues_get","parameters":[{"name":"project","in":"query","required":true,"schema":{"type":"string","description":"Project slug (e.g., cbradio)","title":"Project"},"description":"Project slug (e.g., cbradio)"},{"name":"state","in":"query","required":false,"schema":{"type":"string","description":"State filter: open, closed, all","default":"open","title":"State"},"description":"State filter: open, closed, all"},{"name":"label","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by labels","title":"Label"},"description":"Filter by labels"},{"name":"assignee","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by assignee login","title":"Assignee"},"description":"Filter by assignee login"},{"name":"author","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by author login","title":"Author"},"description":"Filter by author login"},{"name":"milestone","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by milestone name","title":"Milestone"},"description":"Filter by milestone name"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"GitHub search query","title":"Search"},"description":"GitHub search query"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum results","default":30,"title":"Limit"},"description":"Maximum results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__IssueListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/bulk/close":{"post":{"tags":["cbradio","issues"],"summary":"Bulk close issues","description":"Close multiple issues at once with optional comment and reason.","operationId":"bulk_close_api_v1_issues_bulk_close_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkCloseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkOperationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/bulk/label":{"post":{"tags":["cbradio","issues"],"summary":"Bulk label operation","description":"Add or remove labels from multiple issues at once.","operationId":"bulk_label_api_v1_issues_bulk_label_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkLabelRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkOperationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/projects":{"get":{"tags":["cbradio","issues"],"summary":"List all CB* projects","description":"Returns a list of all cb* projects with their GitHub repo paths and descriptions.\n    Use these slugs for filtering and targeting operations.","operationId":"list_projects_api_v1_issues_projects_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbradio__Project"},"type":"array","title":"Response List Projects Api V1 Issues Projects Get"}}}}}}},"/cbradio/api/v1/issues/search":{"get":{"tags":["cbradio","issues"],"summary":"Search issues across projects","description":"Search for issues across multiple cb* projects using GitHub search syntax.\n\n    **Example queries:**\n    - `authentication error` - Find issues mentioning authentication errors\n    - `is:open label:bug` - Find open bugs\n    - `assignee:isenbek` - Issues assigned to a user","operationId":"search_issues_api_v1_issues_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","description":"Search query (GitHub search syntax)","title":"Q"},"description":"Search query (GitHub search syntax)"},{"name":"projects","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated project slugs (e.g., cbauth,cbapp)","title":"Projects"},"description":"Comma-separated project slugs (e.g., cbauth,cbapp)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum results","default":30,"title":"Limit"},"description":"Maximum results"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/{project}":{"post":{"tags":["cbradio","issues"],"summary":"Create a new issue","description":"Create a new issue in the specified project.\n\n    **Request Body:**\n    - `title` (required): Issue title\n    - `body`: Issue description (markdown supported)\n    - `labels`: List of label names to apply\n    - `assignees`: List of GitHub logins (use @me for self)\n    - `milestone`: Milestone name","operationId":"create_issue_api_v1_issues__project__post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__IssueCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/{project}/{number}":{"get":{"tags":["cbradio","issues"],"summary":"Get a single issue","description":"Get detailed information about a specific issue by project and number.","operationId":"get_issue_api_v1_issues__project___number__get","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}},{"name":"comments","in":"query","required":false,"schema":{"type":"boolean","description":"Include comments in response","default":false,"title":"Comments"},"description":"Include comments in response"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","issues"],"summary":"Update an issue","description":"Update an existing issue. All fields are optional.\n\n    **Request Body:**\n    - `title`: New title\n    - `body`: New body\n    - `add_labels`: Labels to add\n    - `remove_labels`: Labels to remove\n    - `add_assignees`: Assignees to add\n    - `remove_assignees`: Assignees to remove\n    - `milestone`: Set milestone name\n    - `remove_milestone`: Remove milestone association","operationId":"update_issue_api_v1_issues__project___number__patch","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__IssueUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","issues"],"summary":"Delete an issue","description":"Permanently delete an issue. Requires admin permissions.","operationId":"delete_issue_api_v1_issues__project___number__delete","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/{project}/{number}/close":{"post":{"tags":["cbradio","issues"],"summary":"Close an issue","description":"Close an open issue with optional comment and reason.\n\n    **Reasons:**\n    - `completed`: Issue was resolved\n    - `not_planned`: Issue won't be fixed","operationId":"close_issue_api_v1_issues__project___number__close_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__IssueClose"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/{project}/{number}/comments":{"get":{"tags":["cbradio","issues"],"summary":"List issue comments","description":"Get all comments on an issue.","operationId":"list_comments_api_v1_issues__project___number__comments_get","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","issues"],"summary":"Add a comment","description":"Add a new comment to an issue.","operationId":"add_comment_api_v1_issues__project___number__comments_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CommentCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/{project}/{number}/lock":{"post":{"tags":["cbradio","issues"],"summary":"Lock issue conversation","description":"Lock an issue to prevent further comments.\n\n    **Lock reasons:**\n    - `off_topic`: Discussion went off-topic\n    - `resolved`: Issue was resolved\n    - `spam`: Spam content\n    - `too_heated`: Discussion became heated","operationId":"lock_issue_api_v1_issues__project___number__lock_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__IssueLock"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/{project}/{number}/pin":{"post":{"tags":["cbradio","issues"],"summary":"Pin an issue","description":"Pin an issue to the repository (limited to 3 pins).","operationId":"pin_issue_api_v1_issues__project___number__pin_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/{project}/{number}/reopen":{"post":{"tags":["cbradio","issues"],"summary":"Reopen a closed issue","description":"Reopen a previously closed issue with optional comment.","operationId":"reopen_issue_api_v1_issues__project___number__reopen_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__IssueReopen"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/{project}/{number}/unlock":{"post":{"tags":["cbradio","issues"],"summary":"Unlock issue conversation","description":"Unlock an issue to allow comments again.","operationId":"unlock_issue_api_v1_issues__project___number__unlock_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/issues/{project}/{number}/unpin":{"post":{"tags":["cbradio","issues"],"summary":"Unpin an issue","description":"Remove pin from an issue.","operationId":"unpin_issue_api_v1_issues__project___number__unpin_post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","title":"Project"}},{"name":"number","in":"path","required":true,"schema":{"type":"integer","title":"Number"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Issue"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/make-good-requests":{"get":{"tags":["cbradio","make-good-requests"],"summary":"List make-good requests","description":"List make-good requests with optional filtering.","operationId":"list_make_good_requests_api_v1_make_good_requests_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"order_packet_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by order packet","title":"Order Packet Id"},"description":"Filter by order packet"},{"name":"radio_buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by radio buy","title":"Radio Buy Id"},"description":"Filter by radio buy"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__MakeGoodListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/make-good-requests/{make_good_id}":{"get":{"tags":["cbradio","make-good-requests"],"summary":"Get make-good request","description":"Get a single make-good request by ID.","operationId":"get_make_good_request_api_v1_make_good_requests__make_good_id__get","parameters":[{"name":"make_good_id","in":"path","required":true,"schema":{"type":"string","title":"Make Good Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__MakeGoodRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","make-good-requests"],"summary":"Resolve make-good request","description":"Resolve (approve/reject/complete) a make-good request.","operationId":"resolve_make_good_request_api_v1_make_good_requests__make_good_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"make_good_id","in":"path","required":true,"schema":{"type":"string","title":"Make Good Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__MakeGoodRequestResolve"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__MakeGoodRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/network-exits":{"get":{"tags":["cbradio","network-exits"],"summary":"List exit requests","description":"List network exit requests with filtering.","operationId":"list_exit_requests_api_v1_network_exits_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"station_callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station","title":"Station Callsign"},"description":"Filter by station"},{"name":"requested_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by requester","title":"Requested By"},"description":"Filter by requester"},{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by reason code","title":"Reason"},"description":"Filter by reason code"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ExitRequestListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","network-exits"],"summary":"Create exit request","description":"Request removal of a station from the network.","operationId":"create_exit_request_api_v1_network_exits_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ExitRequestCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ExitRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/network-exits/{exit_id}":{"get":{"tags":["cbradio","network-exits"],"summary":"Get exit request detail","description":"Get full exit request details with audit trail.","operationId":"get_exit_request_api_v1_network_exits__exit_id__get","parameters":[{"name":"exit_id","in":"path","required":true,"schema":{"type":"string","title":"Exit Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ExitRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","network-exits"],"summary":"Cancel exit request","description":"Cancel a pending exit request.","operationId":"cancel_exit_request_api_v1_network_exits__exit_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"exit_id","in":"path","required":true,"schema":{"type":"string","title":"Exit Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/network-exits/{exit_id}/review":{"post":{"tags":["cbradio","network-exits"],"summary":"Review exit request (Admin only)","description":"Approve or reject a network exit request.","operationId":"review_exit_request_api_v1_network_exits__exit_id__review_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"exit_id","in":"path","required":true,"schema":{"type":"string","title":"Exit Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ExitRequestReview"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ExitRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/nielsen/by-geography":{"get":{"tags":["cbradio","nielsen"],"summary":"Get Nielsen ratings by state or market (bulk)","description":"Returns all rated stations in a state or market, with derived scoring fields.\n\nPowers the **Proposal Builder** station scoring and **SRM State View**. At least one of\n`state` or `market` is required.\n\n**Filters:**\n- `state` — two-letter state code (matches market_state, supports multi-state markets)\n- `market` — exact market name (e.g. \"Detroit MI\")\n- `format` — filter by Nielsen format (e.g. \"News/Talk\", \"Country\")\n- `min_share` — minimum AQH share threshold\n- `top_quartile_only` — if true, only top-25% stations per market\n\n**Examples:**\n- All Michigan stations: `GET /nielsen/by-geography?state=MI`\n- Detroit market only: `GET /nielsen/by-geography?market=Detroit MI`\n- Top Country stations in TX: `GET /nielsen/by-geography?state=TX&format=Country&top_quartile_only=true`","operationId":"by_geography_api_v1_nielsen_by_geography_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state code (e.g. MI, TX)","title":"State"},"description":"Two-letter state code (e.g. MI, TX)"},{"name":"market","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact Nielsen market name","title":"Market"},"description":"Exact Nielsen market name"},{"name":"format","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by Nielsen format","title":"Format"},"description":"Filter by Nielsen format"},{"name":"min_share","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Minimum AQH share","title":"Min Share"},"description":"Minimum AQH share"},{"name":"top_quartile_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only top-25% stations","default":false,"title":"Top Quartile Only"},"description":"Only top-25% stations"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"description":"Max results","default":500,"title":"Limit"},"description":"Max results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Stations found matching criteria","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__GeographyResponse"}}}},"422":{"description":"Must provide state or market","content":{"application/json":{"example":{"detail":"At least one of 'state' or 'market' is required"}}}}}}},"/cbradio/api/v1/nielsen/formats":{"get":{"tags":["cbradio","nielsen"],"summary":"Get format distribution with station counts and average ratings","description":"Returns aggregate statistics for each Nielsen format, sorted by station count descending.\n\nFor each format: how many stations use it, how many markets it appears in, and the average\nlatest rating (rating_4) across all stations.\n\n**Top formats by station count:** Country (~453), Christian CHR (~425), CHR (~300),\nSports (~290), News/Talk (~289), AC (~236), Classic Rock (~234).\n\n**Filters:**\n- `min_stations` — exclude rare formats (default 1). Use `min_stations=10` to focus on major formats.\n\n**Use cases:**\n- \"What are the most common station formats?\" → call with defaults\n- \"Which formats have the highest average ratings?\" → sort client-side by avg_latest_rating\n- \"How many Country stations are there?\" → find the Country row","operationId":"list_formats_api_v1_nielsen_formats_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"min_stations","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Minimum station count to include","default":1,"title":"Min Stations"},"description":"Minimum station count to include"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__FormatSummary"},"title":"Response List Formats Api V1 Nielsen Formats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/nielsen/markets":{"get":{"tags":["cbradio","nielsen"],"summary":"List Nielsen markets with station counts and demographics","description":"Returns all Nielsen markets ordered by rank (1=New York, 2=Los Angeles, etc.),\nwith station counts and population demographics.\n\n**Filters:**\n- `state` — matches against market_state (supports multi-state markets like \"IA-IL\")\n- `min_rank` / `max_rank` — restrict to a rank range (e.g. top 50 markets)\n- `min_population` — filter small markets (e.g. only metros > 1M)\n\n**Pagination:** Use `limit` (default 300, max 500) and `offset` for paging.\n\n**Examples:**\n- All markets: `GET /nielsen/markets`\n- Texas markets: `GET /nielsen/markets?state=TX`\n- Top 10 markets: `GET /nielsen/markets?max_rank=10`\n- Large markets: `GET /nielsen/markets?min_population=1000000`","operationId":"list_markets_api_v1_nielsen_markets_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state code (e.g. NH, TX)","title":"State"},"description":"Filter by state code (e.g. NH, TX)"},{"name":"min_rank","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Minimum market rank","title":"Min Rank"},"description":"Minimum market rank"},{"name":"max_rank","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Maximum market rank","title":"Max Rank"},"description":"Maximum market rank"},{"name":"min_population","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Minimum population","title":"Min Population"},"description":"Minimum population"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max results","default":300,"title":"Limit"},"description":"Max results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Offset for pagination","default":0,"title":"Offset"},"description":"Offset for pagination"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__MarketSummary"},"title":"Response List Markets Api V1 Nielsen Markets Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/nielsen/markets/{market_rank}":{"get":{"tags":["cbradio","nielsen"],"summary":"Get market detail with all station ratings","description":"Returns full detail for a specific market including all station ratings,\nsorted by latest rating (rating_4) descending — strongest performers first.\n\n**Filters:**\n- `nielsen_format` — only return stations with this format (exact match, e.g. \"Country\", \"AC\")\n- `min_rating` — only return stations with rating_4 >= this value (e.g. 5.0 for strong performers)\n\n**Examples:**\n- All stations in New York: `GET /nielsen/markets/1`\n- Country stations in market #50: `GET /nielsen/markets/50?nielsen_format=Country`\n- Strong performers in Chicago: `GET /nielsen/markets/3?min_rating=5.0`\n\n**Interpreting ratings:**\n- `rating_4` > 10.0 = dominant station in the market\n- `rating_4` > 5.0 = strong performer\n- `rating_4` > 2.0 = average\n- `rating_4` < 1.0 or null = minimal measurable audience","operationId":"get_market_api_v1_nielsen_markets__market_rank__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"market_rank","in":"path","required":true,"schema":{"type":"integer","title":"Market Rank"}},{"name":"nielsen_format","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by Nielsen format","title":"Nielsen Format"},"description":"Filter by Nielsen format"},{"name":"min_rating","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"Minimum latest rating (period 4)","title":"Min Rating"},"description":"Minimum latest rating (period 4)"}],"responses":{"200":{"description":"Market detail with station ratings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__MarketDetail"}}}},"404":{"description":"Market rank not found","content":{"application/json":{"example":{"detail":"Market rank 9999 not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/nielsen/stations/{callsign}":{"get":{"tags":["cbradio","nielsen"],"summary":"Get all Nielsen ratings for a station across markets","description":"Returns all Nielsen rating entries for a given callsign.\n\nStations often appear in **multiple overlapping markets** — for example, WLTW-FM (New York AC)\nappears in 8 markets: New York (#1), Nassau-Suffolk (#20), Hudson Valley (#41), etc.\nThis endpoint returns all of them, ordered by market rank.\n\n**Callsign is case-insensitive** — `wltw-fm` and `WLTW-FM` both work.\n\n**Examples:**\n- `GET /nielsen/stations/WLTW-FM` — 8 ratings across NY metro area markets\n- `GET /nielsen/stations/WGIR-FM` — Manchester NH Classic Rock station","operationId":"get_station_ratings_api_v1_nielsen_stations__callsign__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Station ratings found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__StationRatingsResponse"}}}},"404":{"description":"No Nielsen ratings for this callsign","content":{"application/json":{"example":{"detail":"No Nielsen ratings found for ZZZZ-FM"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/nielsen/stats":{"get":{"tags":["cbradio","nielsen"],"summary":"Get Nielsen dataset summary statistics","description":"Returns high-level counts of the Nielsen ratings dataset.\n\nUse this to check data coverage and freshness, or to display on a dashboard.\n\n**Typical values:** ~5,151 ratings, ~4,638 callsigns, ~239 markets, ~66 formats.","operationId":"nielsen_stats_api_v1_nielsen_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__NielsenStatsResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/order-packets":{"get":{"tags":["cbradio","order-packets"],"summary":"List all order packets","description":"List all order packets with optional filtering by status or radio buy.","operationId":"list_order_packets_api_v1_order_packets_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"radio_buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/order-packets/bulk-send":{"post":{"tags":["cbradio","order-packets"],"summary":"Bulk send order packets","description":"Send multiple order packets at once via cbemail.","operationId":"bulk_send_order_packets_api_v1_order_packets_bulk_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkSendRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkSendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/order-packets/dashboard":{"get":{"tags":["cbradio","order-packets"],"summary":"Dashboard status summary","description":"Get order packet status summary and alerts.","operationId":"get_order_dashboard_api_v1_order_packets_dashboard_get","parameters":[{"name":"radio_buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__DashboardResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/order-packets/{order_packet_id}":{"get":{"tags":["cbradio","order-packets"],"summary":"Get order packet","description":"Get a single order packet with full details.","operationId":"get_order_packet_api_v1_order_packets__order_packet_id__get","parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OrderPacketDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","order-packets"],"summary":"Update order packet","description":"Update order packet details.","operationId":"update_order_packet_api_v1_order_packets__order_packet_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OrderPacketUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OrderPacketDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/order-packets/{order_packet_id}/audit-log":{"get":{"tags":["cbradio","order-packets"],"summary":"Get order confirmation audit log","description":"Get the audit trail for an order packet.","operationId":"get_order_audit_log_api_v1_order_packets__order_packet_id__audit_log_get","parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AuditLogResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/order-packets/{order_packet_id}/cancel":{"post":{"tags":["cbradio","order-packets"],"summary":"Cancel order packet","description":"Cancel an order packet.","operationId":"cancel_order_packet_api_v1_order_packets__order_packet_id__cancel_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CancelOrderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CancelOrderResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/order-packets/{order_packet_id}/confirm":{"post":{"tags":["cbradio","order-packets"],"summary":"Manual confirm order","description":"Manually mark an order as confirmed (e.g., from email reply).","operationId":"manual_confirm_order_api_v1_order_packets__order_packet_id__confirm_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ManualConfirmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/order-packets/{order_packet_id}/generate-pdf":{"post":{"tags":["cbradio","order-packets"],"summary":"Generate order sheet PDF","description":"Generate the order sheet PDF and upload to S3.","operationId":"generate_order_pdf_api_v1_order_packets__order_packet_id__generate_pdf_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__GeneratePDFResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/order-packets/{order_packet_id}/send":{"post":{"tags":["cbradio","order-packets"],"summary":"Send order packet email","description":"Send the order email with attachments via cbemail.","operationId":"send_order_packet_api_v1_order_packets__order_packet_id__send_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"order_packet_id","in":"path","required":true,"schema":{"type":"string","title":"Order Packet Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SendOrderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SendOrderResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/ownership-groups":{"post":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Create ownership group","description":"Create a new ownership group (station owner, rep firm, etc.).","operationId":"create_ownership_group_api_v1_ownership_groups_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OwnershipGroupCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"get":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"List ownership groups","description":"Retrieve a paginated list of ownership groups with optional filters.","operationId":"list_ownership_groups_api_v1_ownership_groups_get","parameters":[{"name":"payment_method","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by payment method","title":"Payment Method"},"description":"Filter by payment method"},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active status","title":"Is Active"},"description":"Filter by active status"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name or legal name","title":"Search"},"description":"Search by name or legal name"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/ownership-groups/by-station/{callsign}":{"get":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Find ownership group by station","description":"Returns the ownership group for the specified station, or 404 if not assigned.","operationId":"get_ownership_group_by_station_api_v1_ownership_groups_by_station__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/ownership-groups/{recipient_id}":{"get":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Get ownership group","description":"Retrieve a single ownership group by ID.","operationId":"get_ownership_group_api_v1_ownership_groups__recipient_id__get","parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"put":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Update ownership group","description":"Update an existing ownership group.","operationId":"update_ownership_group_api_v1_ownership_groups__recipient_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OwnershipGroupUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/ownership-groups/{recipient_id}/stations":{"post":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Add station to recipient","description":"Add a station callsign to a payment recipient's station list.","operationId":"add_station_to_recipient_api_v1_ownership_groups__recipient_id__stations_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AddStationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/ownership-groups/{recipient_id}/stations/{callsign}":{"delete":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Remove station from recipient","description":"Remove a station callsign from a payment recipient's station list.","operationId":"remove_station_from_recipient_api_v1_ownership_groups__recipient_id__stations__callsign__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}},{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/payment-recipients":{"post":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Create ownership group","description":"Create a new ownership group (station owner, rep firm, etc.).","operationId":"create_ownership_group_api_v1_payment_recipients_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OwnershipGroupCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"get":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"List ownership groups","description":"Retrieve a paginated list of ownership groups with optional filters.","operationId":"list_ownership_groups_api_v1_payment_recipients_get","parameters":[{"name":"payment_method","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by payment method","title":"Payment Method"},"description":"Filter by payment method"},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active status","title":"Is Active"},"description":"Filter by active status"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name or legal name","title":"Search"},"description":"Search by name or legal name"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/payment-recipients/by-station/{callsign}":{"get":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Find ownership group by station","description":"Returns the ownership group for the specified station, or 404 if not assigned.","operationId":"get_ownership_group_by_station_api_v1_payment_recipients_by_station__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/payment-recipients/{recipient_id}":{"get":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Get ownership group","description":"Retrieve a single ownership group by ID.","operationId":"get_ownership_group_api_v1_payment_recipients__recipient_id__get","parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"put":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Update ownership group","description":"Update an existing ownership group.","operationId":"update_ownership_group_api_v1_payment_recipients__recipient_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OwnershipGroupUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OwnershipGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/payment-recipients/{recipient_id}/stations":{"post":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Add station to recipient","description":"Add a station callsign to a payment recipient's station list.","operationId":"add_station_to_recipient_api_v1_payment_recipients__recipient_id__stations_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AddStationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/payment-recipients/{recipient_id}/stations/{callsign}":{"delete":{"tags":["cbradio","ownership-groups","ownership-groups"],"summary":"Remove station from recipient","description":"Remove a station callsign from a payment recipient's station list.","operationId":"remove_station_from_recipient_api_v1_payment_recipients__recipient_id__stations__callsign__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}},{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/pipelines":{"get":{"tags":["cbradio","Pipelines"],"summary":"Freshness of every cbradio data feed","description":"Per-feed: row count, last write, age, and whether that is acceptable.\n\nDeliberately unauthenticated and mesh-internal, matching the `/datasets`\ndriver posture — it exposes counts and timestamps, no row content.","operationId":"list_pipelines_api_v1_pipelines_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Pipelines Api V1 Pipelines Get"}}}}}}},"/cbradio/api/v1/pipelines/{key}":{"get":{"tags":["cbradio","Pipelines"],"summary":"Freshness of one feed","operationId":"get_pipeline_api_v1_pipelines__key__get","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Pipeline Api V1 Pipelines  Key  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/planning-budget/{plan_id}/pdf":{"get":{"tags":["cbradio","planning-budget"],"summary":"Render the Planning Budget PDF (headless capture of the print route)","description":"Headless-captures the cbradio-ui print route for `plan_id`\n    (`{FRONTEND_URL}/print/planning-budget/{plan_id}`) to a branded PDF and streams\n    it back (`application/pdf`). The coverage map renders inline in the print route,\n    so the capture includes it; stations without contour polygons render as markers.\n\n    Returns **503** if the headless browser isn't provisioned on the host\n    (`playwright` + chromium). The frontend can wire a download affordance to this\n    now — it works the moment the print route ships.","operationId":"planning_budget_pdf_api_v1_planning_budget__plan_id__pdf_get","parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}},{"name":"wait_selector","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSS selector the print route sets once the inline map has settled; best-effort (a missing signal won't fail the render).","default":".pb-map-ready","title":"Wait Selector"},"description":"CSS selector the print route sets once the inline map has settled; best-effort (a missing signal won't fail the render)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals":{"get":{"tags":["cbradio","proposals"],"summary":"List proposals","description":"Returns a paginated list of proposals with aggregate financial data.\n\n    **Filters:**\n    - `status`: Filter by proposal status (draft, sent, accepted, rejected, converted, etc.)\n    - `client_id`: Filter by client ID\n    - `start_after`: Only return proposals with start_date on or after this date (YYYY-MM-DD)\n\n    Each result includes computed totals (total_gross, total_net) and line_item_count\n    from the associated proposal_line_item records. Results are ordered by creation\n    date descending.","operationId":"list_proposals_api_v1_proposals_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by client","title":"Client Id"},"description":"Filter by client"},{"name":"start_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date after (YYYY-MM-DD)","title":"Start After"},"description":"Start date after (YYYY-MM-DD)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","proposals"],"summary":"Create proposal","description":"Create a new proposal with optional line items.\n\n    A unique proposal number (P-YYYY-NNNN) is auto-generated. The request body\n    accepts proposal metadata (client, dates, ad type, geography targets) and an\n    optional list of line items. For each line item, net rate is auto-calculated\n    at 85% of gross if not provided, and total_gross/total_net are computed from\n    spots_per_week * weeks * rate.\n\n    List fields (target_states, target_dmas) are serialized to JSON for storage.\n    The proposal is created inside a single transaction -- if any line item insert\n    fails, the entire operation is rolled back.","operationId":"create_proposal_api_v1_proposals_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ProposalCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/approval-queue":{"get":{"tags":["cbradio","proposals"],"summary":"Get proposals pending approval","description":"Returns proposals that are pending approval, ordered by submission date.\n\n    **Filters:**\n    - `approver_id`: Filter by requested approver\n    - `status`: Filter by status (default: pending_approval)\n\n    Proposals enter the approval queue when submitted via submit-for-approval.","operationId":"get_approval_queue_api_v1_proposals_approval_queue_get","parameters":[{"name":"approver_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by requested approver ID","title":"Approver Id"},"description":"Filter by requested approver ID"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Maximum results","default":50,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/generate":{"post":{"tags":["cbradio","proposals"],"summary":"Generate proposal from criteria","description":"Auto-generate a complete proposal by selecting stations and rates matching criteria.\n\n    **Process:**\n    1. Queries rates matching target states, ad type, spot duration, and preemption level\n    2. Selects the best rate per station (lowest or highest based on `prefer_lowest_rates`)\n    3. Creates a proposal record with a generated proposal number (P-YYYY-NNNN)\n    4. Creates line items for each selected station with computed totals\n    5. Respects budget and max_stations constraints\n\n    Rate selection uses the RATE_SOURCE_PRIORITY_SQL ordering to prefer the most\n    authoritative rate source per station. Net rates default to 85% of gross when\n    not available. The entire operation is transactional -- any failure rolls back\n    all inserts.\n\n    Returns summary statistics including stations_matched, stations_included,\n    totals, and budget_remaining.","operationId":"generate_proposal_api_v1_proposals_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__GenerateProposalRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__GenerateProposalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/proposals/metrics":{"get":{"tags":["cbradio","proposals"],"summary":"Get proposal metrics","description":"Returns aggregate metrics about proposals in the system.\n\n    **Included metrics:**\n    - Total proposals by status\n    - Proposal value statistics (total gross, net, average)\n    - Conversion rates (accepted/sent, converted/accepted)\n    - Time-based metrics (proposals created this month/quarter)\n    - Top clients by proposal value\n\n    This endpoint is useful for dashboards and reporting.","operationId":"get_proposal_metrics_api_v1_proposals_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/proposals/plan":{"post":{"tags":["cbradio","proposals"],"summary":"Solve a budget into a complete flighted media plan","description":"Turn campaign parameters and a strategy posture into a complete, priced,\n    flighted plan: every station, every daypart, every week, every spot count,\n    each carrying the rule that put it there.\n\n    **Writes nothing.** This is a solver, not a mutation — call it as often as\n    you like while a buyer moves the levers.\n\n    **How it allocates.** One greedy loop in which two moves compete on the same\n    value-per-dollar terms: *open* a station at the minimum viable weight, or\n    *deepen* an open one by a spot. Reach-versus-frequency therefore falls out\n    of the arithmetic rather than being hard-coded, and `posture.reach_bias`\n    moves it deliberately.\n\n    The objective is **target audience per client dollar**. Margin is a bounded\n    lever (`posture.network_preference`, default 0) with `posture.min_score` as\n    a floor beneath it, and its position is echoed back on the response.\n\n    **Every price declares its basis.** `card` means a real rate row off the\n    station's own card; `benchmark` means the median for its state and power\n    class. Set `posture.require_card_rates` to refuse estimates — the plan will\n    then be smaller rather than quietly estimated.\n\n    **Lock and re-solve.** `posture.locked_stations` holds stations the buyer\n    has already committed to; the solver spends what is left around them.\n    Locking a plan's own output and re-solving reproduces that plan exactly, so\n    a buyer can keep the three stations they know they want, move any other\n    lever, and see only what the lever changed. `posture.excluded_stations` is\n    the other half.\n\n    **Name the audience.** Pass `target_audience` when you know it —\n    `election_type` cannot express partisan lean, so a Democratic primary sent\n    as `primary` is scored against a Republican format profile. An unrecognised\n    `target_audience` is a 400 rather than a silent fall back to swing.\n\n    **Target states or districts.** Districts are the better mode and win when\n    both are given: sub-state targeting activates the coverage dimension via\n    geo-fit — district reach blended with the share of the station's own signal\n    that lands inside it — so a 100kW blowtorch that sprays mostly elsewhere no\n    longer outranks the station that actually covers the seat.\n\n    **Known limit.** A statewide query has no fit dimension to blend, so\n    coverage scores 0 and `score` caps at 8 of 16; a `min_score` above 8 will\n    empty a statewide plan. District targeting raises the ceiling to 12. Cost\n    per target voter, the fourth dimension, still needs i360 and is inactive in\n    both modes.\n\n    Daypart weights and the concentration curve are calibrated against 8,245\n    historical line items — see `scripts/replay_historical_buys.py`.","operationId":"plan_proposal_api_v1_proposals_plan_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PlanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PlanResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/proposals/suggest":{"post":{"tags":["cbradio","proposals"],"summary":"Get station suggestions for a proposal (non-committing)","description":"Return ranked station suggestions scored by format, coverage, Nielsen, and voter efficiency.\n\n    Scoring mirrors cbradio-ui/shared/scoring.ts with 4 dimensions (0-4 each, max 16).\n    Rates are NOT a ranking factor since many stations have missing or zero rates.\n    In-network stations sort first as a tiebreaker.\n\n    **Required:** `target_geography.states` must contain at least one state.\n\n    The response includes each suggestion's individual dimension scores, a confidence\n    value based on data completeness, and budget coverage tracking. Stations are\n    filtered by station_pool (network-only vs all) and constrained by budget and\n    max_stations limits. This endpoint does not create or modify any records.","operationId":"suggest_stations_api_v1_proposals_suggest_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SuggestStationsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/proposals/{proposal_id}":{"get":{"tags":["cbradio","proposals"],"summary":"Get proposal with line items","description":"Retrieve a single proposal by ID with all associated line items and computed totals.\n\n    The response includes the full proposal metadata, all line items ordered by\n    station callsign, and computed aggregate fields: total_gross, total_net, and\n    total_spots. JSON fields (target_states, target_dmas, target_geography) are\n    automatically parsed from their stored string representation.\n\n    Returns 404 if the proposal does not exist.","operationId":"get_proposal_api_v1_proposals__proposal_id__get","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ProposalWithLineItems"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","proposals"],"summary":"Update proposal","description":"Update proposal metadata (not line items).\n\n    Only proposals in 'draft' or 'pending_review' status can be updated. Fields\n    that are not included in the request body are left unchanged (standard PATCH\n    semantics via `exclude_unset`). List/geography fields are serialized to JSON\n    before storage, and enum fields are stored by value.\n\n    Returns the full updated proposal with line items. Returns 400 if the proposal\n    is in a non-editable status (e.g. sent, accepted, converted).","operationId":"update_proposal_api_v1_proposals__proposal_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ProposalUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","proposals"],"summary":"Delete proposal","description":"Permanently delete a proposal and all its associated line items.\n\n    This is a cascading delete: all proposal_line_item records for this proposal\n    are removed first, followed by the proposal record itself. Both deletions\n    happen within a single transaction.\n\n    Returns 204 No Content on success. Returns 404 if the proposal does not exist.","operationId":"delete_proposal_api_v1_proposals__proposal_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/accept":{"post":{"tags":["cbradio","proposals"],"summary":"Mark proposal as accepted","description":"Mark a proposal as accepted by the client.\n\n    Sets the proposal status to 'accepted' and records the acceptance timestamp.\n    Optional notes can be attached to document the acceptance terms or conditions.\n\n    Only proposals in 'sent' status can be accepted. After acceptance, the proposal\n    can be converted to a radio buy via the /convert endpoint.","operationId":"accept_proposal_api_v1_proposals__proposal_id__accept_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AcceptProposal"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/convert":{"post":{"tags":["cbradio","proposals"],"summary":"Convert proposal to radio buy","description":"Convert an accepted proposal to a radio buy.\n\n    This creates:\n    1. A new radio_buy record with data from the proposal\n    2. radio_buy_station records for each line item with classification and commission\n    3. An initial version snapshot (v1) for payments tracking","operationId":"convert_proposal_api_v1_proposals__proposal_id__convert_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ConvertProposal"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/export":{"get":{"tags":["cbradio","proposals"],"summary":"Export proposal to Excel or CSV","description":"Export a proposal as a downloadable CSV or Excel (.xlsx) file.\n\n    The export follows the standard radio buy format with columns for Client,\n    Station, Days, Time, Format, DMA, Length, Rate, weekly spot distribution\n    columns, Total Spots, and Total Spend. Stations are grouped with subtotals,\n    and a grand total row is appended at the bottom.\n\n    **Formats:**\n    - `csv`: Returns a text/csv stream (always available)\n    - `xlsx`: Returns an Excel workbook with formatted headers, merged title row,\n      and auto-adjusted column widths (requires openpyxl; returns 501 if not installed)\n\n    The response includes a Content-Disposition header for browser download.","operationId":"export_proposal_api_v1_proposals__proposal_id__export_get","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["csv","xlsx"],"default":"csv","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/line-items":{"post":{"tags":["cbradio","proposals"],"summary":"Add line items to proposal","description":"Add one or more line items to an existing proposal.\n\n    Each line item represents a station buy with callsign, spots_per_week, weeks,\n    and rate information. Net rate defaults to 85% of gross if not provided.\n    Total gross and total net are auto-calculated from spots * weeks * rate.\n\n    Line items can only be added to proposals in 'draft' or 'pending_review' status.\n    The proposal's updated_at timestamp is refreshed after the insert. Supports\n    AI-sourced items with optional confidence score, reason, and classification fields.\n\n    Returns the list of created IDs and a count.","operationId":"add_line_items_api_v1_proposals__proposal_id__line_items_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__LineItemCreate"},"title":"Items"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/line-items/{item_id}":{"patch":{"tags":["cbradio","proposals"],"summary":"Update line item","description":"Update a specific line item within a proposal.\n\n    Accepts partial updates to spots_per_week, weeks, gross_rate_per_spot,\n    net_rate_per_spot, and notes. When any rate or quantity field changes,\n    total_gross and total_net are automatically recalculated. Unchanged fields\n    retain their current values.\n\n    Only line items belonging to proposals in 'draft' or 'pending_review' status\n    can be modified. The parent proposal's updated_at timestamp is also refreshed.\n\n    Returns the updated line item record.","operationId":"update_line_item_api_v1_proposals__proposal_id__line_items__item_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__LineItemUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","proposals"],"summary":"Delete line item","description":"Delete a specific line item from a proposal.\n\n    Removes the line item record and updates the parent proposal's updated_at\n    timestamp. Only line items belonging to proposals in 'draft' or 'pending_review'\n    status can be deleted.\n\n    Returns 204 No Content on success. Returns 404 if the line item does not exist\n    within the specified proposal, or 400 if the proposal is in a non-editable status.","operationId":"delete_line_item_api_v1_proposals__proposal_id__line_items__item_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/reject":{"post":{"tags":["cbradio","proposals"],"summary":"Mark proposal as rejected","description":"Mark a proposal as rejected by the client.\n\n    Sets the proposal status to 'rejected' and records the rejection timestamp\n    along with the client's rejection reason. Only proposals in 'sent' status\n    can be rejected.\n\n    The rejection reason is stored in the `rejection_reason` field for reporting\n    and future reference.","operationId":"reject_proposal_api_v1_proposals__proposal_id__reject_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RejectProposal"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/review-decision":{"post":{"tags":["cbradio","proposals"],"summary":"Submit approval decision for proposal","description":"Submit an approval decision for a proposal.\n\n    **Decision values:**\n    - `approved`: Proposal is approved\n    - `rejected`: Proposal is rejected\n    - `needs_revision`: Proposal needs changes before approval\n\n    **Optional fields:**\n    - `notes`: Decision notes\n    - `modifications`: JSON object describing any approved modifications\n\n    This creates an approval history record for audit purposes.","operationId":"review_decision_api_v1_proposals__proposal_id__review_decision_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"decision","in":"query","required":true,"schema":{"type":"string","description":"Approval decision: approved, rejected, needs_revision","title":"Decision"},"description":"Approval decision: approved, rejected, needs_revision"},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Decision notes","title":"Notes"},"description":"Decision notes"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"type":"object","additionalProperties":true},{"type":"null"}],"title":"Modifications"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/send":{"post":{"tags":["cbradio","proposals"],"summary":"Send proposal to client","description":"Mark a proposal as sent to the client, advancing its workflow status.\n\n    Sets the proposal status to 'sent' and records an expiration date calculated\n    from the provided `expires_in_days` value. Optional notes can be attached.\n\n    Only proposals in 'draft' or 'pending_review' status can be sent. Once sent,\n    the proposal can be accepted, rejected, or allowed to expire.","operationId":"send_proposal_api_v1_proposals__proposal_id__send_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SendProposal"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/share-link":{"post":{"tags":["cbradio","proposals"],"summary":"Create share link for proposal","description":"Generate a secure, shareable link for a proposal via CBLinks service.","operationId":"create_share_link_api_v1_proposals__proposal_id__share_link_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CreateShareLinkRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ShareLinkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","proposals"],"summary":"Revoke share link for proposal","description":"Revoke the active share link for a proposal.","operationId":"revoke_share_link_api_v1_proposals__proposal_id__share_link_delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/share-link/status":{"get":{"tags":["cbradio","proposals"],"summary":"Get share link status","description":"Get the status and analytics for the active share link.","operationId":"get_share_link_status_api_v1_proposals__proposal_id__share_link_status_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ShareLinkStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/submit-for-approval":{"post":{"tags":["cbradio","proposals"],"summary":"Submit proposal for approval","description":"Submit a proposal for approval review.\n\n    **Required fields:**\n    - `approver_id`: ID of the user who should review this proposal\n\n    **Optional fields:**\n    - `notes`: Notes for the approver\n    - `discount_percentage`: Requested discount percentage (0-100)\n\n    This changes the proposal status to 'pending_approval' and records\n    the submission in the approval history.","operationId":"submit_for_approval_api_v1_proposals__proposal_id__submit_for_approval_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"approver_id","in":"query","required":true,"schema":{"type":"string","description":"ID of the requested approver","title":"Approver Id"},"description":"ID of the requested approver"},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Notes for the approver","title":"Notes"},"description":"Notes for the approver"},{"name":"discount_percentage","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100,"minimum":0},{"type":"null"}],"description":"Requested discount percentage","title":"Discount Percentage"},"description":"Requested discount percentage"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/proposals/{proposal_id}/summary":{"get":{"tags":["cbradio","proposals"],"summary":"Get proposal financial summary","description":"Returns a financial summary for a proposal.\n\n    Computes aggregate totals across all line items including total_gross,\n    total_net, total_spots, and commission_amount (based on the proposal's\n    commission_rate). Also returns the count of line items and a deduplicated\n    list of station callsigns.\n\n    This is a read-only computed view useful for dashboards and proposal\n    review screens.","operationId":"get_proposal_summary_api_v1_proposals__proposal_id__summary_get","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ProposalFinancialSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/public/contact":{"post":{"tags":["cbradio","public-contact"],"summary":"Submit contact form","description":"Accept a public contact form submission from ruralamfm.com.\n\n    No authentication required. Includes honeypot spam prevention (the\n    `website` field should be left empty by real users) and IP-based rate\n    limiting (max 5 submissions per IP per hour). Silently accepts\n    honeypot-triggered submissions without storing them.","operationId":"submit_contact_form_api_v1_public_contact_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ContactFormRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ContactFormResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/public/orders/{confirmation_token}":{"get":{"tags":["cbradio","public-orders"],"summary":"Get order by token (No Auth)","description":"Public endpoint for stations to view their order. No authentication required.","operationId":"get_public_order_api_v1_public_orders__confirmation_token__get","parameters":[{"name":"confirmation_token","in":"path","required":true,"schema":{"type":"string","title":"Confirmation Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PublicOrderResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/public/orders/{confirmation_token}/confirm":{"post":{"tags":["cbradio","public-orders"],"summary":"Confirm order (No Auth)","description":"Public endpoint for stations to confirm their order.","operationId":"confirm_public_order_api_v1_public_orders__confirmation_token__confirm_post","parameters":[{"name":"confirmation_token","in":"path","required":true,"schema":{"type":"string","title":"Confirmation Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PublicConfirmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PublicConfirmResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/public/orders/{confirmation_token}/make-good":{"post":{"tags":["cbradio","public-orders"],"summary":"Request make-good (No Auth)","description":"Public endpoint for stations to request make-good.","operationId":"request_public_make_good_api_v1_public_orders__confirmation_token__make_good_post","parameters":[{"name":"confirmation_token","in":"path","required":true,"schema":{"type":"string","title":"Confirmation Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__MakeGoodRequestCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PublicMakeGoodResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/public/proposals/{slug}":{"get":{"tags":["cbradio","public-proposals"],"summary":"Get proposal by share link slug (No Auth)","description":"Public endpoint for clients to view proposal details. No authentication required.","operationId":"get_public_proposal_api_v1_public_proposals__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PublicProposalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/public/proposals/{slug}/approve":{"post":{"tags":["cbradio","public-proposals"],"summary":"Approve proposal (No Auth)","description":"Public endpoint for clients to approve a proposal.","operationId":"approve_public_proposal_api_v1_public_proposals__slug__approve_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ClientApprovalRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ClientApprovalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buy-versions/{version_id}":{"get":{"tags":["cbradio","versions"],"summary":"Get version details","description":"Get detailed information about a specific version including:\n    - Version metadata and totals\n    - Commission breakdown by station classification\n    - All payee groups with their payment status","operationId":"get_version_detail_api_v1_radio_buy_versions__version_id__get","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuyVersionDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","versions"],"summary":"Delete a version","description":"Delete a version from a radio buy.\n\n    **Restrictions:**\n    - Cannot delete the only version for a buy (at least one must remain)\n    - Cannot delete versions with 'paid' or 'in_progress' payment status\n\n    Deleting a version removes:\n    - The version record\n    - All payee group snapshots\n    - All payment status records\n    - All payment status history","operationId":"delete_version_api_v1_radio_buy_versions__version_id__delete","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__DeleteVersionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buy-versions/{version_id}/delta/{from_version_id}":{"get":{"tags":["cbradio","versions"],"summary":"Get delta between versions","description":"Compare two versions and return the differences.\n\n    The delta includes:\n    - Summary counts (stations/groups added, removed, modified)\n    - Financial deltas (gross, commission, station payments)\n    - Detailed payee group changes\n\n    **Note:** The `from_version_id` is the older version and `version_id` is the newer version.","operationId":"get_version_delta_api_v1_radio_buy_versions__version_id__delta__from_version_id__get","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}},{"name":"from_version_id","in":"path","required":true,"schema":{"type":"string","title":"From Version Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__VersionDelta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buy-versions/{version_id}/export":{"get":{"tags":["cbradio","versions"],"summary":"Export accounting spreadsheet","description":"Export version data as a CSV or Excel file for accounting purposes.\n\n    The export includes all payee groups with:\n    - Pay to, payment method, email\n    - Station count, spot count\n    - Gross amount, station payments, commission\n    - Stations (comma-separated callsigns)\n    - Payment status, paid date, reference number\n\n    **Formats:**\n    - `csv`: Comma-separated values (default)\n    - `xlsx`: Excel spreadsheet (requires openpyxl)","operationId":"export_version_api_v1_radio_buy_versions__version_id__export_get","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}},{"name":"format","in":"query","required":false,"schema":{"$ref":"#/components/schemas/cbradio__ExportFormat","description":"Export format","default":"csv"},"description":"Export format"},{"name":"include_payment_status","in":"query","required":false,"schema":{"type":"boolean","description":"Include payment status columns","default":true,"title":"Include Payment Status"},"description":"Include payment status columns"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buy-versions/{version_id}/payment-history":{"get":{"tags":["cbradio","versions"],"summary":"Get payment status history","description":"Get the history of payment status changes for a version.\n\n    Returns all status changes across all payee groups, sorted by change time descending.","operationId":"get_payment_history_api_v1_radio_buy_versions__version_id__payment_history_get","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PaymentHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buy-versions/{version_id}/payments/{group_key}":{"patch":{"tags":["cbradio","versions"],"summary":"Update payment status","description":"Update the payment status for a payee group in a version.\n\n    **Status values:**\n    - `not_started`: Payment not yet initiated\n    - `in_progress`: Payment being processed\n    - `paid`: Payment completed\n    - `issue`: Payment has an issue requiring attention\n\n    All changes are recorded in the payment history for audit purposes.","operationId":"update_payment_status_api_v1_radio_buy_versions__version_id__payments__group_key__patch","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}},{"name":"group_key","in":"path","required":true,"schema":{"type":"string","title":"Group Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__src__api__models__version__PaymentStatusUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PaymentStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys":{"get":{"tags":["cbradio","radio-buys"],"summary":"List radio buys","description":"List radio ad buying campaigns with optional filters.\n\n    **Filters:**\n    - `client_id`: Filter by client ID\n    - `buyer_agency_id`: Filter by buyer agency ID\n    - `consultant_id`: Filter by consultant ID\n    - `ad_type`: Filter by ad type (candidate, issue)\n    - `election_type`: Filter by election type (primary, general)\n    - `is_complete`: Filter by completion status\n    - `start_date_from/to`: Filter by start date range\n    - `min_gross/max_gross`: Filter by gross amount range\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 500.","operationId":"list_radio_buys_api_v1_radio_buys_get","parameters":[{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by client ID","title":"Client Id"},"description":"Filter by client ID"},{"name":"buyer_agency_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by buyer agency ID","title":"Buyer Agency Id"},"description":"Filter by buyer agency ID"},{"name":"consultant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by consultant ID","title":"Consultant Id"},"description":"Filter by consultant ID"},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AdTypeEnum"},{"type":"null"}],"description":"Filter by ad type","title":"Ad Type"},"description":"Filter by ad type"},{"name":"election_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ElectionType"},{"type":"null"}],"description":"Filter by election type","title":"Election Type"},"description":"Filter by election type"},{"name":"is_complete","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by completion status","title":"Is Complete"},"description":"Filter by completion status"},{"name":"is_paid","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by payment status","title":"Is Paid"},"description":"Filter by payment status"},{"name":"cycle_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by political cycle ID","title":"Cycle Id"},"description":"Filter by political cycle ID"},{"name":"start_date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date from (YYYY-MM-DD)","title":"Start Date From"},"description":"Start date from (YYYY-MM-DD)"},{"name":"start_date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date to (YYYY-MM-DD)","title":"Start Date To"},"description":"Start date to (YYYY-MM-DD)"},{"name":"min_gross","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum gross amount","title":"Min Gross"},"description":"Minimum gross amount"},{"name":"max_gross","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Maximum gross amount","title":"Max Gross"},"description":"Maximum gross amount"},{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"Sort field with direction prefix: -created_at, -start_date, -gross_amount, name","default":"-start_date","title":"Sort"},"description":"Sort field with direction prefix: -created_at, -start_date, -gross_amount, name"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__RadioBuySummary"},"title":"Response List Radio Buys Api V1 Radio Buys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","radio-buys"],"summary":"Create radio buy","description":"Create a new radio buy campaign. Requires authentication.","operationId":"create_radio_buy_api_v1_radio_buys_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuyCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuy"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/count":{"get":{"tags":["cbradio","radio-buys"],"summary":"Count radio buys","description":"Return the total number of radio buys matching the given filters.\n\n    Accepts the same filter parameters as the list endpoint (client_id,\n    buyer_agency_id, ad_type, election_type, is_complete) but returns only\n    a count instead of full records.","operationId":"count_radio_buys_api_v1_radio_buys_count_get","parameters":[{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"}},{"name":"buyer_agency_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"}},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AdTypeEnum"},{"type":"null"}],"title":"Ad Type"}},{"name":"election_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ElectionType"},{"type":"null"}],"title":"Election Type"}},{"name":"is_complete","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Complete"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Count Radio Buys Api V1 Radio Buys Count Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/summary":{"get":{"tags":["cbradio","radio-buys"],"summary":"Get financial summary","description":"Get aggregated financial summary across all radio buys.","operationId":"get_radio_buy_summary_api_v1_radio_buys_summary_get","parameters":[{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"}},{"name":"buyer_agency_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"}},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AdTypeEnum"},{"type":"null"}],"title":"Ad Type"}},{"name":"election_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ElectionType"},{"type":"null"}],"title":"Election Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuyFinancialSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{buy_id}/versions":{"post":{"tags":["cbradio","versions"],"summary":"Create a new version","description":"Create a new version snapshot for a radio buy.\n\n    This creates:\n    - An immutable snapshot of the current radio buy state\n    - Payee group aggregations\n    - Initial payment status records (not_started)\n    - Commission breakdown by station classification\n\n    **Version numbering:** Versions are numbered sequentially starting from 1.\n    The first version (v1) is typically created automatically when a proposal\n    is converted to a radio buy.","operationId":"create_version_api_v1_radio_buys__buy_id__versions_post","parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuyVersionCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuyVersion"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"get":{"tags":["cbradio","versions"],"summary":"List versions for a radio buy","description":"List all versions for a radio buy, sorted by version number descending.\n\n    Each version includes:\n    - Version ID and number\n    - Snapshot totals (gross, commission, station payments)\n    - Station count and spot count\n    - Whether the version has provisional stations","operationId":"list_versions_api_v1_radio_buys__buy_id__versions_get","parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__VersionListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}":{"get":{"tags":["cbradio","radio-buys"],"summary":"Get radio buy details","description":"Get a radio buy by ID with client, agency, and consultant details.","operationId":"get_radio_buy_api_v1_radio_buys__radio_buy_id__get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuyWithRelations"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","radio-buys"],"summary":"Update radio buy (partial)","description":"Partially update a radio buy. Requires authentication.","operationId":"update_radio_buy_api_v1_radio_buys__radio_buy_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuy"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"put":{"tags":["cbradio","radio-buys"],"summary":"Update radio buy","description":"Update a radio buy. Requires authentication.","operationId":"update_radio_buy_api_v1_radio_buys__radio_buy_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuy"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","radio-buys"],"summary":"Delete radio buy","description":"Delete a radio buy. Requires authentication.","operationId":"delete_radio_buy_api_v1_radio_buys__radio_buy_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/commission-recipients":{"get":{"tags":["cbradio","commissions"],"summary":"List commission recipients for a radio buy","description":"Return all commission recipients attached to a radio buy, ordered by\n    creation date.\n\n    Each recipient includes the split percentage, computed split amount,\n    and payment status.","operationId":"list_commission_recipients_api_v1_radio_buys__radio_buy_id__commission_recipients_get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","commissions"],"summary":"Add commission recipient","description":"Create a new commission recipient for a radio buy. The split amount is\n    automatically calculated from the recipient's split percentage and the\n    buy's net amount.\n\n    After creation, the buy's profit is recalculated. Requires authentication.","operationId":"create_commission_recipient_api_v1_radio_buys__radio_buy_id__commission_recipients_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CommissionRecipientCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/commission-recipients/{recipient_id}":{"put":{"tags":["cbradio","commissions"],"summary":"Update commission recipient","description":"Partially update an existing commission recipient. If the split percentage\n    is changed, all split amounts for the buy are recalculated.\n\n    The buy's profit is always recalculated after the update. Requires\n    authentication.","operationId":"update_commission_recipient_api_v1_radio_buys__radio_buy_id__commission_recipients__recipient_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CommissionRecipientUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","commissions"],"summary":"Delete commission recipient","description":"Remove a commission recipient from a radio buy. The buy's profit is\n    recalculated after deletion. Requires authentication.","operationId":"delete_commission_recipient_api_v1_radio_buys__radio_buy_id__commission_recipients__recipient_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string","title":"Recipient Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/commission-rules":{"get":{"tags":["cbradio","commissions"],"summary":"List commission rules for a radio buy","description":"Return all commission rules attached to a radio buy, ordered by creation\n    date.\n\n    Each rule defines per-category commission rates (in-network, non-rural,\n    Salem, total gross, rural gross) and includes the computed payout amount\n    and hold status.","operationId":"list_commission_rules_api_v1_radio_buys__radio_buy_id__commission_rules_get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","commissions"],"summary":"Add commission rule","description":"Create a new commission rule for a radio buy. Rules define per-category\n    commission rates for a named recipient (consultant, agency, etc.).\n\n    Supports optional custom basis amount, hold status, and notes. Requires\n    authentication.","operationId":"create_commission_rule_api_v1_radio_buys__radio_buy_id__commission_rules_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CommissionRuleCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/commission-rules/{rule_id}":{"put":{"tags":["cbradio","commissions"],"summary":"Update commission rule","description":"Partially update an existing commission rule. Any provided rate fields,\n    hold status, or custom basis amount are updated.\n\n    Decimal fields are converted to float for storage. Requires authentication.","operationId":"update_commission_rule_api_v1_radio_buys__radio_buy_id__commission_rules__rule_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CommissionRuleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","commissions"],"summary":"Delete commission rule","description":"Remove a commission rule from a radio buy. Requires authentication.","operationId":"delete_commission_rule_api_v1_radio_buys__radio_buy_id__commission_rules__rule_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/commission-splits":{"put":{"tags":["cbradio","radio-buys"],"summary":"Update commission splits","description":"Update the commission splits for a radio buy.\n\n    Commission splits track how commission is distributed among agencies, consultants, etc.","operationId":"update_commission_splits_api_v1_radio_buys__radio_buy_id__commission_splits_put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__CommissionSplitsUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Commission Splits Api V1 Radio Buys  Radio Buy Id  Commission Splits Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/consultants/{consultant_id}":{"post":{"tags":["cbradio","radio-buys"],"summary":"Add consultant to radio buy","description":"Associate a consultant with a radio buy. Requires authentication.","operationId":"add_consultant_to_radio_buy_api_v1_radio_buys__radio_buy_id__consultants__consultant_id__post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Consultant To Radio Buy Api V1 Radio Buys  Radio Buy Id  Consultants  Consultant Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","radio-buys"],"summary":"Remove consultant from radio buy","description":"Remove a consultant from a radio buy. Requires authentication.","operationId":"remove_consultant_from_radio_buy_api_v1_radio_buys__radio_buy_id__consultants__consultant_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"consultant_id","in":"path","required":true,"schema":{"type":"string","title":"Consultant Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/network-items":{"put":{"tags":["cbradio","radio-buys"],"summary":"Replace wired-network items for a radio buy","description":"Replace-all sync endpoint for wired-network (StateNets, etc.) buys. Deletes every existing `radio_buy_network_item` for the given buy and inserts the provided list atomically. cbradio-ui is the single writer — see #201 for design context.","operationId":"replace_radio_buy_network_items_api_v1_radio_buys__radio_buy_id__network_items_put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__NetworkItemsReplaceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__NetworkItemsReplaceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"get":{"tags":["cbradio","radio-buys"],"summary":"List wired-network items for a radio buy","description":"Returns all `radio_buy_network_item` rows for the given buy.","operationId":"list_radio_buy_network_items_api_v1_radio_buys__radio_buy_id__network_items_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__NetworkItemOut"},"title":"Response List Radio Buy Network Items Api V1 Radio Buys  Radio Buy Id  Network Items Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/order-packets":{"get":{"tags":["cbradio","order-packets"],"summary":"Get order packets for radio buy","description":"List all order packets for a specific radio buy.","operationId":"get_order_packets_for_radio_buy_api_v1_radio_buys__radio_buy_id__order_packets_get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OrderPacketListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/order-packets/generate":{"post":{"tags":["cbradio","order-packets"],"summary":"Generate order packets","description":"Generate order packets for a radio buy, grouped by station owner/payee.","operationId":"generate_order_packets_api_v1_radio_buys__radio_buy_id__order_packets_generate_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__OrderPacketGenerate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__GenerateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/payment-status":{"patch":{"tags":["cbradio","radio-buys"],"summary":"Update payment status","description":"Mark a radio buy as paid or unpaid. Auto-sets paid_date when marking paid.","operationId":"update_payment_status_api_v1_radio_buys__radio_buy_id__payment_status_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__src__api__routes__radio_buys__PaymentStatusUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/placements":{"get":{"tags":["cbradio","radio-buys"],"summary":"List placements for a radio buy","description":"Get all station placements for a radio buy.","operationId":"list_placements_api_v1_radio_buys__radio_buy_id__placements_get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio__RadioBuyPlacement"},"title":"Response List Placements Api V1 Radio Buys  Radio Buy Id  Placements Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","radio-buys"],"summary":"Add placement to radio buy","description":"Add a station placement to a radio buy. Requires authentication.","operationId":"add_placement_api_v1_radio_buys__radio_buy_id__placements_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PlacementCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RadioBuyPlacement"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/placements/generate-defaults":{"post":{"tags":["cbradio","radio-buys"],"summary":"Generate default placements from proposal or station data","description":"Auto-generate `radio_buy_placement` records from the proposal\nline items (if the buy was converted from a proposal) or from existing\n`radio_buy_station` records as a fallback.\n\n**Use this when:** `convert_proposal()` created the buy and its station records\nbut no placements exist yet, blocking order packet generation.\n\n**cbradio#251**: each row now persists `source_rate_card_id` (resolved by\nstation+year via the materializer's priority order) so the three-axis\norder_packets gates (cbradio#239 unapproved_planning_rate, cbradio#240\nstale_rate) have the snapshot they need. Also uses ISO-week enumeration\nfrom the buy's flight dates rather than a sequential `[1..N]` placeholder.\n\n**Guards:**\n- 409 if placements already exist (use `?replace=true` to override —\n  deletes existing placements first, for the admin recovery path)\n- 404 if no source data found","operationId":"generate_default_placements_api_v1_radio_buys__radio_buy_id__placements_generate_defaults_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"replace","in":"query","required":false,"schema":{"type":"boolean","description":"cbradio#251: when true, delete existing placements before inserting new ones. Default false (preserves the 409 guard). Used by the admin recovery flow.","default":false,"title":"Replace"},"description":"cbradio#251: when true, delete existing placements before inserting new ones. Default false (preserves the 409 guard). Used by the admin recovery flow."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__GenerateDefaultsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/placements/{placement_id}":{"delete":{"tags":["cbradio","radio-buys"],"summary":"Remove placement from radio buy","description":"Remove a placement from a radio buy. Requires authentication.","operationId":"remove_placement_api_v1_radio_buys__radio_buy_id__placements__placement_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}},{"name":"placement_id","in":"path","required":true,"schema":{"type":"string","title":"Placement Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/recalculate-commissions":{"post":{"tags":["cbradio","commissions"],"summary":"Recalculate all commission amounts using the engine","description":"Run the financial calculation engine on a radio buy to recompute all\n    commission amounts.\n\n    Reads the buy's placements and commission rules, classifies station\n    spend into rural / non-rural / in-network / Salem buckets, then applies\n    each rule's rates to produce per-recipient payouts. Updates the buy's\n    financial fields (rural_gross, rural_net, non_rural_gross, non_rural_net,\n    payments_to_stations, profit) and each rule's computed_amount in the\n    database.\n\n    Returns the full financial breakdown including recipient payouts, held\n    amounts, and final profit. Requires authentication.","operationId":"recalculate_commissions_api_v1_radio_buys__radio_buy_id__recalculate_commissions_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/traffic-assets":{"get":{"tags":["cbradio","traffic-assets"],"summary":"List traffic assets for radio buy","description":"Get all audio spots and NAB form for a radio buy.","operationId":"list_traffic_assets_api_v1_radio_buys__radio_buy_id__traffic_assets_get","parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__TrafficAssetsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","traffic-assets"],"summary":"Create traffic asset","description":"Register a new traffic asset after file upload.","operationId":"create_traffic_asset_api_v1_radio_buys__radio_buy_id__traffic_assets_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__TrafficAssetCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__TrafficAssetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/traffic-assets/reorder":{"put":{"tags":["cbradio","traffic-assets"],"summary":"Reorder audio spots","description":"Bulk update rotation order for all audio spots.","operationId":"reorder_audio_spots_api_v1_radio_buys__radio_buy_id__traffic_assets_reorder_put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ReorderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio-buys/{radio_buy_id}/traffic-assets/upload-url":{"post":{"tags":["cbradio","traffic-assets"],"summary":"Get presigned upload URL","description":"Get a presigned URL for direct file upload to CBFiles.","operationId":"get_upload_url_api_v1_radio_buys__radio_buy_id__traffic_assets_upload_url_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"radio_buy_id","in":"path","required":true,"schema":{"type":"string","title":"Radio Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__UploadUrlRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__UploadUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/radio/districts/{geoid}":{"get":{"tags":["cbradio","coverage"],"summary":"Get stations covering a district by geoid","description":"Returns all stations whose coverage contours intersect with a district,\nusing the raw geoid format from districts.campaignbrain.dev.\n\n**Path Parameters:**\n- `geoid` - District geoid (e.g., MI-07, MI-SD-24, 26065)\n\n**Query Parameters:**\n- `min_coverage` - Minimum coverage percentage (0-100)\n- `in_network_only` - Only return in-network stations","operationId":"get_district_stations_by_geoid_api_v1_radio_districts__geoid__get","parameters":[{"name":"geoid","in":"path","required":true,"schema":{"type":"string","title":"Geoid"}},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"default":0,"title":"Min Coverage"}},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only in-network stations","default":false,"title":"In Network Only"},"description":"Only in-network stations"},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by am or fm","title":"Service Type"},"description":"Filter by am or fm"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"AM only: day, night, or service","title":"Contour Type"},"description":"AM only: day, night, or service"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards":{"get":{"tags":["cbradio","rate-cards"],"summary":"List rate cards","description":"List rate cards with optional filters.\n\n    **Filters:**\n    - `station_id`: Filter by station ID\n    - `year`: Filter by rate card year\n    - `rate_type`: Filter by rate type (e.g., 'Political', 'Gross')\n    - `source`: Filter by ingestion source (e.g., 'iheart', 'katz', 'amfm')\n    - `lifecycle`: Filter by `rate_card.lifecycle` — `planning`, `final`, or\n      `all` (default). `lifecycle` is the placement gate (cbradio#205,\n      cbradio#239): `planning` cards block order-packet generation until ops\n      approves via `POST /rate-cards/{id}/approve-planning`. Distinct from\n      `review_status` (parser-accuracy / QA axis).\n    - `staleness`: Filter by freshness — `fresh`, `stale`, or `all` (default).\n      Stale = not verified within 365 days (cbradio#240). Independent of\n      `lifecycle`; the order_packets gate blocks both.\n\n    **Response shape:**\n    Each row includes `review_status` (parser QA), `lifecycle` (placement\n    gate), and `is_stale` (freshness gate) as three independent fields.\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 500.","operationId":"list_rate_cards_api_v1_rate_cards_get","parameters":[{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station ID","title":"Station Id"},"description":"Filter by station ID"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station callsign(s), comma-separated and case-insensitive (cbradio#339). Use this instead of `station_id` when you have a callsign rather than an internal UUID.","examples":["KATK-AM"],"title":"Callsign"},"description":"Filter by station callsign(s), comma-separated and case-insensitive (cbradio#339). Use this instead of `station_id` when you have a callsign rather than an internal UUID."},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by year","title":"Year"},"description":"Filter by year"},{"name":"rate_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by rate type","title":"Rate Type"},"description":"Filter by rate type"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by ingestion source","title":"Source"},"description":"Filter by ingestion source"},{"name":"lifecycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(planning|final|all)$"},{"type":"null"}],"description":"Placement-gate filter on `rate_card.lifecycle`: 'planning' (blocks order packets until approved), 'final' (cleared for placement), or 'all' / omit for no filter. See cbradio#205 / cbradio#239 for the full axis definition.","title":"Lifecycle"},"description":"Placement-gate filter on `rate_card.lifecycle`: 'planning' (blocks order packets until approved), 'final' (cleared for placement), or 'all' / omit for no filter. See cbradio#205 / cbradio#239 for the full axis definition."},{"name":"staleness","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(fresh|stale|all)$"},{"type":"null"}],"description":"Freshness filter (cbradio#240): 'fresh' (verified within 365 days), 'stale' (>365 days since last verify), or 'all' / omit for no filter. Independent of `lifecycle`.","title":"Staleness"},"description":"Freshness filter (cbradio#240): 'fresh' (verified within 365 days), 'stale' (>365 days since last verify), or 'all' / omit for no filter. Independent of `lifecycle`."},{"name":"review_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Parser-QA filter on `rate_card.review_status` (cbradio#237 / cbradio#239): 'pending' / 'in_review' / 'needs_review' / 'needs_follow_up' / 'approved' / 'rejected' / 'needs_correction' / 'all' (or omit) for no filter. Independent of `lifecycle` and `staleness`.","title":"Review Status"},"description":"Parser-QA filter on `rate_card.review_status` (cbradio#237 / cbradio#239): 'pending' / 'in_review' / 'needs_review' / 'needs_follow_up' / 'approved' / 'rejected' / 'needs_correction' / 'all' (or omit) for no filter. Independent of `lifecycle` and `staleness`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","rate-cards"],"summary":"Create rate card with rates","description":"Create a new rate card with nested rates in a single atomic transaction.\n\n    **Auto-calculation:**\n    - If only `gross_rate` is provided, `net_rate` is calculated as `gross * 0.85`\n    - If only `net_rate` is provided, `gross_rate` is calculated as `net / 0.85`\n\n    **Denormalization:**\n    Station callsign and state are automatically populated on each rate.","operationId":"create_rate_card_api_v1_rate_cards_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCardCreateWithRates"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCardWithRates"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/active":{"get":{"tags":["cbradio","rate-cards"],"summary":"Get active rate cards","description":"Return rate cards that are currently active based on their effective and\n    expiration dates (effective_date <= today and expiration_date is null or\n    >= today).\n\n    Supports filtering by network membership and state.","operationId":"get_active_rate_cards_api_v1_rate_cards_active_get","parameters":[{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is In Network"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/bulk-review":{"post":{"tags":["cbradio","rate-cards"],"summary":"Bulk review multiple rate cards","description":"Submit the same review decision for multiple rate cards at once.\n\n    **Use cases:**\n    - Approve multiple AI-extracted rate cards after verification\n    - Reject a batch of outdated rate cards\n    - Mark multiple cards as needing correction\n\n    Returns a summary of successes and failures.","operationId":"bulk_review_rate_cards_api_v1_rate_cards_bulk_review_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"decision","in":"query","required":true,"schema":{"type":"string","description":"Review decision: approved, rejected, needs_correction","title":"Decision"},"description":"Review decision: approved, rejected, needs_correction"},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Review notes for all cards","title":"Notes"},"description":"Review notes for all cards"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"title":"Rate Card Ids"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkOperationResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/by-market/{market}":{"get":{"tags":["cbradio","rate-cards"],"summary":"Get rate cards by market","description":"Return rate cards for a specific market or DMA. Matches against both\n    the rate card's market field and the station's Nielsen DMA using partial\n    matching.\n\n    Supports filtering by year and network membership.","operationId":"get_rate_cards_by_market_api_v1_rate_cards_by_market__market__get","parameters":[{"name":"market","in":"path","required":true,"schema":{"type":"string","title":"Market"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}},{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is In Network"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/corrections/export":{"get":{"tags":["cbradio","rate-cards"],"summary":"Export corrections for OCR training","description":"Returns all corrections made within a date range for OCR training purposes.\n\n    **Query Parameters:**\n    - `start_date`: Start of date range (ISO 8601)\n    - `end_date`: End of date range (ISO 8601)\n    - `format`: `json` (default) or `csv`","operationId":"export_corrections_for_ocr_api_v1_rate_cards_corrections_export_get","parameters":[{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"Start date (YYYY-MM-DD)","title":"Start Date"},"description":"Start date (YYYY-MM-DD)"},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"End date (YYYY-MM-DD)","title":"End Date"},"description":"End date (YYYY-MM-DD)"},{"name":"format","in":"query","required":false,"schema":{"type":"string","description":"Export format: json or csv","default":"json","title":"Format"},"description":"Export format: json or csv"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/export":{"get":{"tags":["cbradio","rate-cards"],"summary":"Export rate cards in consumer format","description":"Export rate cards with their rates grouped by ad type and preemption\n    level.\n\n    Returns a consumer-friendly format with station info, network membership\n    status, and nested rate data. Supports filtering by network membership,\n    year, market, and active status.","operationId":"export_rate_cards_api_v1_rate_cards_export_get","parameters":[{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by network membership","title":"Is In Network"},"description":"Filter by network membership"},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by year","title":"Year"},"description":"Filter by year"},{"name":"market","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by market (partial match)","title":"Market"},"description":"Filter by market (partial match)"},{"name":"active_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only active rate cards","default":false,"title":"Active Only"},"description":"Only active rate cards"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Maximum results","default":100,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/export/{callsign}":{"get":{"tags":["cbradio","rate-cards"],"summary":"Export rate card for a station","description":"Export rate card(s) for a specific station identified by callsign.\n\n    By default returns the most recent rate card. Use `all=true` to return\n    every rate card for the station. Optionally filter by year. Includes\n    inherited rate cards from parent stations when applicable.","operationId":"export_station_rate_card_api_v1_rate_cards_export__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by year","title":"Year"},"description":"Filter by year"},{"name":"all","in":"query","required":false,"schema":{"type":"boolean","description":"Return all rate cards","default":false,"title":"All"},"description":"Return all rate cards"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/my-claims":{"get":{"tags":["cbradio","rate-cards"],"summary":"Get my claimed rate cards","description":"Returns all rate cards currently claimed by the authenticated user.","operationId":"get_my_claimed_rate_cards_api_v1_rate_cards_my_claims_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/rate-cards/qa-stats":{"get":{"tags":["cbradio","rate-cards"],"summary":"Get QA queue statistics","description":"Returns aggregate statistics about the QA queue and reviewer activity.\n\n    **Query Parameters:**\n    - `period`: `day`, `week`, `month` (default: `week`)","operationId":"get_qa_statistics_api_v1_rate_cards_qa_stats_get","parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","description":"Period: day, week, or month","default":"week","title":"Period"},"description":"Period: day, week, or month"},{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by station network membership (includes Salem)","title":"Is In Network"},"description":"Filter by station network membership (includes Salem)"},{"name":"exclude_ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exclude these ownership groups (comma-separated)","examples":["salem,iheart,katz,cumulus,townsquare,audacy,beasley,saga"],"title":"Exclude Ownership Group"},"description":"Exclude these ownership groups (comma-separated)"},{"name":"review_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by review status: pending, approved, rejected, needs_follow_up","title":"Review Status"},"description":"Filter by review status: pending, approved, rejected, needs_follow_up"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/review-queue":{"get":{"tags":["cbradio","rate-cards"],"summary":"Get rate cards pending review","description":"Returns rate cards that are pending review, ordered by submission date.\n\n    **Filters:**\n    - `status`: Filter by review status (pending, approved, rejected, needs_correction)\n    - `source`: Filter by submission source (manual, ai_extracted, imported)\n\n    **Finding one station (cbradio#339):**\n    - `q`: free-text search over station callsign and Nielsen DMA,\n      case-insensitive substring. `%` and `_` are matched literally.\n    - `callsign`: exact callsign, or a comma-separated list. Case-insensitive.\n    - `state`: 2-letter state code, or a comma-separated list.\n    - `market`: case-insensitive substring of the Nielsen DMA.\n\n    All four are applied in SQL and are reflected in `total`, so paging a\n    filtered queue is correct. They combine with AND.\n\n    Rate cards enter the review queue when they have review_status='pending'.","operationId":"get_review_queue_api_v1_rate_cards_review_queue_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by review status","examples":["pending"],"title":"Status"},"description":"Filter by review status"},{"name":"review_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias for `status` (cbradio#339). The sibling `GET /rate-cards` spells this filter `review_status`, and callers — including cbradio-ui — were sending that name here, where it was silently ignored. `status` wins if both are given.","examples":["pending"],"title":"Review Status"},"description":"Alias for `status` (cbradio#339). The sibling `GET /rate-cards` spells this filter `review_status`, and callers — including cbradio-ui — were sending that name here, where it was silently ignored. `status` wins if both are given."},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter by rate-card year (cbradio#339).","examples":[2026],"title":"Year"},"description":"Filter by rate-card year (cbradio#339)."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search callsign or Nielsen DMA (case-insensitive substring). Wildcards are matched literally.","examples":["KATK"],"title":"Q"},"description":"Search callsign or Nielsen DMA (case-insensitive substring). Wildcards are matched literally."},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact callsign(s), comma-separated. Case-insensitive.","examples":["KATK-AM"],"title":"Callsign"},"description":"Exact callsign(s), comma-separated. Case-insensitive."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"State code(s), comma-separated. Case-insensitive.","examples":["NM"],"title":"State"},"description":"State code(s), comma-separated. Case-insensitive."},{"name":"scope","in":"query","required":false,"schema":{"type":"string","description":"cbradio#377. `in_scope` (default) | `out_of_scope` | `all`.\n\n**The default is not a filter, it is the honest count.** The queue held 1,505 pending cards against 2 human verifications ever recorded; 1,154 of them (77%) are for stations we have never bought from and do not target, so counting them made the real work — 351 cards — invisible inside a number nobody could ever drain.\n\nOut of scope is not rejected and not low priority: nobody is expected to look at it. It says nothing about whether the rate can be quoted — an out-of-scope card still prices a plan and obeys every selection rule. Re-run `apply_review_scope.py` when the target states change.","examples":["in_scope"],"default":"in_scope","title":"Scope"},"description":"cbradio#377. `in_scope` (default) | `out_of_scope` | `all`.\n\n**The default is not a filter, it is the honest count.** The queue held 1,505 pending cards against 2 human verifications ever recorded; 1,154 of them (77%) are for stations we have never bought from and do not target, so counting them made the real work — 351 cards — invisible inside a number nobody could ever drain.\n\nOut of scope is not rejected and not low priority: nobody is expected to look at it. It says nothing about whether the rate can be quoted — an out-of-scope card still prices a plan and obeys every selection rule. Re-run `apply_review_scope.py` when the target states change."},{"name":"market","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Nielsen DMA substring, case-insensitive.","examples":["Albuquerque"],"title":"Market"},"description":"Nielsen DMA substring, case-insensitive."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Include only these sources (comma-separated)","examples":["ai_extracted"],"title":"Source"},"description":"Include only these sources (comma-separated)"},{"name":"exclude_source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exclude these sources (comma-separated)","examples":["salem,katz,iheart,cumulus,townsquare,audacy,beasley,saga"],"title":"Exclude Source"},"description":"Exclude these sources (comma-separated)"},{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by station network membership (includes Salem per #132)","title":"Is In Network"},"description":"Filter by station network membership (includes Salem per #132)"},{"name":"ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Include only these ownership groups (comma-separated)","examples":["salem,iheart"],"title":"Ownership Group"},"description":"Include only these ownership groups (comma-separated)"},{"name":"exclude_ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exclude these ownership groups (comma-separated)","examples":["salem,iheart,katz,cumulus,townsquare,audacy,beasley,saga"],"title":"Exclude Ownership Group"},"description":"Exclude these ownership groups (comma-separated)"},{"name":"order_by","in":"query","required":false,"schema":{"type":"string","description":"`submitted_at` (default, arrival order) or `risk` (cbradio#371). Risk ordering scores every card matching the filters against the same checks that keep a bad number out of a quote — zero and sentinel prices, rates a fraction of their class benchmark, :30/:60 conflicts, an unreadable source document, a card holding nothing buyable — and returns `risk_score` and `risk_findings` on each item. Opt-in because it reads every matching card's rates, which arrival order does not.","examples":["risk"],"default":"submitted_at","title":"Order By"},"description":"`submitted_at` (default, arrival order) or `risk` (cbradio#371). Risk ordering scores every card matching the filters against the same checks that keep a bad number out of a quote — zero and sentinel prices, rates a fraction of their class benchmark, :30/:60 conflicts, an unreadable source document, a card holding nothing buyable — and returns `risk_score` and `risk_findings` on each item. Opt-in because it reads every matching card's rates, which arrival order does not."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Maximum results","default":50,"title":"Limit"},"description":"Maximum results"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Results to skip","default":0,"title":"Offset"},"description":"Results to skip"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/summary":{"get":{"tags":["cbradio","rate-cards"],"summary":"Get rate card summary statistics","description":"Returns aggregate statistics about rate cards in the system.\n\n    **Included metrics:**\n    - Total rate cards and rates\n    - Rate cards by year\n    - Rate cards by review status\n    - Rate range statistics (min/max/avg)\n    - Coverage statistics (stations with rate cards)\n\n    **`state`** (optional): restrict every aggregation to one 2-letter state.\n    cbradio-ui#135/#140: the SRM page calls this once per state; honoring `state`\n    turns the ~6.5s full-database scan into a fast indexed lookup and returns the\n    correct per-state numbers (previously every state got the global totals).\n    Omit `state` for the global summary (unchanged behavior).\n\n    Results are cached briefly (5 min) per state.","operationId":"get_rate_card_summary_api_v1_rate_cards_summary_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional 2-letter state code to scope the summary (e.g. MI).","title":"State"},"description":"Optional 2-letter state code to scope the summary (e.g. MI)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}":{"get":{"tags":["cbradio","rate-cards"],"summary":"Get rate card with rates","description":"Get a rate card by ID with all associated rates embedded.\n\n    Returns the rate card metadata plus an array of rate objects.","operationId":"get_rate_card_api_v1_rate_cards__rate_card_id__get","parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCardWithRates"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","rate-cards"],"summary":"Partial update rate card","description":"Update rate card metadata (not rates). Only provided fields are updated.\n\n    To update rates, use the bulk rate endpoints:\n    - `PATCH /rate-cards/{id}/rates` - Update multiple rates\n    - `POST /rate-cards/{id}/rates` - Add rates\n    - `DELETE /rate-cards/{id}/rates` - Remove rates","operationId":"update_rate_card_api_v1_rate_cards__rate_card_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCardUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCardWithRates"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","rate-cards"],"summary":"Delete rate card","description":"Delete a rate card and all associated rates (cascade delete).\n\n    **Warning:** This operation is irreversible.","operationId":"delete_rate_card_api_v1_rate_cards__rate_card_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}/approve-planning":{"post":{"tags":["cbradio","rate-cards"],"summary":"Approve a PLANNING rate card for committed pricing","description":"Flips a rate_card with lifecycle='planning' into the approved state, recording who approved it, when, and an optional note. Once approved, the card is treated as FINAL by the order-packet generate gate — placements priced from it no longer block packet generation. This is the ops-facing approval path per cbradio#205.","operationId":"approve_planning_rate_card_api_v1_rate_cards__rate_card_id__approve_planning_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__PlanningApprovalBody"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PlanningApprovalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}/claim":{"post":{"tags":["cbradio","rate-cards"],"summary":"Claim rate card for review","description":"Assigns the rate card to the current user for review, preventing other users\n    from working on it simultaneously.\n\n    **Business Rules:**\n    - Only rate cards with `review_status: pending` or `in_review` (unclaimed) can be claimed\n    - A user can only have up to 5 rate cards claimed at once\n    - Claiming a rate card updates `review_status` to `in_review`","operationId":"claim_rate_card_api_v1_rate_cards__rate_card_id__claim_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}/qa-detail":{"get":{"tags":["cbradio","rate-cards"],"summary":"Get rate card detail for QA review","description":"Returns complete rate card data including extracted rates, source document info,\n    and any existing warnings or corrections for QA review.","operationId":"get_rate_card_qa_detail_api_v1_rate_cards__rate_card_id__qa_detail_get","parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}/rates":{"post":{"tags":["cbradio","rate-cards"],"summary":"Add rates to rate card","description":"Add multiple rates to an existing rate card in a single atomic transaction.\n\n    All rates will inherit the station_id from the rate card.\n\n    **Auto-calculation:**\n    - If only `gross_rate` provided, `net_rate = gross * 0.85`\n    - If only `net_rate` provided, `gross_rate = net / 0.85`","operationId":"bulk_create_rates_api_v1_rate_cards__rate_card_id__rates_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkRateCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkOperationResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","rate-cards"],"summary":"Bulk update rates","description":"Update multiple rates by ID. Allows partial success - failed updates\n    are reported in the errors array.\n\n    Each update object must include the rate `id` plus fields to update.","operationId":"bulk_update_rates_api_v1_rate_cards__rate_card_id__rates_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkRateUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkOperationResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","rate-cards"],"summary":"Bulk delete rates","description":"Delete multiple rates by ID from a rate card.\n\n    Only rates belonging to the specified rate card will be deleted.","operationId":"bulk_delete_rates_api_v1_rate_cards__rate_card_id__rates_delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkRateDelete"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkOperationResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}/review":{"post":{"tags":["cbradio","rate-cards"],"summary":"Submit review decision for rate card","description":"Submit a review decision for a rate card (approve, reject, or request corrections).\n\n    **Decision values:**\n    - `approved`: Rate card is approved for use\n    - `rejected`: Rate card is rejected (with reason in notes)\n    - `needs_correction`: Rate card needs corrections before approval\n\n    **What `decision='approved'` does (cbradio#243):**\n    Approval flips three axes atomically on the same human action — it's\n    one ops gesture, three signals:\n      - `review_status: → 'approved'` (parser QA confirmed)\n      - `lifecycle: 'planning' → 'final'` (placement gate cleared) — only\n        when currently `'planning'`. `'approved_planning'` is preserved so\n        the iHeart ops-approval flow isn't overwritten.\n      - `last_verified_at: → now()` (freshness timer reset per cbradio#240)\n    For `rejected` / `needs_correction`, only `review_status` changes.\n\n    **Corrections:**\n    When decision is `needs_correction`, you can provide a list of corrections\n    specifying which fields need to be fixed.\n\n    This endpoint creates a review history record for audit purposes.\n\n    **`rates_written` (cbradio#309):**\n    The response carries `rates_written` — the number of `rate` rows this call\n    created or updated from `approved_rates`. It is `0` for a decision other\n    than `approved`, and `0` when no `approved_rates` were supplied.\n\n    **Approved rates are validated BEFORE the decision is applied.** An unknown\n    `daypart_id` or an unresolvable station is a `400` that changes nothing —\n    the card keeps its previous review status and no rates are written. This\n    used to be swallowed, leaving the card marked `approved` with none of its\n    rates persisted and a `200` that looked like success. Validation covers the\n    whole payload, so one bad daypart rejects the entire approval; fix it and\n    re-submit.","operationId":"review_rate_card_api_v1_rate_cards__rate_card_id__review_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}},{"name":"decision","in":"query","required":true,"schema":{"type":"string","description":"Review decision: approved, rejected, needs_correction","examples":["approved"],"title":"Decision"},"description":"Review decision: approved, rejected, needs_correction"},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Review notes","title":"Notes"},"description":"Review notes"}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ReviewApprovalBody"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}/review-context":{"get":{"tags":["cbradio","rate-cards"],"summary":"Structured review context for a rate card (cbradio#237)","description":"Return everything a human or MCP agent needs to make a review decision on\n    a single rate card, in one structured response:\n\n    - `card` — metadata (id, callsign, year, source, axis state, confidence)\n    - `rates` — full rate rows attached to this card\n    - `source_document` — OCR text + extraction status for the underlying doc\n    - `siblings.same_station_year` — other cards on the same (station, year)\n       with their rate counts and a sample of rates (most useful for diff\n       reasoning across re-extractions or cross-source comparisons)\n    - `siblings.same_document` — other cards extracted from the same document\n       (rare, but happens when extraction was re-run and produced a new card)\n    - `signals` — derived flags an agent can short-circuit on: rate_count,\n       extraction_confidence, all_rates_null_slot, any_zero_gross_rate,\n       siblings_available\n\n    Read-only. To act on the decision, call\n    `POST /api/v1/rate-cards/{rate_card_id}/review?decision=approved|rejected|needs_correction`\n    (cbradio#243 — atomic three-axis flip on approval).\n\n    Designed to feed an MCP review tool but works directly from curl too.","operationId":"get_rate_card_review_context_api_v1_rate_cards__rate_card_id__review_context_get","parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}/unclaim":{"post":{"tags":["cbradio","rate-cards"],"summary":"Unclaim rate card","description":"Releases the rate card so another user can claim it.\n\n    **Business Rules:**\n    - Users can only unclaim rate cards they have claimed (unless admin)\n    - Unclaiming sets `review_status` back to `pending`\n    - The reason field is optional but recommended for audit purposes","operationId":"unclaim_rate_card_api_v1_rate_cards__rate_card_id__unclaim_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}},{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Reason for unclaiming","title":"Reason"},"description":"Reason for unclaiming"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}/verify":{"post":{"tags":["cbradio","rate-cards"],"summary":"Mark a rate card as freshly verified","description":"Records that a human has confirmed the rate card is still accurate. Updates `last_verified_at` to now() and resets the staleness gate. Distinct from `/approve-planning` — verification is the freshness axis (cbradio#240), placement approval is the lifecycle axis (cbradio#205). The order_packets generator blocks both stale and unapproved-planning rates independently.","operationId":"verify_rate_card_api_v1_rate_cards__rate_card_id__verify_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__VerifyRateCardBody"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__VerifyRateCardResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}/warnings":{"get":{"tags":["cbradio","rate-cards"],"summary":"Get warnings for rate card","description":"Returns all warnings for a rate card, including acknowledgment status.","operationId":"get_rate_card_warnings_api_v1_rate_cards__rate_card_id__warnings_get","parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-cards/{rate_card_id}/warnings/{warning_id}/acknowledge":{"post":{"tags":["cbradio","rate-cards"],"summary":"Acknowledge warning","description":"Marks a warning as acknowledged by the reviewer.\n\n    **Business Rules:**\n    - Only the user who claimed the rate card can acknowledge warnings\n    - Error-level warnings must be acknowledged before the rate card can be approved\n    - Warning-level and info-level warnings can be acknowledged but are not required","operationId":"acknowledge_warning_api_v1_rate_cards__rate_card_id__warnings__warning_id__acknowledge_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_card_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Card Id"}},{"name":"warning_id","in":"path","required":true,"schema":{"type":"string","title":"Warning Id"}},{"name":"note","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Acknowledgment note","title":"Note"},"description":"Acknowledgment note"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-corrections":{"post":{"tags":["cbradio","Rate Corrections"],"summary":"Create a rate correction","description":"Create a new rate correction record.\n\n    All authenticated users can create corrections with status 'pending_review'.\n    Only admins can create corrections with status 'verified' (immediate approval).","operationId":"create_rate_correction_api_v1_rate_corrections_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCorrectionCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCorrection"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"get":{"tags":["cbradio","Rate Corrections"],"summary":"List rate corrections","description":"List rate corrections with optional filters.","operationId":"list_rate_corrections_api_v1_rate_corrections_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"station","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station callsign","title":"Station"},"description":"Filter by station callsign"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status: pending_review, verified, rejected","title":"Status"},"description":"Filter by status: pending_review, verified, rejected"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-corrections/{correction_id}":{"get":{"tags":["cbradio","Rate Corrections"],"summary":"Get a rate correction","description":"Get details of a specific rate correction.","operationId":"get_rate_correction_api_v1_rate_corrections__correction_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"correction_id","in":"path","required":true,"schema":{"type":"string","title":"Correction Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-corrections/{correction_id}/reject":{"post":{"tags":["cbradio","Rate Corrections"],"summary":"Reject a rate correction","description":"Reject a pending rate correction. Admin only.","operationId":"reject_rate_correction_api_v1_rate_corrections__correction_id__reject_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"correction_id","in":"path","required":true,"schema":{"type":"string","title":"Correction Id"}},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Rejection reason","title":"Notes"},"description":"Rejection reason"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rate-corrections/{correction_id}/verify":{"post":{"tags":["cbradio","Rate Corrections"],"summary":"Verify a rate correction","description":"Verify (approve) a pending rate correction. Admin only.","operationId":"verify_rate_correction_api_v1_rate_corrections__correction_id__verify_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"correction_id","in":"path","required":true,"schema":{"type":"string","title":"Correction Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCorrectionVerify"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rates":{"get":{"tags":["cbradio","rates"],"summary":"Query political advertising rates","description":"Search and filter political advertising rates across all stations.\n\n    \n**Rate Fields:**\n- `ad_type`: Type of ad - `candidate` (LUR protected), `issue` (PAC/advocacy), `all`\n- `rate_context`: `in_window` (during LUR period), `out_of_window`, `all`\n- `preemption`: `non_preemptible` (guaranteed), `preemptible` (can be bumped)\n- `slot_days`: Day pattern - `m-f`, `m-sat`, `sat-sun`, `sat`, `sun`, `all`\n- `duration_seconds`: Spot length - 15, 30, 60, 90, or 120 seconds\n- `gross_rate`: Rate before agency commission (15%)\n- `net_rate`: Rate after commission (gross * 0.85)\n\n\n    **Example queries:**\n    - `/api/v1/rates?state=TX&ad_type=candidate` - Texas candidate rates\n    - `/api/v1/rates?station=KAAA-AM&duration=60` - 60-second spots for KAAA-AM (exact match)\n    - `/api/v1/rates?callsign=KAAA&duration=60` - 60-second spots matching KAAA (partial match)\n    - `/api/v1/rates?year=2024&preemption=non_preemptible` - 2024 guaranteed rates\n    - `/api/v1/rates?min_rate=10&max_rate=50` - Rates between $10-$50\n\n    **Parameter Aliases:**\n    - `station` (exact match) or `callsign` (partial match)\n    - `campaign_type` or `ad_type`\n\n    **Pagination:**\n    Use `limit` and `offset` for pagination. Default limit is 100, max is 1000.","operationId":"list_rates_api_v1_rates_get","parameters":[{"name":"station","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by exact station callsign","examples":["KAAA-AM"],"title":"Station"},"description":"Filter by exact station callsign"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station callsign (partial match)","examples":["KAAA"],"title":"Callsign"},"description":"Filter by station callsign (partial match)"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state abbreviation","examples":["TX"],"title":"State"},"description":"Filter by state abbreviation"},{"name":"dma","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by Nielsen DMA market (partial match)","examples":["Dallas"],"title":"Dma"},"description":"Filter by Nielsen DMA market (partial match)"},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Ad type: candidate, issue, or all","examples":["candidate"],"title":"Ad Type"},"description":"Ad type: candidate, issue, or all"},{"name":"campaign_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias for ad_type: candidate, issue","examples":["candidate"],"title":"Campaign Type"},"description":"Alias for ad_type: candidate, issue"},{"name":"duration","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Spot duration in seconds: 15, 30, 60, 90, 120","examples":[60],"title":"Duration"},"description":"Spot duration in seconds: 15, 30, 60, 90, 120"},{"name":"preemption","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Preemption status: non_preemptible, preemptible","examples":["non_preemptible"],"title":"Preemption"},"description":"Preemption status: non_preemptible, preemptible"},{"name":"pricing","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pricing type: gross, net (filters to rates with that value)","examples":["net"],"title":"Pricing"},"description":"Pricing type: gross, net (filters to rates with that value)"},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Rate card year","examples":[2024],"title":"Year"},"description":"Rate card year"},{"name":"min_rate","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum gross or net rate","examples":[10.0],"title":"Min Rate"},"description":"Minimum gross or net rate"},{"name":"max_rate","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Maximum gross or net rate","examples":[50.0],"title":"Max Rate"},"description":"Maximum gross or net rate"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum results to return","default":100,"title":"Limit"},"description":"Maximum results to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip","default":0,"title":"Offset"},"description":"Number of results to skip"},{"name":"include_diagnostics","in":"query","required":false,"schema":{"type":"boolean","description":"Include diagnostic info when results are empty","default":false,"title":"Include Diagnostics"},"description":"Include diagnostic info when results are empty"},{"name":"include_simulcast","in":"query","required":false,"schema":{"type":"boolean","description":"If station has no rates, include rates from simulcast partner","default":true,"title":"Include Simulcast"},"description":"If station has no rates, include rates from simulcast partner"}],"responses":{"200":{"description":"List of rate objects matching filters","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","rates"],"summary":"Create a single rate","description":"Create a new rate entry.\n\n    **Auto-calculation:**\n    - If only `gross_rate` provided, `net_rate = gross * 0.85`\n    - If only `net_rate` provided, `gross_rate = net / 0.85`\n\n    **Denormalization:**\n    Station callsign and state are automatically populated from the station.","operationId":"create_rate_api_v1_rates_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rates/benchmarks":{"get":{"tags":["cbradio","rates"],"summary":"Power-class benchmark — median issue :60/:30 by (state, power bucket)","description":"Median ISSUE advertising rate (`:60` and `:30`), grouped by US state and power\n    bucket (`lt2kw` <2kW · `2to10kw` 2–10kW · `10to50kw` 10–50kW · `50kwplus` 50kW+),\n    computed live from the rate table. Feeds the RCI Planning Budget estimate mode\n    (`shared/planningBudget` `BenchmarkLookup`).\n\n    **Always resolves** a number for any real US state via per-metric progressive\n    widening: `state+bucket` → `state` → `bucket` (national) → `national`. `:60` and\n    `:30` widen independently (a bucket may have one but not the other). `basis`\n    reports which level each value came from; `n` is the state+bucket sample size.\n\n    `GET /api/v1/rates/benchmarks?states=AL,MS` →\n    `{ \"AL\": { \"lt2kw\": {\"sec60\": 23, \"sec30\": 17, \"basis\": {...}, \"n\": 12}, ... }, \"MS\": {...} }`","operationId":"rate_benchmarks_api_v1_rates_benchmarks_get","parameters":[{"name":"states","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated state abbreviations, e.g. 'AL,MS'.","title":"States"},"description":"Comma-separated state abbreviations, e.g. 'AL,MS'."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rates/by-ad-type":{"get":{"tags":["cbradio","rates"],"summary":"List rates grouped by ad type","description":"Return rate statistics broken down by ad type (e.g., `candidate`, `issue`).\n\n    Each row includes the rate count, average gross/net rate, and the\n    min/max gross rate for that ad type.","operationId":"rates_by_ad_type_api_v1_rates_by_ad_type_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/rates/by-duration":{"get":{"tags":["cbradio","rates"],"summary":"List rates grouped by spot duration","description":"Return rate statistics broken down by spot duration in seconds\n    (e.g., 15, 30, 60, 90, 120).\n\n    Each row includes the rate count and average gross/net rate for that\n    duration bucket.","operationId":"rates_by_duration_api_v1_rates_by_duration_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/rates/by-state":{"get":{"tags":["cbradio","rates"],"summary":"List rates grouped by state","description":"Return rate counts and average gross/net rates for each US state.\n\n    Each row includes the number of rates, number of unique stations,\n    and the average gross and net rate for that state.\n\n    Results are sorted alphabetically by state abbreviation.","operationId":"rates_by_state_api_v1_rates_by_state_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/rates/count":{"get":{"tags":["cbradio","rates"],"summary":"Count rates matching filters","description":"Return the total number of rate records that match the given filter criteria.\n\n    Supports filtering by station callsign (exact or partial match), state,\n    ad type (or its alias `campaign_type`), spot duration, and rate card year.","operationId":"count_rates_api_v1_rates_count_get","parameters":[{"name":"station","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by exact station callsign","title":"Station"},"description":"Filter by exact station callsign"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station callsign (partial match)","title":"Callsign"},"description":"Filter by station callsign (partial match)"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ad Type"}},{"name":"campaign_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alias for ad_type","title":"Campaign Type"},"description":"Alias for ad_type"},{"name":"duration","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rates/coverage":{"get":{"tags":["cbradio","rates"],"summary":"Political-rate coverage / do-not-quote roster (cbradio#309)","description":"The fall-through guard. Classifies each station's **political-rate\n    coverage** so the proposal / QA surfaces can flag stations that would\n    otherwise be priced off stale `ad_type='all'` blended ROS rows (the\n    KQFN-AM false-overcharge bug).\n\n    Per-station verdict (`src/lib/rate_coverage.py`):\n    - **`ok`** — a typed candidate/issue rate exists for the current cycle.\n    - **`stale`** — newest `rate_card_year` < `election_year` (do-not-quote).\n    - **`untyped`** — current-year rows but only `ad_type='all'` (verify first).\n    - **`no_rates`** — no quotable rate rows (do-not-quote).\n\n    Quotability mirrors the proposal builder: only rows with `gross_rate > 0`\n    count. This endpoint is **read-only** and does not change rate selection —\n    it is the signal the front end / backfill prioritization consume.\n\n    Returns portfolio counts, a stale-by-year breakdown, and a sample roster\n    (filter via `status`).","operationId":"rate_coverage_api_v1_rates_coverage_get","parameters":[{"name":"network","in":"query","required":false,"schema":{"type":"string","pattern":"^(in|out|all)$","description":"Network scope: 'in' (in-network, default), 'out', 'all'.","default":"in","title":"Network"},"description":"Network scope: 'in' (in-network, default), 'out', 'all'."},{"name":"election_year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":2100,"minimum":2000},{"type":"null"}],"description":"Freshness bar — a newest card year below this is 'stale'. Defaults to the current calendar year.","title":"Election Year"},"description":"Freshness bar — a newest card year below this is 'stale'. Defaults to the current calendar year."},{"name":"status","in":"query","required":false,"schema":{"type":"string","pattern":"^(do_not_quote|stale|untyped|no_rates|ok|all)$","description":"Which verdicts to include in the sample roster: 'do_not_quote' (stale + no_rates, default), 'stale', 'untyped', 'no_rates', 'ok', or 'all'. Counts always cover every station.","default":"do_not_quote","title":"Status"},"description":"Which verdicts to include in the sample roster: 'do_not_quote' (stale + no_rates, default), 'stale', 'untyped', 'no_rates', 'ok', or 'all'. Counts always cover every station."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional 2-letter state filter.","title":"State"},"description":"Optional 2-letter state filter."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"description":"Max stations in the sample.","default":100,"title":"Limit"},"description":"Max stations in the sample."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rates/summary":{"get":{"tags":["cbradio","rates"],"summary":"Get rate statistics summary","description":"Returns aggregate statistics for all rates in the database.\n\n    **Included metrics:**\n    - Total rate count\n    - Unique stations and states\n    - Min/max/avg gross rates\n    - Min/max/avg net rates\n\n    This endpoint is useful for AI systems to quickly understand the rate data scope.","operationId":"rate_summary_api_v1_rates_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/rates/years":{"get":{"tags":["cbradio","rates"],"summary":"List rate card years","description":"Return every rate card year present in the database along with the number\n    of rate records in each year.\n\n    Results are sorted by year descending (most recent first).","operationId":"list_years_api_v1_rates_years_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/rates/{rate_id}":{"get":{"tags":["cbradio","rates"],"summary":"Get rate by ID","description":"Retrieve a single rate record by its unique ID.\n\n    The response includes all rate fields plus a computed `daypart_id`\n    derived from the slot start/end times or slot name.\n\n    Returns 404 if the rate ID does not exist.","operationId":"get_rate_api_v1_rates__rate_id__get","parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"put":{"tags":["cbradio","rates"],"summary":"Replace a rate","description":"Replace a rate entirely with new data.\n\n    All fields must be provided (except optional fields).\n    For partial updates, use PATCH instead.","operationId":"replace_rate_api_v1_rates__rate_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","rates"],"summary":"Partial update a rate","description":"Update specific fields of a rate. Only provided fields are updated.\n\n    For specialized updates, consider using:\n    - `PATCH /rates/{id}/pricing` - Update only gross/net rates\n    - `PATCH /rates/{id}/time-slot` - Update only time slot fields\n    - `PATCH /rates/{id}/classification` - Update only ad_type, rate_context, preemption","operationId":"update_rate_api_v1_rates__rate_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__RateUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","rates"],"summary":"Delete a rate","description":"Delete a rate by ID. This operation is irreversible.","operationId":"delete_rate_api_v1_rates__rate_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rates/{rate_id}/classification":{"patch":{"tags":["cbradio","rates"],"summary":"Adjust classification only","description":"Update only the classification fields (ad_type, rate_context, preemption).\n\n    This endpoint is optimized for AI correction workflows that need to fix\n    only the rate classification without touching pricing or time slots.\n\n    **Fields:**\n    - `ad_type`: candidate, issue, or all\n    - `rate_context`: in_window, out_of_window, or all\n    - `preemption`: non_preemptible, preemptible, preemptible_with_notice, or all","operationId":"adjust_classification_api_v1_rates__rate_id__classification_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ClassificationAdjustment"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rates/{rate_id}/pricing":{"patch":{"tags":["cbradio","rates"],"summary":"Adjust pricing only","description":"Update only the pricing fields (gross_rate, net_rate).\n\n    **Auto-calculation:**\n    - If only `gross_rate` provided, `net_rate = gross * 0.85`\n    - If only `net_rate` provided, `gross_rate = net / 0.85`\n\n    This endpoint is optimized for AI correction workflows that need to fix\n    only the rate values without touching other fields.","operationId":"adjust_pricing_api_v1_rates__rate_id__pricing_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PricingAdjustment"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/rates/{rate_id}/time-slot":{"patch":{"tags":["cbradio","rates"],"summary":"Adjust time slot only","description":"Update only the time slot fields (slot_days, slot_start_time, slot_end_time, slot_name).\n\n    This endpoint is optimized for AI correction workflows that need to fix\n    only the time slot information without touching pricing or classification.","operationId":"adjust_time_slot_api_v1_rates__rate_id__time_slot_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__TimeSlotAdjustment"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/simulcast-groups":{"post":{"tags":["cbradio","simulcast-groups"],"summary":"Create simulcast group","description":"Create a new simulcast group for stations that share programming.","operationId":"create_simulcast_group_api_v1_simulcast_groups_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SimulcastGroupCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SimulcastGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"get":{"tags":["cbradio","simulcast-groups"],"summary":"List simulcast groups","description":"Retrieve a paginated list of simulcast groups with optional search.","operationId":"list_simulcast_groups_api_v1_simulcast_groups_get","parameters":[{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by name or callsign","title":"Search"},"description":"Search by name or callsign"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/simulcast-groups/by-callsign/{callsign}":{"get":{"tags":["cbradio","simulcast-groups"],"summary":"Find simulcast group by callsign","description":"Returns the simulcast group containing the specified callsign, or 404 if not in any group.","operationId":"get_simulcast_group_by_callsign_api_v1_simulcast_groups_by_callsign__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/simulcast-groups/{group_id}":{"get":{"tags":["cbradio","simulcast-groups"],"summary":"Get simulcast group","description":"Retrieve a single simulcast group by ID.","operationId":"get_simulcast_group_api_v1_simulcast_groups__group_id__get","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SimulcastGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"put":{"tags":["cbradio","simulcast-groups"],"summary":"Update simulcast group","description":"Update an existing simulcast group.","operationId":"update_simulcast_group_api_v1_simulcast_groups__group_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SimulcastGroupUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SimulcastGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","simulcast-groups"],"summary":"Delete simulcast group","description":"Delete a simulcast group.","operationId":"delete_simulcast_group_api_v1_simulcast_groups__group_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/srm/alerts":{"get":{"tags":["cbradio","srm"],"summary":"List SRM alerts","description":"List system-generated alerts for stations needing attention.\n\n    **Alert Types:**\n    - `ownership_change`: Station ownership changed recently\n    - `agreement_expiring`: Agreement expires within 30 days\n    - `no_contact`: Active station with no contact in 90+ days\n\n    **Filters:**\n    - `dismissed`: Include dismissed alerts (default: false)\n    - `alert_type`: Filter by specific alert type\n    - `station_callsign`: Filter by station","operationId":"list_alerts_api_v1_srm_alerts_get","parameters":[{"name":"dismissed","in":"query","required":false,"schema":{"type":"boolean","description":"Include dismissed alerts","default":false,"title":"Dismissed"},"description":"Include dismissed alerts"},{"name":"alert_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by alert type","title":"Alert Type"},"description":"Filter by alert type"},{"name":"station_callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station","title":"Station Callsign"},"description":"Filter by station"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Results per page","default":50,"title":"Limit"},"description":"Results per page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__AlertList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/srm/alerts/{alert_id}/dismiss":{"post":{"tags":["cbradio","srm"],"summary":"Dismiss an alert","description":"Mark an alert as dismissed. Dismissed alerts won't appear in the default list.","operationId":"dismiss_alert_api_v1_srm_alerts__alert_id__dismiss_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SRMAlert"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/srm/dashboard/coverage":{"get":{"tags":["cbradio","srm"],"summary":"Get coverage analytics by state","description":"Returns coverage analysis by state, showing:\n    - Total stations in each state\n    - In-network stations\n    - Coverage percentage\n    - Prospects available\n    - At-risk stations needing attention\n\n    Useful for identifying expansion opportunities and risk areas.","operationId":"get_coverage_by_state_api_v1_srm_dashboard_coverage_get","parameters":[{"name":"min_stations","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Minimum stations to include state","default":1,"title":"Min Stations"},"description":"Minimum stations to include state"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/srm/dashboard/coverage/dma":{"get":{"tags":["cbradio","srm"],"summary":"Get coverage analytics by DMA","description":"Returns coverage analysis by Nielsen DMA (Designated Market Area).\n    Similar to state coverage but grouped by media market.","operationId":"get_coverage_by_dma_api_v1_srm_dashboard_coverage_dma_get","parameters":[{"name":"min_stations","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Minimum stations to include DMA","default":1,"title":"Min Stations"},"description":"Minimum stations to include DMA"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/srm/dashboard/metrics":{"get":{"tags":["cbradio","srm"],"summary":"Get SRM dashboard metrics","description":"Returns summary metrics for the SRM dashboard.\n\n    **Station Metrics:**\n    - Total stations in system\n    - Breakdown by lifecycle state\n    - In-network vs out-of-network counts\n\n    **Task Metrics:**\n    - Total pending/in-progress tasks\n    - Tasks due today\n    - Overdue tasks\n    - Breakdown by task type\n\n    **Contact Metrics:**\n    - Contacts logged today\n    - Contacts logged this week\n    - Contacts logged this month","operationId":"get_dashboard_metrics_api_v1_srm_dashboard_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/srm/signals/{callsign}":{"get":{"tags":["cbradio","srm"],"summary":"Get computed signals for a station","description":"Returns computed signals and health metrics for a specific station.\n\n    **Signals:**\n    - `priority_score`: Overall outreach priority (0-100, higher = more urgent)\n    - `relationship_health`: Health of relationship (good/warning/critical)\n    - `outreach_urgency`: How urgently this station needs contact\n    - `revenue_risk`: Risk of losing revenue from this station\n\n    Signals are computed on-demand based on current SRM data.","operationId":"get_station_signals_api_v1_srm_signals__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/statenets/network-buys":{"post":{"tags":["cbradio","statenets"],"summary":"Create Network Buy","description":"Create a new StateNets network buy with commission calculations.","operationId":"create_network_buy_api_v1_statenets_network_buys_post","security":[{"OAuth2PasswordBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__NetworkBuyCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"get":{"tags":["cbradio","statenets"],"summary":"List Network Buys","description":"List StateNets network buys with optional filters.","operationId":"list_network_buys_api_v1_statenets_network_buys_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proposal_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proposal Id"}},{"name":"radio_buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"}},{"name":"network_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Network Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/statenets/network-buys/{buy_id}":{"get":{"tags":["cbradio","statenets"],"summary":"Get Network Buy","description":"Get a single StateNets network buy with network details.","operationId":"get_network_buy_api_v1_statenets_network_buys__buy_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","statenets"],"summary":"Update Network Buy","description":"Update status, flight_start, or flight_end on a network buy.","operationId":"update_network_buy_api_v1_statenets_network_buys__buy_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__NetworkBuyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/statenets/networks":{"get":{"tags":["cbradio","statenets"],"summary":"List Networks","description":"List StateNets networks with optional filters.","operationId":"list_networks_api_v1_statenets_networks_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by 2-letter state code","title":"State"},"description":"Filter by 2-letter state code"},{"name":"network_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by network type (wired/unwired)","title":"Network Type"},"description":"Filter by network type (wired/unwired)"},{"name":"product_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by product type","title":"Product Type"},"description":"Filter by product type"},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active status","title":"Is Active"},"description":"Filter by active status"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/statenets/networks/{network_id}":{"get":{"tags":["cbradio","statenets"],"summary":"Get Network","description":"Get a single StateNets network with its affiliates inline.","operationId":"get_network_api_v1_statenets_networks__network_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"network_id","in":"path","required":true,"schema":{"type":"string","title":"Network Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"put":{"tags":["cbradio","statenets"],"summary":"Update Network","description":"Update editable fields on a StateNets network.","operationId":"update_network_api_v1_statenets_networks__network_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"network_id","in":"path","required":true,"schema":{"type":"string","title":"Network Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__NetworkUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/statenets/networks/{network_id}/affiliates":{"get":{"tags":["cbradio","statenets"],"summary":"List Affiliates","description":"List affiliates for a network (paginated).","operationId":"list_affiliates_api_v1_statenets_networks__network_id__affiliates_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"network_id","in":"path","required":true,"schema":{"type":"string","title":"Network Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/statenets/networks/{network_id}/overlaps":{"get":{"tags":["cbradio","statenets"],"summary":"Get Network Overlaps","description":"Return affiliates that are matched to in-network stations or signed agreements.","operationId":"get_network_overlaps_api_v1_statenets_networks__network_id__overlaps_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"network_id","in":"path","required":true,"schema":{"type":"string","title":"Network Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/statenets/networks/{network_id}/scores":{"get":{"tags":["cbradio","statenets"],"summary":"Score all affiliates in a StateNets network","description":"Scores all affiliates in a network using the same 0-16 scale as individual stations. Returns per-affiliate scores and aggregate averages. Affiliates with excluded formats (score=-1) are included in the response but excluded from aggregate averages. Unmatched affiliates get format-only scoring.","operationId":"get_network_scores_api_v1_statenets_networks__network_id__scores_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"network_id","in":"path","required":true,"schema":{"type":"string","title":"Network Id"}},{"name":"target_audience","in":"query","required":true,"schema":{"type":"string","description":"Audience profile: republican_primary, democratic_primary, or swing_general","title":"Target Audience"},"description":"Audience profile: republican_primary, democratic_primary, or swing_general"},{"name":"target_states","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated state codes for coverage calculation (e.g. 'MI,WI')","title":"Target States"},"description":"Comma-separated state codes for coverage calculation (e.g. 'MI,WI')"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__NetworkScoresResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/states/{state_code}/congressional-districts":{"get":{"tags":["cbradio","coverage"],"summary":"Get congressional districts for a state","description":"Returns all congressional districts for a state.\n\n**Query Parameters:**\n- `include_coverage_stats` - Include station coverage statistics\n- `include_representative` - Include current representative info","operationId":"get_state_congressional_districts_api_v1_states__state_code__congressional_districts_get","parameters":[{"name":"state_code","in":"path","required":true,"schema":{"type":"string","title":"State Code"}},{"name":"include_coverage_stats","in":"query","required":false,"schema":{"type":"boolean","description":"Include coverage statistics","default":true,"title":"Include Coverage Stats"},"description":"Include coverage statistics"},{"name":"include_representative","in":"query","required":false,"schema":{"type":"boolean","description":"Include representative info","default":true,"title":"Include Representative"},"description":"Include representative info"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/states/{state_code}/counties":{"get":{"tags":["cbradio","coverage"],"summary":"Get counties for a state","description":"Returns all counties for a state.","operationId":"get_state_counties_api_v1_states__state_code__counties_get","parameters":[{"name":"state_code","in":"path","required":true,"schema":{"type":"string","title":"State Code"}},{"name":"include_coverage_stats","in":"query","required":false,"schema":{"type":"boolean","description":"Include coverage statistics","default":true,"title":"Include Coverage Stats"},"description":"Include coverage statistics"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/states/{state_code}/coverage-summary":{"get":{"tags":["cbradio","coverage"],"summary":"Get coverage summary for a state","description":"Returns an aggregated coverage summary for a state.\n\n**Includes:**\n- Congressional district coverage stats\n- State legislative district coverage (if available)\n- County coverage stats\n- In-network station counts\n- Coverage gaps identified","operationId":"get_state_coverage_summary_api_v1_states__state_code__coverage_summary_get","parameters":[{"name":"state_code","in":"path","required":true,"schema":{"type":"string","title":"State Code"}},{"name":"include_legislative","in":"query","required":false,"schema":{"type":"boolean","description":"Include state legislative districts","default":true,"title":"Include Legislative"},"description":"Include state legislative districts"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/states/{state_code}/state-house-districts":{"get":{"tags":["cbradio","coverage"],"summary":"Get state house districts","description":"Returns state house districts for a state. Full boundary data available for some states; basic district list from coverage cache for others.","operationId":"get_state_house_districts_api_v1_states__state_code__state_house_districts_get","parameters":[{"name":"state_code","in":"path","required":true,"schema":{"type":"string","title":"State Code"}},{"name":"include_coverage_stats","in":"query","required":false,"schema":{"type":"boolean","description":"Include coverage statistics","default":true,"title":"Include Coverage Stats"},"description":"Include coverage statistics"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/states/{state_code}/state-senate-districts":{"get":{"tags":["cbradio","coverage"],"summary":"Get state senate districts","description":"Returns state senate districts for a state. Full boundary data available for some states; basic district list from coverage cache for others.","operationId":"get_state_senate_districts_api_v1_states__state_code__state_senate_districts_get","parameters":[{"name":"state_code","in":"path","required":true,"schema":{"type":"string","title":"State Code"}},{"name":"include_coverage_stats","in":"query","required":false,"schema":{"type":"boolean","description":"Include coverage statistics","default":true,"title":"Include Coverage Stats"},"description":"Include coverage statistics"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations":{"get":{"tags":["cbradio","stations"],"summary":"List radio stations","description":"Retrieve a paginated list of radio stations with optional filters.\n\n    **Filters:**\n    - `state`: Two-letter state abbreviation (e.g., TX, CA, FL)\n    - `dma`: Nielsen DMA market name (partial match)\n    - `status`: Station network status (signed, pending, inactive, prospect)\n    - `in_network`: Whether station is in the Rural AM/FM network (see below)\n    - `tag`: Filter by status tag (e.g., salem, iheart, cumulus)\n    - `search`: Partial callsign match (e.g., \"KAA\" matches KAAA-AM, KAAB-FM)\n\n    **in_network Flag:**\n    - `true` (1,602 stations): Station has signed agreement with Rural AM/FM network.\n      These stations get priority in proposal generation and have negotiated rates.\n    - `false` (421 stations): Station is not in network (prospect, competitor, or inactive).\n      Rates for these stations may be estimated or from public filings.\n\n    **Example queries:**\n    - `/api/v1/stations?state=TX&status=signed` - Signed Texas stations\n    - `/api/v1/stations?dma=Dallas&limit=50` - Dallas market stations\n    - `/api/v1/stations?in_network=true&state=MI` - Michigan in-network stations\n    - `/api/v1/stations?tag=salem` - Salem Media stations\n    - `/api/v1/stations?search=KAAA` - Stations matching KAAA\n    - `/api/v1/stations?lifecycle_state=active` - Active lifecycle stations\n    - `/api/v1/stations?lifecycle_state=at_risk` - At-risk stations needing attention","operationId":"list_stations_api_v1_stations_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by state abbreviation (e.g., TX, CA)","examples":["TX"],"title":"State"},"description":"Filter by state abbreviation (e.g., TX, CA)"},{"name":"dma","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by Nielsen DMA market (partial match)","examples":["Dallas"],"title":"Dma"},"description":"Filter by Nielsen DMA market (partial match)"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station status: signed, pending, inactive, prospect","examples":["signed"],"title":"Status"},"description":"Station status: signed, pending, inactive, prospect"},{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by network membership (in-network OR salem-tagged)","title":"Is In Network"},"description":"Filter by network membership (in-network OR salem-tagged)"},{"name":"is_salem","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by Salem status tag","title":"Is Salem"},"description":"Filter by Salem status tag"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status tag (e.g., salem, iheart)","examples":["salem"],"title":"Tag"},"description":"Filter by status tag (e.g., salem, iheart)"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search callsign (partial match)","examples":["KAAA"],"title":"Search"},"description":"Search callsign (partial match)"},{"name":"lifecycle_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by SRM lifecycle state: prospect, contacted, negotiating, active, at_risk, churned, inactive","examples":["active"],"title":"Lifecycle State"},"description":"Filter by SRM lifecycle state: prospect, contacted, negotiating, active, at_risk, churned, inactive"},{"name":"ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by ownership group (e.g., iheart, salem). Use 'null' for unassigned.","examples":["iheart"],"title":"Ownership Group"},"description":"Filter by ownership group (e.g., iheart, salem). Use 'null' for unassigned."},{"name":"has_email","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter stations that have (true) or lack (false) a contact email","title":"Has Email"},"description":"Filter stations that have (true) or lack (false) a contact email"},{"name":"has_mailing_address","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter stations that have (true) or lack (false) a structured mailing address","title":"Has Mailing Address"},"description":"Filter stations that have (true) or lack (false) a structured mailing address"},{"name":"format","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station format (e.g., talk, news_talk, country)","examples":["talk"],"title":"Format"},"description":"Filter by station format (e.g., talk, news_talk, country)"},{"name":"national_rep_firm","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by national rep firm (partial match)","examples":["katz"],"title":"National Rep Firm"},"description":"Filter by national rep firm (partial match)"},{"name":"exclude_non_commercial","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Exclude non-commercial stations (is_commercial=false)","title":"Exclude Non Commercial"},"description":"Exclude non-commercial stations (is_commercial=false)"},{"name":"station_pool","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station pool filter: 'network_only' (in-network + salem) or 'all'","examples":["network_only"],"title":"Station Pool"},"description":"Station pool filter: 'network_only' (in-network + salem) or 'all'"},{"name":"classification","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by classification: in_network, salem, out_of_network, provisional","examples":["in_network"],"title":"Classification"},"description":"Filter by classification: in_network, salem, out_of_network, provisional"},{"name":"valid_callsign_only","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only return stations with valid FCC callsigns (W/K/C/X prefix, 3-5 letters). Defaults to True — pass false to include junk/numeric callsigns.","default":true,"title":"Valid Callsign Only"},"description":"Only return stations with valid FCC callsigns (W/K/C/X prefix, 3-5 letters). Defaults to True — pass false to include junk/numeric callsigns."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum results to return","default":100,"title":"Limit"},"description":"Maximum results to return"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of results to skip","default":0,"title":"Offset"},"description":"Number of results to skip"}],"responses":{"200":{"description":"List of station objects","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/batch-coverage":{"post":{"tags":["cbradio","coverage"],"summary":"Get coverage data for multiple stations","description":"Get district coverage data for multiple stations in a single request.\n\n**Request:** Comma-separated list of facility IDs (max 100). Optional `states`\nfilter (e.g. `MI,IN`) restricts the districts returned and selects which\nper-state overlap entries appear in `state_overlap`.\n\n**Response:**\n- `stations[].districts` — per-district coverage (existing shape)\n- `stations[].state_overlap` — per-state \"% of station's signal inside this\n  state\" (new, cbradio#217). Empty list if the station's contour is not\n  indexed or no states requested matched.\n- `errors` / `summary` — unchanged","operationId":"get_batch_station_coverage_api_v1_stations_batch_coverage_post","parameters":[{"name":"facility_ids","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated facility IDs (max 100)","title":"Facility Ids"},"description":"Comma-separated facility IDs (max 100)"},{"name":"district_types","in":"query","required":false,"schema":{"type":"string","description":"Filter district types","default":"all","title":"District Types"},"description":"Filter district types"},{"name":"district_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Singular alias of `district_types`. Accepts a single value (e.g. `state_senate`). Provided because the singular form was previously silently ignored — cbradio#227 surfaced this.","title":"District Type"},"description":"Singular alias of `district_types`. Accepts a single value (e.g. `state_senate`). Provided because the singular form was previously silently ignored — cbradio#227 surfaced this."},{"name":"states","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional comma-separated 2-letter state codes (e.g. 'MI,IN'). Filters districts to those inside the requested states and restricts the state_overlap array to matching states.","title":"States"},"description":"Optional comma-separated 2-letter state codes (e.g. 'MI,IN'). Filters districts to those inside the requested states and restricts the state_overlap array to matching states."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/bulk-contact-import":{"post":{"tags":["cbradio","stations"],"summary":"Bulk import contact data","description":"Import contact data (email, name, mailing address) for multiple stations.\n\n    - Only updates NULL fields (does not overwrite existing values)\n    - Sets contact_source\n    - Audit logs each change","operationId":"bulk_contact_import_api_v1_stations_bulk_contact_import_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkContactImportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/stations/bulk-ownership-import":{"post":{"tags":["cbradio","stations"],"summary":"Bulk import ownership groups","description":"Import ownership group assignments for multiple stations.\n\n    - Validates ownership_group values\n    - Skips stations with an existing ownership_group_override\n    - Audit logs each change","operationId":"bulk_ownership_import_api_v1_stations_bulk_ownership_import_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__BulkOwnershipImportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/stations/by-geography":{"post":{"tags":["cbradio","coverage"],"summary":"Get stations covering selected geographies","description":"Returns stations that cover the specified geographies.\n\n**Used by proposal workflow** when user selects target districts.\n\n**Request Body:**\n- `geography_type` - Type of geography (congressional, state_senate, state_house, county)\n- `geography_ids` - List of district/county IDs to find coverage for (max 20)\n- `filters` - Optional filters (in_network_only, min_coverage_percentage, has_rate_card)\n\n**Response includes:**\n- Station details with cbradio enrichment (in_network, has_rate_card)\n- Coverage percentage for each selected geography\n- Recommendation score for ranking","operationId":"get_stations_by_geography_api_v1_stations_by_geography_post","parameters":[{"name":"geography_type","in":"query","required":true,"schema":{"type":"string","description":"Type: congressional, state_senate, state_house, county","title":"Geography Type"},"description":"Type: congressional, state_senate, state_house, county"},{"name":"geography_ids","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated list of district/county IDs","title":"Geography Ids"},"description":"Comma-separated list of district/county IDs"},{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only return in-network stations","default":false,"title":"In Network Only"},"description":"Only return in-network stations"},{"name":"has_rate_card_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only return stations with rate cards","default":false,"title":"Has Rate Card Only"},"description":"Only return stations with rate cards"},{"name":"min_coverage_percentage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage %","default":0,"title":"Min Coverage Percentage"},"description":"Minimum coverage %"},{"name":"threshold_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only stations meeting 50% threshold","default":false,"title":"Threshold Only"},"description":"Only stations meeting 50% threshold"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort by: recommendation, coverage, callsign","default":"recommendation","title":"Sort By"},"description":"Sort by: recommendation, coverage, callsign"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"service_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by service type: 'am' or 'fm'","title":"Service Type"},"description":"Filter by service type: 'am' or 'fm'"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"For AM: 'day', 'night', or 'service'. FM only supports 'service'.","title":"Contour Type"},"description":"For AM: 'day', 'night', or 'service'. FM only supports 'service'."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/count":{"get":{"tags":["cbradio","stations"],"summary":"Count stations matching filters","description":"Return the total number of stations that match the given filter criteria.\n\n    Supports filtering by state, DMA, status, network membership, status tags,\n    SRM lifecycle state, ownership group, and contact data presence (email,\n    mailing address).\n\n    Pass `ownership_group=null` to count stations with no ownership group assigned.","operationId":"count_stations_api_v1_stations_count_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"dma","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dma"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"is_in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by network membership","title":"Is In Network"},"description":"Filter by network membership"},{"name":"is_salem","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by Salem status tag","title":"Is Salem"},"description":"Filter by Salem status tag"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by status tag (e.g., salem)","title":"Tag"},"description":"Filter by status tag (e.g., salem)"},{"name":"lifecycle_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by SRM lifecycle state","title":"Lifecycle State"},"description":"Filter by SRM lifecycle state"},{"name":"ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by ownership group. Use 'null' for unassigned.","title":"Ownership Group"},"description":"Filter by ownership group. Use 'null' for unassigned."},{"name":"has_email","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by contact email presence","title":"Has Email"},"description":"Filter by contact email presence"},{"name":"has_mailing_address","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by structured mailing address presence","title":"Has Mailing Address"},"description":"Filter by structured mailing address presence"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/coverage-summary":{"get":{"tags":["cbradio","stations"],"summary":"Get station coverage summary","description":"Returns aggregate statistics about station coverage across states and DMAs.\n\n    **Included metrics:**\n    - Total stations and in-network stations\n    - Coverage by state (top 10)\n    - Coverage by DMA (top 10)\n    - Format distribution\n    - Lifecycle state distribution\n\n    This endpoint is useful for dashboards and quick overviews of network coverage.","operationId":"get_coverage_summary_api_v1_stations_coverage_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/stations/dmas":{"get":{"tags":["cbradio","stations"],"summary":"List DMAs with station counts","description":"Return every unique Nielsen DMA (Designated Market Area) that has at least\n    one station, along with the number of stations in each DMA.\n\n    Optionally filter to DMAs that have at least one station in a given `state`\n    (2-letter code, case-insensitive).\n\n    Results are sorted alphabetically by DMA name.","operationId":"list_dmas_api_v1_stations_dmas_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/facility-conflicts":{"get":{"tags":["cbradio","stations"],"summary":"Stations sharing an fcc_facility_id (cbradio#330)","description":"Every `fcc_facility_id` held by more than one station. A facility ID is issued per licensed facility, so sharing one is a contradiction, not a tolerance — and since callsign text is already unreliable (#308), it leaves no trustworthy join key on the table at all.\n\nEach row is flagged `band_mismatch` when its own `frequency` and its `fcc_frequency` are in different broadcast bands. That is the decisive tell that the row is carrying a **sibling's** FCC identity rather than its own — the observed cause is enrichment copied from the AM sibling instead of looked up per licence. A bare uniqueness count says a facility is shared; this says which row is wrong.\n\n`resolution` is `recorded` when some row in the group names another via `parent_callsign` (how the WTKZ pair was closed on #334), `unresolved` otherwise. Use `parent_callsign` to retire a group from this report without deleting the evidence.","operationId":"facility_conflicts_api_v1_stations_facility_conflicts_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"in_network_only","in":"query","required":false,"schema":{"type":"boolean","description":"Restrict to in-network stations (the money-relevant set)","default":true,"title":"In Network Only"},"description":"Restrict to in-network stations (the money-relevant set)"},{"name":"unresolved_only","in":"query","required":false,"schema":{"type":"boolean","description":"Hide groups already documented via parent_callsign","default":false,"title":"Unresolved Only"},"description":"Hide groups already documented via parent_callsign"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/import/precisiontrak":{"post":{"tags":["cbradio","stations"],"summary":"Import PrecisionTrak enrichment data","description":"Import station enrichment data from PrecisionTrak CSV.\n\n    **Behavior:**\n    - Matches stations by callsign + band (e.g., \"WBRN\" + \"AM\" → \"WBRN-AM\")\n    - PT-specific fields (`national_rep_firm`, `is_commercial`, `county`) always overwrite\n    - Contact fields only fill NULL values (never overwrite existing)\n    - Returns counts: imported, skipped, not_found, errors","operationId":"import_precisiontrak_api_v1_stations_import_precisiontrak_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PrecisionTrakImportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/stations/network-drift":{"get":{"tags":["cbradio","stations"],"summary":"Stations where is_in_network disagrees with Airtable (cbradio#318)","description":"Every station whose stored `is_in_network` contradicts what its persisted Airtable status implies. Airtable is the source of truth for whether an agreement exists, but nothing in cbradio recorded it before #318 — `airtable_id` was NULL on all 16,455 rows and the sync never wrote `is_in_network`, so the flag driving a 15%-vs-50% commission split (a 3.3x swing) drifted with no way to see it.\n\nThis makes the disagreement **visible, not authoritative**: it reports, it never rewrites. Applying a flip is a reviewed decision (cbradio#308's per-owner backfill), because `is_in_network` is also legitimately set by ops for stations Airtable does not cover.\n\n`direction` is what the flip WOULD be: `turn_on` = Airtable says ours but the flag says no; `turn_off` = the flag claims ours but Airtable disagrees (the overpaying direction).\n\nA station whose Airtable record has a BLANK Status is reported separately as `no_signal`, never as drift. 2,975 records are blank; an empty Status is an incomplete record, not Airtable asserting non-membership, and treating absence of evidence as evidence is how #308 over-counted in the first place.","operationId":"network_drift_api_v1_stations_network_drift_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional two-letter state filter","title":"State"},"description":"Optional two-letter state filter"},{"name":"direction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one direction: turn_on | turn_off","title":"Direction"},"description":"Filter to one direction: turn_on | turn_off"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/network-universe":{"get":{"tags":["cbradio","stations"],"summary":"Tiered station universe for a state (cbradio#318)","description":"Every station in a state grouped by commission/payee **tier** (`rci_direct | salem | oon | prospect`) — the queryable replacement for the hand-maintained CSV exports the MI Caucus plan relied on. Consumers read `tier`, never the raw `is_in_network` boolean (which flattens Salem and prospects — the source of WDTK's wrong in-network 2x boost). Optional `tier` filter (comma-separated).","operationId":"network_universe_api_v1_stations_network_universe_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":true,"schema":{"type":"string","description":"Two-letter state code, e.g. MI","title":"State"},"description":"Two-letter state code, e.g. MI"},{"name":"tier","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated tier filter: rci_direct,salem,oon,prospect","title":"Tier"},"description":"Comma-separated tier filter: rci_direct,salem,oon,prospect"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/ownership-summary":{"get":{"tags":["cbradio","stations"],"summary":"Get ownership group summary","description":"Aggregate stations by ownership group with counts.\n\n    Returns total stations, in-network count, stations with email,\n    and stations with structured mailing address per group.","operationId":"get_ownership_summary_api_v1_stations_ownership_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/stations/ownership-summary/{group_id}/stations":{"get":{"tags":["cbradio","stations"],"summary":"List stations in ownership group","description":"Returns stations belonging to a specific ownership group.","operationId":"get_ownership_group_stations_api_v1_stations_ownership_summary__group_id__stations_get","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/score":{"post":{"tags":["cbradio","stations"],"summary":"Score stations (format, coverage, Nielsen, voter efficiency)","description":"Score a list of stations across 4 dimensions (max 16 points).\n\n    **Dimensions (0-4 each):**\n    - **Format**: audience-specific tier mapping (talk=4 for republican_primary)\n    - **Coverage**: % of station footprint in target states\n    - **Nielsen**: AQH share thresholds (≥5%→4, ≥3%→3, ≥1.5%→2, ≥0.5%→1)\n    - **Voter efficiency**: Nielsen-derived market percentile\n\n    Stations with excluded formats (e.g., urban for republican_primary) are omitted.","operationId":"score_stations_api_v1_stations_score_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ScoreRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/cbradio/api/v1/stations/states":{"get":{"tags":["cbradio","stations"],"summary":"List states with station counts","description":"Return every unique US state that has at least one station, along with the\n    number of stations in each state.\n\n    Results are sorted alphabetically by state abbreviation.","operationId":"list_states_api_v1_stations_states_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/stations/tags":{"get":{"tags":["cbradio","stations"],"summary":"List status tags with station counts","description":"Return every unique status tag and the number of stations carrying that tag.\n\n    Tags are extracted from the JSON `status_tags` array on each station.\n    Common tags include `salem`, `iheart`, and `cumulus`.\n\n    Useful for understanding network composition and building filter UIs.","operationId":"list_tags_api_v1_stations_tags_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbradio/api/v1/stations/unbacked-network":{"get":{"tags":["cbradio","stations"],"summary":"is_in_network asserted with no Airtable Status behind it (cbradio#336)","description":"Stations flagged `is_in_network = true` whose `airtable_status` is empty or NULL — in-network, at 50% (or 30% on a `salem` tag), on **no recorded authority at all**. Airtable is the authority for whether an agreement exists (#318), so an empty Status means nothing is asserting one.\n\n**Why this needs its own endpoint rather than /network-drift.** That report filters `airtable_status IS NOT NULL`, so a station the sync never reached is invisible to it, and a blank Status collapses into a bare `no_signal` integer with no callsigns. Neither is inspectable. This one enumerates.\n\n`group` separates the two causes, which need different remedies:\n\n- `linked` — has an `airtable_id`, so the record exists and its Status was **emptied**. Self-contradictory rows result: the sync defaults `status` to `prospect` on an empty Status and never touches `is_in_network`, so a row reads `status=prospect` and `is_in_network=true` at once. Nothing records who emptied it or when.\n- `unlinked` — `airtable_id` IS NULL, so the empty Status is an **absence**, not a removal. These were never matched to an Airtable record, and a `status` of `signed` on them came from something other than the sync.\n\n**Reports, never rewrites** — the same posture as `/network-drift` and `/tier-drift`. Deliberately NOT a hard reconciliation failure: `is_in_network` is also legitimately set by ops for stations Airtable does not cover, so until it is ruled what an emptied Status *means* (#336 ask 2 — ended agreement, or incomplete record?), a station here is a question and not yet a defect.\n\n`ownership_group_source` is included because it is where a wrong rate comes from: `WBTK-AM` carries a `salem` tag derived by pattern-match from a stale `owner` string, holding it at 30% — and Salem's own roster names a different owner. A tag three inferences deep from stale data is indistinguishable here from a recorded fact, which is the #331 provenance argument.\n\nNot covered, and worth knowing: cbradio syncs the Airtable **Stations** table, not the **Call Signs** table where agreement documents hang. A station can therefore be absent here in cbradio's view while an `Agreement` document exists upstream — 9 of these 15 are exactly that.","operationId":"unbacked_network_api_v1_stations_unbacked_network_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional two-letter state filter","title":"State"},"description":"Optional two-letter state filter"},{"name":"group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: linked | unlinked","title":"Group"},"description":"Filter: linked | unlinked"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/{callsign}":{"get":{"tags":["cbradio","stations"],"summary":"Get station by callsign","description":"Retrieve a single station by its callsign, including embedded SRM\n    (Station Relationship Management) data such as lifecycle state, last\n    contact outcome, and agreement dates.\n\n    If the station belongs to a simulcast group, sibling stations are\n    resolved and included in the response.\n\n    Returns 404 if the callsign does not match any station.","operationId":"get_station_api_v1_stations__callsign__get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"put":{"tags":["cbradio","stations"],"summary":"Update station status","description":"Update a station's status field. Requires authentication.\n\n    **Valid statuses:**\n    - `signed`: Station has signed agreement with network\n    - `pending`: Station is in negotiation\n    - `inactive`: Station is disabled/not active\n    - `prospect`: Station is a potential lead","operationId":"update_station_status_api_v1_stations__callsign__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__StationStatusUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","stations"],"summary":"Update station fields","description":"Partially update a station's fields. Only provided fields will be updated.\n\n    **Use Cases:**\n    - Airtable sync updates (contact info, status, tags)\n    - Manual corrections to station data\n    - Bulk updates via API\n\n    **Updatable Fields:**\n    - Contact info: primary_contact (name, email, phone)\n    - Location: mailing_address, nielsen_dma, nielsen_msa\n    - Status: status, status_tags, is_in_network\n    - Ownership: owner, commission_rate\n    - Other: website, notes, format","operationId":"patch_station_api_v1_stations__callsign__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__StationUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/{callsign}/contacts":{"get":{"tags":["cbradio","srm"],"summary":"List contact history for a station","description":"Retrieve contact history for a station, ordered by most recent first.","operationId":"list_station_contacts_api_v1_stations__callsign__contacts_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Results per page","default":20,"title":"Limit"},"description":"Results per page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ContactLogList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","srm"],"summary":"Log a new contact","description":"Log a new contact interaction with a station.\n\n    **Side Effects:**\n    1. Updates `station_srm.last_contacted_at`\n    2. Updates `station_srm.last_contact_outcome`\n    3. Updates `station_srm.last_contact_by_user_id`\n    4. Auto-advances lifecycle state based on outcome (see rules below)\n\n    **Auto-Advance Rules:**\n    | Current State | Outcome | New State |\n    |---------------|---------|-----------|\n    | prospect | Any positive/neutral | contacted |\n    | contacted | meeting_scheduled, agreement_sent | negotiating |\n    | negotiating | agreement_signed | active |\n    | at_risk | Any positive | active |\n    | churned | interested, meeting_scheduled | contacted |\n\n    **Positive Outcomes:** interested, meeting_scheduled, agreement_sent, agreement_signed","operationId":"create_station_contact_api_v1_stations__callsign__contacts_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ContactLogCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ContactLog"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/{callsign}/documents":{"get":{"tags":["cbradio","stations"],"summary":"Get documents for a station","description":"Return all documents associated with the specified station, with optional\n    filtering by document type and year.\n\n    Document metadata includes filename, type, format, rate card type, year,\n    extraction status, and extracted rate count.\n\n    Results are ordered by year descending, then by filename.","operationId":"get_station_documents_api_v1_stations__callsign__documents_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/{callsign}/rate-history":{"get":{"tags":["cbradio","stations"],"summary":"Get rate change history for a station","description":"Return a chronological history of rate changes for the specified station,\n    including rate card approvals and corrections (pending, verified, rejected).\n\n    Optionally filter by daypart name to narrow the history to a specific\n    time slot.\n\n    Useful for auditing rate changes over time and tracking review activity.","operationId":"get_station_rate_history_api_v1_stations__callsign__rate_history_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"daypart","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by daypart name","title":"Daypart"},"description":"Filter by daypart name"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/{callsign}/rates":{"post":{"tags":["cbradio","stations"],"summary":"Write rates for a station","description":"Write or update rates for a station. Used by the proposal builder's\n    \"Update System Rate\" feature and QA correction flows.\n\n    Rates are upserted by (callsign, daypart_id, spot_length, ad_type, year) —\n    posting the same combination again updates the existing rate.","operationId":"write_station_rates_api_v1_stations__callsign__rates_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__ManualRateWriteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"get":{"tags":["cbradio","stations"],"summary":"Get rates for a station","description":"Return all advertising rates for the specified station, with optional\n    filtering by ad type, spot duration, and rate card year.\n\n    Each rate includes provenance information from the parent rate card:\n    source, review status, and last verification timestamp.\n\n    `rate_card_lifecycle` reads `rate_card.lifecycle` directly — the placement\n    gate axis (cbradio#205, cbradio#239). `review_status` is a separate axis\n    (parser-accuracy / QA review) and is not collapsed onto lifecycle.\n\n    | rate_card.lifecycle  | rate_card_lifecycle | meaning                                      |\n    |---|---|---|\n    | `planning`           | `planning`          | needs ops `/approve-planning` before placement |\n    | `approved_planning`  | `approved_planning` | ops-approved planning rate, cleared for placement |\n    | `final`              | `final`             | full placement-cleared rate                    |\n    | `rejected` review    | (rate omitted)      | parser QA failed — rate suppressed             |\n\n    `is_stale` (cbradio#240) signals that the parent rate_card hasn't been\n    verified within 365 days. Hard-blocks order_packet generation. Re-verify\n    via `POST /api/v1/rate-cards/{id}/verify`. NULL `last_verified_at` falls\n    back to the card's `created_at` for the staleness calculation.\n\n    Results are ordered by effective date descending, then by duration and\n    ad type.\n\n    **cbradio#241**: pass `?proposal_class=candidate_in_window|candidate_out_of_window|issue`\n    to filter rates by the proposal/buy's classification. Combines an\n    `ad_type` filter (candidate/all or issue/all) with a `rate_context`\n    filter (in_window/out_of_window/all/standard) so political-window-aware\n    consumers get just the rows that apply. When set, the standalone\n    `?ad_type=` param is ignored.","operationId":"get_station_rates_api_v1_stations__callsign__rates_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ad Type"}},{"name":"proposal_class","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(candidate_in_window|candidate_out_of_window|issue)$"},{"type":"null"}],"description":"Proposal classification (cbradio#241). One of: `candidate_in_window`, `candidate_out_of_window`, `issue`. Overrides `ad_type` when set.","title":"Proposal Class"},"description":"Proposal classification (cbradio#241). One of: `candidate_in_window`, `candidate_out_of_window`, `issue`. Overrides `ad_type` when set."},{"name":"duration","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/{callsign}/srm":{"get":{"tags":["cbradio","srm"],"summary":"Get SRM data for a station","description":"Retrieve SRM (Station Relationship Management) data for a station.\n\n    Returns lifecycle state, contact history, agreement dates, and ownership tracking.\n    Creates an SRM record automatically if one doesn't exist.","operationId":"get_station_srm_api_v1_stations__callsign__srm_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__StationSRM"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio","srm"],"summary":"Update SRM data for a station","description":"Update SRM data for a station.\n\n    **Lifecycle State Transitions:**\n    State transitions are validated. Invalid transitions will return a 400 error.\n\n    Valid transitions:\n    - prospect → contacted\n    - contacted → negotiating, inactive\n    - negotiating → active, churned, inactive\n    - active → at_risk, churned\n    - at_risk → active, churned\n    - churned → contacted, inactive\n    - inactive → prospect, contacted\n\n    **Side Effects:**\n    - Lifecycle state changes update `lifecycle_state_changed_at`","operationId":"update_station_srm_api_v1_stations__callsign__srm_patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__StationSRMUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__StationSRM"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/{callsign}/voters":{"get":{"tags":["cbradio","voters"],"summary":"Get voter data for a station's coverage area","description":"Returns pre-aggregated voter universe data for a station by combining\nits broadcast coverage contour with i360 voter propensity data.\n\n**How it works:**\n1. Looks up the station's county coverage from the coverage cache\n2. Joins with county-level voter data from i360\n3. Aggregates voter counts weighted by coverage percentage\n4. Applies 15% radio listening penetration rate for listener estimates\n\n**Coverage weighting:** If a station covers 60% of a county with 10,000 swing voters,\nthe weighted contribution is 6,000 swing voters.\n\n**Estimated listeners:** Total voters * 0.15 (industry-standard radio penetration rate).\n\n**Data freshness:** Coverage cache and voter data are refreshed monthly.\nCheck `fetched_at` timestamps for currency.\n\n**Use this endpoint when:**\n- Evaluating a station for a media buy\n- Computing Cost Per Ears (CPE) metrics\n- Comparing voter reach across stations in a market","operationId":"get_station_voters_api_v1_stations__callsign__voters_get","parameters":[{"name":"callsign","in":"path","required":true,"schema":{"type":"string","title":"Callsign"}},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage percentage to include a county (filters tiny slivers).","default":0.5,"title":"Min Coverage"},"description":"Minimum coverage percentage to include a county (filters tiny slivers)."}],"responses":{"200":{"description":"Voter data returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__StationVoterResponse"}}}},"404":{"description":"Station not found or no coverage data","content":{"application/json":{"example":{"detail":"No county coverage data for WXYZ-FM"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/stations/{facility_id}/district-coverage":{"get":{"tags":["cbradio","coverage"],"summary":"Get district coverage for a station","description":"Returns all districts that a station's coverage contour intersects with.\n\n**District Types:**\n- `congressional` - US Congressional districts (all states)\n- `state_senate` - State senate districts (MI, NC, AZ, GA, WI, PA, NV)\n- `state_house` - State house districts (MI, NC, AZ, GA, WI, PA, NV)\n- `county` - US counties (all states)\n\n**Filters:**\n- `district_types` - Comma-separated list to filter (default: all)\n- `min_coverage` - Minimum coverage percentage to include (0-100)","operationId":"get_station_district_coverage_api_v1_stations__facility_id__district_coverage_get","parameters":[{"name":"facility_id","in":"path","required":true,"schema":{"type":"string","title":"Facility Id"}},{"name":"district_types","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated: congressional,state_senate,state_house,county","default":"all","title":"District Types"},"description":"Comma-separated: congressional,state_senate,state_house,county"},{"name":"min_coverage","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":0,"description":"Minimum coverage percentage","default":0,"title":"Min Coverage"},"description":"Minimum coverage percentage"},{"name":"contour_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"AM only: day, night, or service. Defaults to day for AM stations.","title":"Contour Type"},"description":"AM only: day, night, or service. Defaults to day for AM stations."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/tasks":{"get":{"tags":["cbradio","srm"],"summary":"List SRM tasks","description":"List SRM tasks with filtering and pagination.\n\n    **Filters:**\n    - `status`: Filter by task status (pending, in_progress, completed, cancelled)\n    - `task_type`: Filter by task type\n    - `owner_user_id`: Filter by assigned user (use 'me' for current user)\n    - `station_callsign`: Filter by specific station\n    - `state`: Filter by station state (e.g., TX, CA)\n    - `due_before`: Show tasks due before this date\n    - `due_after`: Show tasks due after this date\n    - `include_completed`: Include completed/cancelled tasks (default: false)\n\n    **Sorting:**\n    - Default: priority ASC, due_date ASC\n    - Returns tasks ordered by urgency\n\n    **Response includes:**\n    - Task list with optional embedded station data\n    - Total count for pagination\n    - Summary stats (due_today, overdue)","operationId":"list_tasks_api_v1_tasks_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__TaskStatus"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"task_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbradio__TaskType"},{"type":"null"}],"description":"Filter by task type","title":"Task Type"},"description":"Filter by task type"},{"name":"owner_user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by owner (use 'me' for self)","title":"Owner User Id"},"description":"Filter by owner (use 'me' for self)"},{"name":"station_callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station","title":"Station Callsign"},"description":"Filter by station"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by station state","title":"State"},"description":"Filter by station state"},{"name":"due_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Tasks due before date","title":"Due Before"},"description":"Tasks due before date"},{"name":"due_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Tasks due after date","title":"Due After"},"description":"Tasks due after date"},{"name":"include_completed","in":"query","required":false,"schema":{"type":"boolean","description":"Include completed tasks","default":false,"title":"Include Completed"},"description":"Include completed tasks"},{"name":"include_station","in":"query","required":false,"schema":{"type":"boolean","description":"Include station details","default":true,"title":"Include Station"},"description":"Include station details"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Results per page","default":50,"title":"Limit"},"description":"Results per page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset","default":0,"title":"Offset"},"description":"Pagination offset"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__TaskList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"post":{"tags":["cbradio","srm"],"summary":"Create a manual task","description":"Create a task manually. Most tasks are auto-generated by the system,\n    but this endpoint allows creating custom tasks.\n\n    **Required fields:**\n    - station_callsign: The station this task is for\n    - task_type: Type of task\n    - title: Task title\n    - due_date: When the task is due\n\n    **Optional fields:**\n    - reason: Why this task was created\n    - priority: 0 (highest) to 3 (lowest), default 1","operationId":"create_task_api_v1_tasks_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"station_callsign","in":"query","required":true,"schema":{"type":"string","title":"Station Callsign"}},{"name":"task_type","in":"query","required":true,"schema":{"$ref":"#/components/schemas/cbradio__TaskType"}},{"name":"title","in":"query","required":true,"schema":{"type":"string","title":"Title"}},{"name":"due_date","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Due Date"}},{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},{"name":"priority","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Priority"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/tasks/{task_id}":{"get":{"tags":["cbradio","srm"],"summary":"Get task details","description":"Get detailed information about a specific task.","operationId":"get_task_api_v1_tasks__task_id__get","parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/tasks/{task_id}/cancel":{"post":{"tags":["cbradio","srm"],"summary":"Cancel a task","description":"Cancel a task that is no longer needed.\n\n    **Requirements:**\n    - Task must be 'pending' or 'in_progress'\n    - Requires authentication\n\n    **Side Effects:**\n    - Sets task `status` to 'cancelled'","operationId":"cancel_task_api_v1_tasks__task_id__cancel_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}},{"name":"notes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/tasks/{task_id}/claim":{"post":{"tags":["cbradio","srm"],"summary":"Claim a task","description":"Claim a task for yourself. Only pending tasks can be claimed.\n\n    **Side Effects:**\n    - Sets `owner_user_id` to current user\n    - Sets `status` to 'in_progress'\n    - Updates `updated_at`","operationId":"claim_task_api_v1_tasks__task_id__claim_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/tasks/{task_id}/complete":{"post":{"tags":["cbradio","srm"],"summary":"Complete a task","description":"Complete a task with an outcome. This also logs a contact for the station.\n\n    **Requirements:**\n    - Task must be in 'in_progress' status\n    - You must be the current owner (or admin)\n\n    **Side Effects:**\n    1. Sets task `status` to 'completed'\n    2. Sets `completed_at` and `completed_by_user_id`\n    3. Creates a contact log entry for the station\n    4. Updates station SRM data (last_contacted_at, etc.)\n    5. May auto-advance lifecycle state based on outcome","operationId":"complete_task_api_v1_tasks__task_id__complete_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__TaskCompleteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/tasks/{task_id}/unclaim":{"post":{"tags":["cbradio","srm"],"summary":"Release a claimed task","description":"Release a task you have claimed. Returns it to the pending queue.\n\n    **Requirements:**\n    - Task must be in 'in_progress' status\n    - You must be the current owner (or admin)\n\n    **Side Effects:**\n    - Clears `owner_user_id` and `owner_name`\n    - Sets `status` back to 'pending'","operationId":"unclaim_task_api_v1_tasks__task_id__unclaim_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__SRMTask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/traffic-assets/{asset_id}":{"patch":{"tags":["cbradio","traffic-assets"],"summary":"Update traffic asset","description":"Update asset metadata (label, rotation order/weight).","operationId":"update_traffic_asset_api_v1_traffic_assets__asset_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","title":"Asset Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__TrafficAssetUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__TrafficAssetResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}},"delete":{"tags":["cbradio","traffic-assets"],"summary":"Delete traffic asset","description":"Remove a traffic asset.","operationId":"delete_traffic_asset_api_v1_traffic_assets__asset_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","title":"Asset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/year-proposals":{"get":{"tags":["cbradio","year-proposals"],"summary":"Documents with no year, and a proposed one where the text supports it","description":"Year-less documents in the re-extraction cohort, each with a candidate year derived from its OCR text and the evidence behind it. Proposals are ~80% precise, which is why nothing here is written until a human confirms it — see the module docstring for why a wrong year is worse than a missing one.","operationId":"list_proposals_api_v1_year_proposals_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"only_proposed","in":"query","required":false,"schema":{"type":"boolean","description":"Hide documents where the text supports no candidate","default":true,"title":"Only Proposed"},"description":"Hide documents where the text supports no candidate"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Proposals Api V1 Year Proposals Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/api/v1/year-proposals/{document_id}":{"post":{"tags":["cbradio","year-proposals"],"summary":"Accept a year for one document (or reject the proposal)","description":"Writes `document.year`. `year: null` records a rejection by leaving the row untouched, so a reviewer can move past a bad proposal without the queue re-offering it as unreviewed work.","operationId":"decide_api_v1_year_proposals__document_id__post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__Body_decide_api_v1_year_proposals__document_id__post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Decide Api V1 Year Proposals  Document Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HTTPValidationError"}}}}}}},"/cbradio/health":{"get":{"tags":["cbradio","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HealthResponse"}}}}}}},"/cbradio/health/detailed":{"get":{"tags":["cbradio","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__HealthResponse"}}}}}}},"/cbradio/prime":{"get":{"tags":["cbradio","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__PrimeResponse"}}}}}}},"/cbradio/status":{"get":{"tags":["cbradio","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio__StatusResponse"}}}}}}},"/cbradio2/api/v1/airtable-attachments":{"get":{"tags":["cbradio2","Pipelines"],"summary":"List airtable attachments — every `2026 Rate Cards` attachment the Airtable document feed has seen, and where it left it (#23 / #48)","description":"Every airtable attachments — every `2026 Rate Cards` attachment the Airtable document feed has seen, and where it left it (#23 / #48) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY airtable-attachments rows — a screen, an export, a count. When you already hold the `attachment_id`, use `GET /airtable-attachments/{attachment_id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `seen_at`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `filename`, `callsign`.\n- **Filters** —\n  - `?callsign` (eq) — The Airtable record's Id, normalised\n  - `?status` (eq) — stored | duplicate | skipped | unmatched | failed\n  - `?document_id` (eq) — The document it landed as\n  - `?run_id` (eq) — The feed run that last touched it\n- **Domains** — `status` is one of `stored`, `duplicate`, `skipped`, `unmatched`, `failed`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `airtable_attachment`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_airtable_attachments_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The Airtable record's Id, normalised","title":"Callsign"},"description":"The Airtable record's Id, normalised"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"stored | duplicate | skipped | unmatched | failed","title":"Status"},"description":"stored | duplicate | skipped | unmatched | failed"},{"name":"document_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The document it landed as","title":"Document Id"},"description":"The document it landed as"},{"name":"run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The feed run that last touched it","title":"Run Id"},"description":"The feed run that last touched it"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Airtable Attachments Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/airtable-attachments/{row_id}":{"get":{"tags":["cbradio2","Pipelines"],"summary":"Get one airtable attachments — every `2026 Rate Cards` attachment the Airtable document feed has seen, and where it left it (#23 / #48)","description":"One airtable attachments — every `2026 Rate Cards` attachment the Airtable document feed has seen, and where it left it (#23 / #48), by `attachment_id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `attachment_id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /airtable-attachments` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `attachment_id` is a **404**.","operationId":"get_row_api_v1_airtable_attachments__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Airtable Attachments  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buy-lines":{"get":{"tags":["cbradio2","Buys"],"summary":"List buy lines — a station OR a network on a buy, never both (#19)","description":"Every buy lines — a station OR a network on a buy, never both (#19) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY buy-lines rows — a screen, an export, a count. When you already hold the `id`, use `GET /buy-lines/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `buy_id`. A column this resource does not have is refused, never silently ignored.\n- **Filters** —\n  - `?buy_id` (eq) — One buy's lines\n  - `?station_id` (eq) — Every buy line on a station\n  - `?network_id` (eq) — Every buy line on a network\n  - `?cycle` (int) — The buy's cycle\n  - `?proposal_line_item_id` (eq) — The proposal line this was placed from\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_buy_lines_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One buy's lines","title":"Buy Id"},"description":"One buy's lines"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Every buy line on a station","title":"Station Id"},"description":"Every buy line on a station"},{"name":"network_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Every buy line on a network","title":"Network Id"},"description":"Every buy line on a network"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The buy's cycle","title":"Cycle"},"description":"The buy's cycle"},{"name":"proposal_line_item_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The proposal line this was placed from","title":"Proposal Line Item Id"},"description":"The proposal line this was placed from"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Buy Lines Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Buys"],"summary":"Create a buy lines — a station OR a network on a buy, never both (#19)","description":"Create a buy lines — a station OR a network on a buy, never both (#19). Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a buy lines — a station OR a network on a buy, never both (#19) that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `buy_id`, `station_id`, `network_id`, `rate_id`, `network_rate_id`, `source_document_id`, `spots_per_week`, `weeks`, `unit_gross`, `spots`, `gross`, `net`, `note`, `added_by`.\n- **Required** — `buy_id`. An explicit `null` counts as omitted.\n- **`added_by` is mandatory and non-empty** on every write. A hand-maintained row's provenance is who touched it last, and an unsigned correction is the half of the record that gets asked for in six months.\n- `id` is generated unless the body names it. A constraint buy_line refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_buy_lines_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Buy Lines Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buy-lines/{row_id}":{"get":{"tags":["cbradio2","Buys"],"summary":"Get one buy lines — a station OR a network on a buy, never both (#19)","description":"One buy lines — a station OR a network on a buy, never both (#19), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /buy-lines` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_buy_lines__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Buy Lines  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Buys"],"summary":"Update a buy lines — a station OR a network on a buy, never both (#19","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_buy_lines__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Buy Lines  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buy-splits":{"get":{"tags":["cbradio2","Buys"],"summary":"List buy splits — who gets what on a buy: role x party x amount (#19)","description":"Every buy splits — who gets what on a buy: role x party x amount (#19) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY buy-splits rows — a screen, an export, a count. When you already hold the `id`, use `GET /buy-splits/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `buy_id`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `party`.\n- **Filters** —\n  - `?buy_id` (eq) — One buy's splits\n  - `?role` (eq) — salesman | consultant | buyer_agency\n  - `?party` (eq) — The person or agency, as printed\n  - `?cycle` (int) — The buy's cycle\n- **Domains** — `role` is one of `salesman`, `consultant`, `buyer_agency`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_buy_splits_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One buy's splits","title":"Buy Id"},"description":"One buy's splits"},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"salesman | consultant | buyer_agency","title":"Role"},"description":"salesman | consultant | buyer_agency"},{"name":"party","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The person or agency, as printed","title":"Party"},"description":"The person or agency, as printed"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The buy's cycle","title":"Cycle"},"description":"The buy's cycle"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Buy Splits Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Buys"],"summary":"Create a buy splits — who gets what on a buy: role x party x amount (#19)","description":"Create a buy splits — who gets what on a buy: role x party x amount (#19). Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a buy splits — who gets what on a buy: role x party x amount (#19) that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `buy_id`, `role`, `party`, `amount`, `share_pct`, `amount_text`, `added_by`.\n- **Required** — `buy_id`, `role`, `party`. An explicit `null` counts as omitted.\n- **`added_by` is mandatory and non-empty** on every write. A hand-maintained row's provenance is who touched it last, and an unsigned correction is the half of the record that gets asked for in six months.\n- **Domains** — `role` is one of `salesman`, `consultant`, `buyer_agency`.\n- A collision on (`buy_id`, `role`, `party`) is a **409 carrying the row already there**, not a 500.\n- `id` is generated unless the body names it. A constraint buy_split refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_buy_splits_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Buy Splits Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buy-splits/{row_id}":{"get":{"tags":["cbradio2","Buys"],"summary":"Get one buy splits — who gets what on a buy: role x party x amount (#19)","description":"One buy splits — who gets what on a buy: role x party x amount (#19), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /buy-splits` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_buy_splits__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Buy Splits  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Buys"],"summary":"Update a buy splits — who gets what on a buy: role x party x amount (#19","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_buy_splits__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Buy Splits  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buys":{"get":{"tags":["cbradio2","Buys"],"summary":"List buys — one flight, the whole flight's money, the tracker's row (#19)","description":"Every buys — one flight, the whole flight's money, the tracker's row (#19) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY buys rows — a screen, an export, a count. When you already hold the `id`, use `GET /buys/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `flight_start`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `client`, `election`, `notes`, `nab_agency`.\n- **Filters** —\n  - `?cycle` (int) — The election cycle, e.g. 2026\n  - `?client` (eq) — The payer, as printed\n  - `?election_kind` (eq) — primary | general | runoff | special\n  - `?commission_state` (eq) — paid | held | outstanding — the newest ledger row's word\n  - `?allocation_status` (eq) — proposed | approved — NULL rows are not filterable here; the summary counts them as not_recorded\n  - `?source` (eq) — buy_sheet | manual\n  - `?proposal_id` (eq) — The buy placed from this proposal\n  - `?nab_agency` (eq) — The agency of record on the NAB\n  - `?amount_basis` (eq) — gross | net | net_plus_pct | flat | other — the shape money in was agreed as; NULL rows are not filterable here\n- **Domains** — `election_kind` is one of `primary`, `general`, `runoff`, `special`; `allocation_status` is one of `proposed`, `approved`; `tier_split_basis` is one of `lines`, `stated`; `amount_basis` is one of `gross`, `net`, `net_plus_pct`, `flat`, `other`.\n- **Rows left out by default** — Include buys that have been RETIRED — a test placement, a mis-placement, a duplicate. Off by default, because a retired buy is not business and a total that carries one is wrong. Pass `?include_withdrawn=true` to include them. The exclusion is LIST-only: get-one still returns such a row.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_buys_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The election cycle, e.g. 2026","title":"Cycle"},"description":"The election cycle, e.g. 2026"},{"name":"client","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The payer, as printed","title":"Client"},"description":"The payer, as printed"},{"name":"election_kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"primary | general | runoff | special","title":"Election Kind"},"description":"primary | general | runoff | special"},{"name":"commission_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"paid | held | outstanding — the newest ledger row's word","title":"Commission State"},"description":"paid | held | outstanding — the newest ledger row's word"},{"name":"allocation_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"proposed | approved — NULL rows are not filterable here; the summary counts them as not_recorded","title":"Allocation Status"},"description":"proposed | approved — NULL rows are not filterable here; the summary counts them as not_recorded"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"buy_sheet | manual","title":"Source"},"description":"buy_sheet | manual"},{"name":"proposal_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The buy placed from this proposal","title":"Proposal Id"},"description":"The buy placed from this proposal"},{"name":"nab_agency","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The agency of record on the NAB","title":"Nab Agency"},"description":"The agency of record on the NAB"},{"name":"amount_basis","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"gross | net | net_plus_pct | flat | other — the shape money in was agreed as; NULL rows are not filterable here","title":"Amount Basis"},"description":"gross | net | net_plus_pct | flat | other — the shape money in was agreed as; NULL rows are not filterable here"},{"name":"include_withdrawn","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Include buys that have been RETIRED — a test placement, a mis-placement, a duplicate. Off by default, because a retired buy is not business and a total that carries one is wrong.","title":"Include Withdrawn"},"description":"Include buys that have been RETIRED — a test placement, a mis-placement, a duplicate. Off by default, because a retired buy is not business and a total that carries one is wrong."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Buys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Buys"],"summary":"Create a buys — one flight, the whole flight's money, the tracker's row (#19)","description":"Create a buys — one flight, the whole flight's money, the tracker's row (#19). Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a buys — one flight, the whole flight's money, the tracker's row (#19) that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `cycle`, `client`, `election`, `election_kind`, `flight_start`, `flight_end`, `flight_text`, `gross`, `net`, `rural_gross`, `rural_net`, `in_network_gross`, `salem_gross`, `network_gross`, `tier_split_basis`, `non_rural_gross`, `non_rural_net`, `payments_to_stations`, `payments_to_networks`, `amount_basis`, `amount_basis_pct`, `amount_basis_note`, `production_cost`, `production_billed`, `production_adjustment`, `profit`, `commission_tier_id`, `commission_rate`, `nab_agency`, `allocation_status`, `allocation_approved_by`, `allocation_approved_at`, `notes`, `added_by`.\n- **Required** — `cycle`, `client`, `flight_start`, `flight_end`, `gross`. An explicit `null` counts as omitted.\n- **`added_by` is mandatory and non-empty** on every write. A hand-maintained row's provenance is who touched it last, and an unsigned correction is the half of the record that gets asked for in six months.\n- **Domains** — `election_kind` is one of `primary`, `general`, `runoff`, `special`; `allocation_status` is one of `proposed`, `approved`; `tier_split_basis` is one of `lines`, `stated`; `amount_basis` is one of `gross`, `net`, `net_plus_pct`, `flat`, `other`.\n- A collision on (`cycle`, `client`, `flight_start`, `flight_end`) is a **409 carrying the row already there**, not a 500.\n- `id` is generated unless the body names it. A constraint buy refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_buys_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Buys Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buys/from-proposal/{proposal_id}":{"post":{"tags":["cbradio2","Buys"],"summary":"Place a won proposal as a buy on the ledger","description":"Place a `won` proposal on the buy ledger. Answers **201** with the buy.\n\n**Use this endpoint when** the client has agreed and the money is real. It\nis the only way a proposal becomes a buy, and it runs as one transaction.\n\n- **Only a `won` proposal places.** Anything else is a **422** saying so; a\n  plan gets here by being promoted to a proposal first.\n- **Idempotent by proposal.** A second call answers **409** carrying the buy\n  already placed, never a second one.\n- **A buy is a record, not a formula.** Gross, net and the rural split are\n  computed ONCE, here, and frozen on the row; every line keeps the rate it\n  was priced from, `unit_gross x spots_per_week x weeks`, and the station's\n  commission tier and rate AS OF THIS MOMENT. A later tier change or a moved\n  policy cannot move what a payout was computed at.\n- **The NAB agency of record is identity only.** Its commission may be $0,\n  and $0 is a real value — but that is the STATION-side cut. Every network\n  line states its own agency slice in `network_agency`, and a line that\n  does not is a 422: the slice is 15% of the cascade and does not vanish,\n  so unassigned it accrues silently to Rural at 21.375%.\n- **Placement never approves an allocation.** Proposed splits land as\n  `allocation_status = proposed`; approval is a screen action a person takes.\n- It never touches a buy that came from the sheet — an identity collision is\n  a 409 naming it. Nothing here writes a `commission_event`, so a placed buy\n  reads `outstanding` until money actually moves.","operationId":"place_api_v1_buys_from_proposal__proposal_id__post","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__PlacementRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Place Api V1 Buys From Proposal  Proposal Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buys/summary":{"get":{"tags":["cbradio2","Buys"],"summary":"The cycle's buys, commission and financials, aggregated","description":"The cycle's buys, commission and financials — the ONE dashboard aggregate.\n\n**Use this endpoint when** you want cycle-level totals. Nothing is computed\nin the browser: the answer carries the cycle's totals, the same totals by\nmonth of flight start, and what each PERSON is owed. Every money value is a\n**string with two decimals** — this API's contract is that money is parsed\nas a decimal, never a float.\n\nDefinitions, stated here so a dashboard cannot re-derive them differently:\n\n- **`commission_payable`** is the `salesman` + `consultant` splits — what\n  Rural pays OUT. **`buyer_agency_commission`** is the buyer's cut, reported\n  beside it and never added to it.\n- **`commission_state`** is the newest `commission_event` row's word:\n  `paid`, `held`, else `outstanding`. It is the same expression the `buys`\n  list uses, so a row and this summary cannot disagree.\n- **`held`** additionally reports the hold amounts the ledger STATES, which\n  may differ from the bucketed splits. Both are shown; neither is corrected.\n- **`outstanding`** splits three ways by `allocation_status` —\n  `allocated_unpaid` (approved), `awaiting_allocation` (proposed) and\n  `not_recorded` (NULL, where every imported buy starts). The three sum to\n  `outstanding`.\n\nA `cycle` outside 2000-2100 is a **422**.","operationId":"summary_api_v1_buys_summary_get","parameters":[{"name":"cycle","in":"query","required":true,"schema":{"type":"integer","description":"The election cycle, e.g. 2026","title":"Cycle"},"description":"The election cycle, e.g. 2026"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Summary Api V1 Buys Summary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buys/{buy_id}/commission-preview":{"get":{"tags":["cbradio2","Buys"],"summary":"What the commission rules say this buy owes, computed and shown","description":"Apply every active `commission-rule` for this buy's client and show the\nworking. **Writes nothing** (cbradio2#43, ruling B1).\n\n**Use this endpoint when** a screen needs to propose an allocation. It is\na proposal and nothing more — the operator's ruling on #19 is that *CJ\nproposes, Dave approves in person*, so nothing here creates a `buy_split`\nor moves `allocation_status`. Every payout carries its `terms`, because a\ncommission somebody disagrees with is the entire reason this is computed\nserver-side: a number with no derivation beside it cannot be argued with\ncorrectly.\n\n- **Rules are matched on `(client, role)`** and the client string exactly —\n  ruling B1: rates are remembered per person PER CLIENT. Measured over the\n  whole book, only 2 of 12 people hold one rate pair across clients, and\n  both of those have n=2, where a two-parameter fit through two points\n  always passes.\n- **A buy with no tier split is refused (422)**, not approximated.\n  `rural_gross` is `in_network + salem + network`, three slices whose\n  payout rates differ; reading it as any one of them is the conflation\n  rulings C1 and D1 closed. ⚠️ **All 57 imported sheet buys are in this\n  state** — the sheet has no Salem or network column — so this endpoint\n  answers for buys v2 placed itself and refuses, by name, for the rest.\n- **A buy with network gross under a rule with no `rate_network` is\n  refused**, because the network slice sits inside `rural_gross` and would\n  otherwise inherit a rate nobody put it under. `0` is a real answer.\n- `no_rules` on the response is not an error: it means nobody has recorded\n  what this client's people earn. ⚠️ That is the state of every client\n  today — v1's 44 `commission_rule` rows carry every rate as 0 and cannot\n  seed this.","operationId":"commission_preview_api_v1_buys__buy_id__commission_preview_get","parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Commission Preview Api V1 Buys  Buy Id  Commission Preview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buys/{buy_id}/reinstate":{"post":{"tags":["cbradio2","Buys"],"summary":"Put a retired buy back in the book","description":"Reverse a retirement. A **reason is required**, as it is for retiring.\n\nThe reason, the name and the moment are CLEARED from the row — a live row\ncarries no retirement record — but the act is appended to\n`retirement_event`, so the row's history survives the clear. Reinstating a\nlive buy is a 409.","operationId":"reinstate_buy_api_v1_buys__buy_id__reinstate_post","parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__Reinstatement"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reinstate Buy Api V1 Buys  Buy Id  Reinstate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buys/{buy_id}/retire":{"post":{"tags":["cbradio2","Buys"],"summary":"Take a buy out of the book","description":"Retire a buy. It leaves **every** total and stays readable by id.\n\n**Use this endpoint when** a buy should not be counted — a test placement,\na mis-placement, a duplicate flight. `reason` is required (8 characters\nminimum, because \"test\" does not answer the question somebody asks in six\nmonths) and `added_by` is the person taking responsibility.\n\n- **The totals move.** `/buys/summary` excludes it from the cycle totals,\n  by month, by client, by person and the allocation buckets, and reports\n  what it excluded under `retired`.\n- **The list excludes it**; `GET /buys?include_withdrawn=true` shows it.\n  `GET /buys/{id}` always returns it.\n- **Nothing cascades.** Lines, splits, ledger events and payment\n  allocations stay. The response reports what the buy carried so nothing\n  leaves the totals unseen, and names the proposal so that decision is\n  taken separately rather than for you.\n- Retiring twice is a **409** carrying the row, so the first reason and\n  name are never overwritten.","operationId":"retire_buy_api_v1_buys__buy_id__retire_post","parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__Retirement"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Retire Buy Api V1 Buys  Buy Id  Retire Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buys/{buy_id}/retirement":{"patch":{"tags":["cbradio2","Buys"],"summary":"Correct a standing retirement's reason or actor","description":"Fix who a retirement is attributed to, or what it says, **without\nputting the buy back in the totals**.\n\n**Use this endpoint when** a retirement record is wrong — most likely\nbecause `deleted_by` names somebody who did not perform the write.\n\n- **`deleted_at` is not touched.** It is the moment the buy left the book,\n  and correcting a name did not move that moment.\n- **The old values are preserved**, on a `corrected` row in\n  `retirement_event` carrying `previous_actor` and `previous_reason`.\n  Overwriting a wrong attribution silently would destroy the evidence of\n  the mistake along with the mistake.\n- Correcting a buy that is not retired is a **409**.\n\n⚠️ **`deleted_by` is a CLAIM and this service cannot verify it.** Every\ncaller arrives with a key that resolves to a SERVICE, not a person —\n`CBRADIO2_API_KEY` is the fleet key, and `auth.py`'s second door only asks\ncbauth whether a presented key is live. So cbradio2 cannot tell cbrural\nrelaying a genuinely signed-in human from an agent typing a name, and a\nrefusal based on what the string looks like would break the honest case\nwhile catching nothing. **Send the identity of whoever actually performed\nthe write; if you are acting on somebody's instruction, that belongs in\nthe reason.**","operationId":"correct_buy_retirement_api_v1_buys__buy_id__retirement_patch","parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__Correction"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Correct Buy Retirement Api V1 Buys  Buy Id  Retirement Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buys/{buy_id}/retirement-history":{"get":{"tags":["cbradio2","Buys"],"summary":"A buy's retirement ledger","description":"Every retirement, reinstatement and correction on this buy, oldest\nfirst. Append-only: a correction is a row that says what it corrected.","operationId":"buy_retirement_history_api_v1_buys__buy_id__retirement_history_get","parameters":[{"name":"buy_id","in":"path","required":true,"schema":{"type":"string","title":"Buy Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Buy Retirement History Api V1 Buys  Buy Id  Retirement History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/buys/{row_id}":{"get":{"tags":["cbradio2","Buys"],"summary":"Get one buys — one flight, the whole flight's money, the tracker's row (#19)","description":"One buys — one flight, the whole flight's money, the tracker's row (#19), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /buys` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.\n- A row the list leaves out is still returned here: you named it, and 404 would say it never existed rather than that it was withdrawn.","operationId":"get_row_api_v1_buys__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Buys  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Buys"],"summary":"Update a buys — one flight, the whole flight's money, the tracker's row (#19","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_buys__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Buys  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/commission-events":{"get":{"tags":["cbradio2","Buys"],"summary":"List commission events — the ledger: paid <date>, hold <amount>, released (#19)","description":"Every commission events — the ledger: paid <date>, hold <amount>, released (#19) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY commission-events rows — a screen, an export, a count. When you already hold the `id`, use `GET /commission-events/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `recorded_at`. A column this resource does not have is refused, never silently ignored.\n- **Filters** —\n  - `?buy_id` (eq) — One buy's ledger\n  - `?kind` (eq) — paid | hold | released\n  - `?cycle` (int) — The buy's cycle\n- **Domains** — `kind` is one of `paid`, `hold`, `released`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_commission_events_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One buy's ledger","title":"Buy Id"},"description":"One buy's ledger"},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"paid | hold | released","title":"Kind"},"description":"paid | hold | released"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The buy's cycle","title":"Cycle"},"description":"The buy's cycle"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Commission Events Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Buys"],"summary":"Create a commission events — the ledger: paid <date>, hold <amount>, released (#19)","description":"Create a commission events — the ledger: paid <date>, hold <amount>, released (#19). Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a commission events — the ledger: paid <date>, hold <amount>, released (#19) that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `buy_id`, `kind`, `event_date`, `amount`, `reason`, `note`, `recorded_by`.\n- **Required** — `buy_id`, `kind`. An explicit `null` counts as omitted.\n- **`recorded_by` is mandatory and non-empty** on every write. A hand-maintained row's provenance is who touched it last, and an unsigned correction is the half of the record that gets asked for in six months.\n- **Domains** — `kind` is one of `paid`, `hold`, `released`.\n- `id` is generated unless the body names it. A constraint commission_event refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_commission_events_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Commission Events Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/commission-events/{row_id}":{"get":{"tags":["cbradio2","Buys"],"summary":"Get one commission events — the ledger: paid <date>, hold <amount>, released (#19)","description":"One commission events — the ledger: paid <date>, hold <amount>, released (#19), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /commission-events` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_commission_events__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Commission Events  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Buys"],"summary":"Update a commission events — the ledger: paid <date>, hold <amount>, released (#19","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_commission_events__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Commission Events  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/commission-rules":{"get":{"tags":["cbradio2","Buys"],"summary":"List commission rules — what a person earns, per CLIENT, by tier (#43 B1)","description":"Every commission rules — what a person earns, per CLIENT, by tier (#43 B1) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY commission-rules rows — a screen, an export, a count. When you already hold the `id`, use `GET /commission-rules/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `client`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `party`, `client`, `note`.\n- **Filters** —\n  - `?client` (eq) — The buy's client, matched exactly\n  - `?party` (eq) — The person or agency, as printed\n  - `?role` (eq) — salesman | consultant\n  - `?basis` (eq) — stated | fitted — ⚠️ a FITTED rate is a reading of the sheet's dollars, not something anybody agreed to\n  - `?active` (bool) — true | false\n- **Domains** — `role` is one of `salesman`, `consultant`; `basis` is one of `stated`, `fitted`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_commission_rules_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"client","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The buy's client, matched exactly","title":"Client"},"description":"The buy's client, matched exactly"},{"name":"party","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The person or agency, as printed","title":"Party"},"description":"The person or agency, as printed"},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"salesman | consultant","title":"Role"},"description":"salesman | consultant"},{"name":"basis","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"stated | fitted — ⚠️ a FITTED rate is a reading of the sheet's dollars, not something anybody agreed to","title":"Basis"},"description":"stated | fitted — ⚠️ a FITTED rate is a reading of the sheet's dollars, not something anybody agreed to"},{"name":"active","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"true | false","title":"Active"},"description":"true | false"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Commission Rules Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Buys"],"summary":"Create a commission rules — what a person earns, per CLIENT, by tier (#43 B1)","description":"Create a commission rules — what a person earns, per CLIENT, by tier (#43 B1). Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a commission rules — what a person earns, per CLIENT, by tier (#43 B1) that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `party`, `client`, `role`, `rate_in_network`, `rate_salem`, `rate_non_rural`, `rate_rural_gross`, `rate_total_gross`, `rate_network`, `custom_basis_amount`, `basis`, `note`, `active`, `added_by`.\n- **Required** — `party`, `client`, `role`, `basis`. An explicit `null` counts as omitted.\n- **`added_by` is mandatory and non-empty** on every write. A hand-maintained row's provenance is who touched it last, and an unsigned correction is the half of the record that gets asked for in six months.\n- **Domains** — `role` is one of `salesman`, `consultant`; `basis` is one of `stated`, `fitted`.\n- A collision on (`party`, `client`, `role`) is a **409 carrying the row already there**, not a 500.\n- `id` is generated unless the body names it. A constraint commission_rule refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_commission_rules_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Commission Rules Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/commission-rules/{row_id}":{"get":{"tags":["cbradio2","Buys"],"summary":"Get one commission rules — what a person earns, per CLIENT, by tier (#43 B1)","description":"One commission rules — what a person earns, per CLIENT, by tier (#43 B1), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /commission-rules` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_commission_rules__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Commission Rules  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Buys"],"summary":"Update a commission rules — what a person earns, per CLIENT, by tier (#43 B1","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_commission_rules__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Commission Rules  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/dayparts":{"get":{"tags":["cbradio2","Reference"],"summary":"List dayparts","description":"Every dayparts row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY dayparts rows — a screen, an export, a count. When you already hold the `id`, use `GET /dayparts/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `sort_order`. A column this resource does not have is refused, never silently ignored.\n- **Filters** —\n  - `?sellable` (bool) — Only inventory we sell\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `daypart`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_dayparts_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"sellable","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only inventory we sell","title":"Sellable"},"description":"Only inventory we sell"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Dayparts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/dayparts/{row_id}":{"get":{"tags":["cbradio2","Reference"],"summary":"Get one daypart","description":"One daypart, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /dayparts` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_dayparts__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Dayparts  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/documents":{"get":{"tags":["cbradio2","Pipelines"],"summary":"List documents — a file that arrived and where its bytes live (#41/#23)","description":"Every documents — a file that arrived and where its bytes live (#41/#23) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY documents rows — a screen, an export, a count. When you already hold the `id`, use `GET /documents/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- 🚨 **One row per (document, station) pair — NOT one per id.** `X-Total-Count` and the array length both count ROWS, so they exceed the number of distinct documents. Paging is correct on rows; a count you intend to show a human should be over distinct `id`.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `received_at`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `callsign`, `filename`, `source_ref`.\n- **Filters** —\n  - `?station_id` (eq) — Station id\n  - `?state` (eq) — Two-letter state of the station\n  - `?source` (eq) — mailbox | airtable | upload | v1_backfill\n  - `?status` (eq) — received | extracting | extracted | failed | not_a_rate_card\n  - `?card_year` (int) — Card year\n  - `?storage_scheme` (eq) — cbfiles | local\n- **Domains** — `source` is one of `mailbox`, `airtable`, `upload`, `v1_backfill`; `status` is one of `received`, `extracting`, `extracted`, `failed`, `not_a_rate_card`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `document`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_documents_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station id","title":"Station Id"},"description":"Station id"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state of the station","title":"State"},"description":"Two-letter state of the station"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"mailbox | airtable | upload | v1_backfill","title":"Source"},"description":"mailbox | airtable | upload | v1_backfill"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"received | extracting | extracted | failed | not_a_rate_card","title":"Status"},"description":"received | extracting | extracted | failed | not_a_rate_card"},{"name":"card_year","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Card year","title":"Card Year"},"description":"Card year"},{"name":"storage_scheme","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"cbfiles | local","title":"Storage Scheme"},"description":"cbfiles | local"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Documents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/documents/{row_id}":{"get":{"tags":["cbradio2","Pipelines"],"summary":"Get one documents — a file that arrived and where its bytes live (#41/#23)","description":"One documents — a file that arrived and where its bytes live (#41/#23), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /documents` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_documents__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Documents  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/fcc-facilities":{"get":{"tags":["cbradio2","Stations"],"summary":"List FCC facilities","description":"Every FCC facilities row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY fcc-facilities rows — a screen, an export, a count. When you already hold the `facility_id`, use `GET /fcc-facilities/{facility_id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `callsign`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `callsign`.\n- **Filters** —\n  - `?facility_id` (eq) — FCC facility id\n  - `?band` (eq) — AM | FM\n  - `?station_class` (eq) — Licensed FM/AM class, where known\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `fcc_facility`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_fcc_facilities_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"facility_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"FCC facility id","title":"Facility Id"},"description":"FCC facility id"},{"name":"band","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"AM | FM","title":"Band"},"description":"AM | FM"},{"name":"station_class","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Licensed FM/AM class, where known","title":"Station Class"},"description":"Licensed FM/AM class, where known"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Fcc Facilities Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/fcc-facilities/{row_id}":{"get":{"tags":["cbradio2","Stations"],"summary":"Get one FCC facilitie","description":"One FCC facilitie, by `facility_id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `facility_id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /fcc-facilities` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `facility_id` is a **404**.","operationId":"get_row_api_v1_fcc_facilities__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Fcc Facilities  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/feed-runs":{"get":{"tags":["cbradio2","Pipelines"],"summary":"List feed runs","description":"Every feed runs row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY feed-runs rows — a screen, an export, a count. When you already hold the `id`, use `GET /feed-runs/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `started_at`. A column this resource does not have is refused, never silently ignored.\n- **Filters** —\n  - `?feed` (eq) — Feed name\n  - `?status` (eq) — running | ok | failed | partial\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `feed_run`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_feed_runs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"feed","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Feed name","title":"Feed"},"description":"Feed name"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"running | ok | failed | partial","title":"Status"},"description":"running | ok | failed | partial"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Feed Runs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/feed-runs/{row_id}":{"get":{"tags":["cbradio2","Pipelines"],"summary":"Get one feed run","description":"One feed run, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /feed-runs` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_feed_runs__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Feed Runs  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/ingest-rejects":{"get":{"tags":["cbradio2","Pipelines"],"summary":"List ingest rejects","description":"Every ingest rejects row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY ingest-rejects rows — a screen, an export, a count. When you already hold the `id`, use `GET /ingest-rejects/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `created_at`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `reason`, `source_ref`.\n- **Filters** —\n  - `?feed` (eq) — Feed name\n  - `?rule` (eq) — Reject rule\n  - `?stage` (eq) — Pipeline stage\n  - `?run_id` (eq) — One run\n  - `?rate_card_id` (eq) — Rejects belonging to one card\n  - `?blocks_review` (bool) — Same product, different price — a human has to adjudicate\n- **Rows left out by default** — Every run, not only the newest per (card, rule) Pass `?include_history=true` to include them (naming `?run_id` also lifts it). The exclusion is LIST-only: get-one still returns such a row.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `ingest_reject`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_ingest_rejects_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"feed","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Feed name","title":"Feed"},"description":"Feed name"},{"name":"rule","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Reject rule","title":"Rule"},"description":"Reject rule"},{"name":"stage","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pipeline stage","title":"Stage"},"description":"Pipeline stage"},{"name":"run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One run","title":"Run Id"},"description":"One run"},{"name":"rate_card_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Rejects belonging to one card","title":"Rate Card Id"},"description":"Rejects belonging to one card"},{"name":"blocks_review","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same product, different price — a human has to adjudicate","title":"Blocks Review"},"description":"Same product, different price — a human has to adjudicate"},{"name":"include_history","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Every run, not only the newest per (card, rule)","title":"Include History"},"description":"Every run, not only the newest per (card, rule)"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Ingest Rejects Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/ingest-rejects/{row_id}":{"get":{"tags":["cbradio2","Pipelines"],"summary":"Get one ingest reject","description":"One ingest reject, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /ingest-rejects` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.\n- A row the list leaves out is still returned here: you named it, and 404 would say it never existed rather than that it was withdrawn.","operationId":"get_row_api_v1_ingest_rejects__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Ingest Rejects  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/mailbox-messages":{"get":{"tags":["cbradio2","Pipelines"],"summary":"List mailbox messages — every mail the rates reader has seen (#41)","description":"Every mailbox messages — every mail the rates reader has seen (#41) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY mailbox-messages rows — a screen, an export, a count. When you already hold the `message_id`, use `GET /mailbox-messages/{message_id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `seen_at`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `subject`, `from_address`.\n- **Filters** —\n  - `?station_id` (eq) — Station id\n  - `?status` (eq) — matched | unmatched | ignored\n  - `?run_id` (eq) — The feed run that saw it\n- **Domains** — `status` is one of `matched`, `unmatched`, `ignored`; `match_method` is one of `thread`, `subject_callsign`, `sender_domain`, `body_callsign`, `subject_bare_callsign`, `body_bare_callsign`, `subject_bare_callsign_both_bands`, `body_bare_callsign_both_bands`, `attachment_filename_callsign`, `attachment_filename_bare_callsign`, `attachment_filename_bare_callsign_both_bands`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `mailbox_message`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_mailbox_messages_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station id","title":"Station Id"},"description":"Station id"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"matched | unmatched | ignored","title":"Status"},"description":"matched | unmatched | ignored"},{"name":"run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The feed run that saw it","title":"Run Id"},"description":"The feed run that saw it"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Mailbox Messages Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/mailbox-messages/{row_id}":{"get":{"tags":["cbradio2","Pipelines"],"summary":"Get one mailbox messages — every mail the rates reader has seen (#41)","description":"One mailbox messages — every mail the rates reader has seen (#41), by `message_id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `message_id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /mailbox-messages` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `message_id` is a **404**.","operationId":"get_row_api_v1_mailbox_messages__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Mailbox Messages  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/manual-rate":{"post":{"tags":["cbradio2","Rate cards"],"summary":"Record a rate somebody phoned in","description":"Create the card and the rate together, or attach to the card that exists.\n\nForcing somebody to build a rate card before they can record a price they\nalready have is exactly the blocker CJ asked to remove, so there is one\ncall and it does both.","operationId":"create_manual_rate_api_v1_manual_rate_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__ManualRate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__ManualRateResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/manual-rate/{rate_id}":{"delete":{"tags":["cbradio2","Rate cards"],"summary":"Withdraw a rate somebody phoned in","description":"Stop a manual rate from pricing, and say who stopped it.\n\n🚨 This exists because the create path made a ONE-WAY DOOR, and the\ndangerous case is not the obvious one. `replace` already fixes a wrong\nPRICE. It cannot fix a rate typed against the wrong STATION or the wrong\nDAYPART — and `human` is the HIGHEST entry in the policy's\n`source_priority`, so a misplaced phoned-in rate does not sit harmlessly\nbeside that station's real card, it OUTRANKS it. Before this, the only\nremedy was a hand-written statement against the store, which is precisely\nthe thing `store.py` exists to stop being normal.\n\n⚠️ SOFT delete, never a DELETE. `is_deleted` is what the policy's\n`not_deleted` eligibility rule reads, so flipping it stops the rate pricing\nimmediately through the rule that already exists rather than a second\nmechanism. And a price somebody obtained by phone is evidence even once it\nis wrong: who said it, when, and who withdrew it is exactly what gets asked\nin six months.\n\n⚠️ It refuses anything that is not on a `human` card. This endpoint's whole\nclaim is that it writes one shape; being able to soft-delete a harvested\nKatz rate would make it a general delete verb on the catalogue with a\nnarrow-sounding name.\n\nWithdrawing an already-withdrawn rate SUCCEEDS rather than 404ing. The\ncaller asked for a state, the state holds, and a second click on a button\nwhose first click was lost to a timeout must not read as an error — the\nsame reasoning that makes `StoreTimeout` \"unknown\" rather than \"did not\nhappen\". `was_already_withdrawn` says which happened.","operationId":"withdraw_manual_rate_api_v1_manual_rate__rate_id__delete","parameters":[{"name":"rate_id","in":"path","required":true,"schema":{"type":"string","title":"Rate Id"}},{"name":"withdrawn_by","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Who is withdrawing it. Required, as on create.","title":"Withdrawn By"},"description":"Who is withdrawing it. Required, as on create."},{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Why. Recorded on the rate.","title":"Reason"},"description":"Why. Recorded on the rate."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__Withdrawal"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/network-rates":{"get":{"tags":["cbradio2","Networks"],"summary":"List network rates — the published price per spot, per cell, per year (#18)","description":"Every network rates — the published price per spot, per cell, per year (#18) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY network-rates rows — a screen, an export, a count. When you already hold the `id`, use `GET /network-rates/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `network_id`. A column this resource does not have is refused, never silently ignored.\n- **Filters** —\n  - `?network_id` (eq) — One network's prices\n  - `?ad_type` (eq) — candidate | issue\n  - `?duration_seconds` (int) — 30 | 60\n  - `?rate_year` (int) — The year the price is FOR\n  - `?network_state` (eq) — Two-letter state of the network\n- **Domains** — `ad_type` is one of `candidate`, `issue`.\n- **Rows left out by default** — Withdrawn prices are left out of the list unless asked for Pass `?include_withdrawn=true` to include them. The exclusion is LIST-only: get-one still returns such a row.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_network_rates_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"network_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One network's prices","title":"Network Id"},"description":"One network's prices"},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"candidate | issue","title":"Ad Type"},"description":"candidate | issue"},{"name":"duration_seconds","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"30 | 60","title":"Duration Seconds"},"description":"30 | 60"},{"name":"rate_year","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The year the price is FOR","title":"Rate Year"},"description":"The year the price is FOR"},{"name":"network_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state of the network","title":"Network State"},"description":"Two-letter state of the network"},{"name":"include_withdrawn","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Withdrawn prices are left out of the list unless asked for","title":"Include Withdrawn"},"description":"Withdrawn prices are left out of the list unless asked for"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Network Rates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Networks"],"summary":"Create a network rates — the published price per spot, per cell, per year (#18)","description":"Create a network rates — the published price per spot, per cell, per year (#18). Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a network rates — the published price per spot, per cell, per year (#18) that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `network_id`, `ad_type`, `duration_seconds`, `gross_rate`, `rate_year`, `effective_date`, `source_document_id`, `is_deleted`, `deleted_at`, `added_by`.\n- **Required** — `network_id`, `ad_type`, `duration_seconds`, `gross_rate`, `rate_year`, `source_document_id`. An explicit `null` counts as omitted.\n- **`added_by` is mandatory and non-empty** on every write. A hand-maintained row's provenance is who touched it last, and an unsigned correction is the half of the record that gets asked for in six months.\n- **Domains** — `ad_type` is one of `candidate`, `issue`.\n- A collision on (`network_id`, `ad_type`, `duration_seconds`, `rate_year`) is a **409 carrying the row already there**, not a 500.\n- `id` is generated unless the body names it. A constraint network_rate refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_network_rates_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Network Rates Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/network-rates/{row_id}":{"get":{"tags":["cbradio2","Networks"],"summary":"Get one network rates — the published price per spot, per cell, per year (#18)","description":"One network rates — the published price per spot, per cell, per year (#18), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /network-rates` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.\n- A row the list leaves out is still returned here: you named it, and 404 would say it never existed rather than that it was withdrawn.","operationId":"get_row_api_v1_network_rates__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Network Rates  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Networks"],"summary":"Update a network rates — the published price per spot, per cell, per year (#18","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_network_rates__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Network Rates  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/network-stations":{"get":{"tags":["cbradio2","Networks"],"summary":"List network stations — who the vendor says airs a network (#18)","description":"Every network stations — who the vendor says airs a network (#18) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY network-stations rows — a screen, an export, a count. When you already hold the `id`, use `GET /network-stations/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `callsign`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `callsign`, `market`, `dma`.\n- **Filters** —\n  - `?network_id` (eq) — One network's list\n  - `?station_id` (eq) — Every network a station airs on\n  - `?callsign` (eq) — As printed on the vendor's list\n  - `?matched` (bool) — Only members we could tie to a station row\n  - `?network_state` (eq) — Two-letter state of the network\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_network_stations_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"network_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One network's list","title":"Network Id"},"description":"One network's list"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Every network a station airs on","title":"Station Id"},"description":"Every network a station airs on"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"As printed on the vendor's list","title":"Callsign"},"description":"As printed on the vendor's list"},{"name":"matched","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only members we could tie to a station row","title":"Matched"},"description":"Only members we could tie to a station row"},{"name":"network_state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state of the network","title":"Network State"},"description":"Two-letter state of the network"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Network Stations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Networks"],"summary":"Create a network stations — who the vendor says airs a network (#18)","description":"Create a network stations — who the vendor says airs a network (#18). Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a network stations — who the vendor says airs a network (#18) that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `network_id`, `callsign`, `station_id`, `market`, `dma`, `county`, `frequency`, `format`, `market_size`, `added_by`.\n- **Required** — `network_id`, `callsign`. An explicit `null` counts as omitted.\n- **`added_by` is mandatory and non-empty** on every write. A hand-maintained row's provenance is who touched it last, and an unsigned correction is the half of the record that gets asked for in six months.\n- A collision on (`network_id`, `callsign`) is a **409 carrying the row already there**, not a 500.\n- `id` is generated unless the body names it. A constraint network_station refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_network_stations_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Network Stations Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/network-stations/{row_id}":{"get":{"tags":["cbradio2","Networks"],"summary":"Get one network stations — who the vendor says airs a network (#18)","description":"One network stations — who the vendor says airs a network (#18), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /network-stations` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_network_stations__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Network Stations  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Networks"],"summary":"Update a network stations — who the vendor says airs a network (#18","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_network_stations__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Network Stations  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/networks":{"get":{"tags":["cbradio2","Networks"],"summary":"List networks — a thing bought as one line at the vendor's price (#18)","description":"Every networks — a thing bought as one line at the vendor's price (#18) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY networks rows — a screen, an export, a count. When you already hold the `id`, use `GET /networks/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `name`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `name`.\n- **Filters** —\n  - `?state` (eq) — Two-letter state\n  - `?vendor` (eq) — Who sells it (StateNets today)\n  - `?network_type` (eq) — wired | unwired\n  - `?product_type` (eq) — sports | news | ag | talk | spanish | specialty | unwired | other\n  - `?active` (bool) — Only networks still offered\n  - `?source` (eq) — v1_networks | manual\n  - `?source_document_id` (eq) — The document row it came from\n- **Domains** — `network_type` is one of `wired`, `unwired`; `product_type` is one of `sports`, `news`, `ag`, `talk`, `spanish`, `specialty`, `unwired`, `other`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_networks_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state","title":"State"},"description":"Two-letter state"},{"name":"vendor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Who sells it (StateNets today)","title":"Vendor"},"description":"Who sells it (StateNets today)"},{"name":"network_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"wired | unwired","title":"Network Type"},"description":"wired | unwired"},{"name":"product_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"sports | news | ag | talk | spanish | specialty | unwired | other","title":"Product Type"},"description":"sports | news | ag | talk | spanish | specialty | unwired | other"},{"name":"active","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only networks still offered","title":"Active"},"description":"Only networks still offered"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"v1_networks | manual","title":"Source"},"description":"v1_networks | manual"},{"name":"source_document_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The document row it came from","title":"Source Document Id"},"description":"The document row it came from"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Networks Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Networks"],"summary":"Create a networks — a thing bought as one line at the vendor's price (#18)","description":"Create a networks — a thing bought as one line at the vendor's price (#18). Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a networks — a thing bought as one line at the vendor's price (#18) that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `name`, `state`, `vendor`, `network_type`, `product_type`, `stated_station_count`, `schedule_text`, `source_document_id`, `is_active`, `note`, `added_by`.\n- **Required** — `name`, `state`, `vendor`, `source_document_id`. An explicit `null` counts as omitted.\n- **`added_by` is mandatory and non-empty** on every write. A hand-maintained row's provenance is who touched it last, and an unsigned correction is the half of the record that gets asked for in six months.\n- **Domains** — `network_type` is one of `wired`, `unwired`; `product_type` is one of `sports`, `news`, `ag`, `talk`, `spanish`, `specialty`, `unwired`, `other`.\n- `id` is generated unless the body names it. A constraint network refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_networks_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Networks Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/networks/{row_id}":{"get":{"tags":["cbradio2","Networks"],"summary":"Get one networks — a thing bought as one line at the vendor's price (#18)","description":"One networks — a thing bought as one line at the vendor's price (#18), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /networks` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_networks__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Networks  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Networks"],"summary":"Update a networks — a thing bought as one line at the vendor's price (#18","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_networks__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Networks  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/outreach":{"post":{"tags":["cbradio2","Outreach"],"summary":"Log an outreach attempt or an answer","description":"Record that somebody reached out — and whatever came back. Answers **201**.\n\n**Use this endpoint when** a station was emailed or called, whether or not it\nproduced a rate. *\"They said to ask someone else\"* is the modal outcome of a\ncold call, and it is a fact worth as much as a number: log the attempt, and\nthe referral it learned is written as a `station_contact` in the SAME\ntransaction, so a caller can never half-land the pair.\n\nThis is the **only writer** of `outreach_event`; the `outreach` resource in\nthe generic API is read-only. If the call produced an actual price, record\nthat through `POST /manual-rate` — this endpoint logs contact, not rates.\n\nAn unknown `station_id` is refused, and a repeat `out` attempt for a\ncampaign already sent to that station is refused with the attempt already\nlogged rather than duplicated.","operationId":"log_attempt_api_v1_outreach_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__Attempt"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__Logged"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"get":{"tags":["cbradio2","Outreach"],"summary":"List outreach events","description":"Every outreach events row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY outreach rows — a screen, an export, a count. When you already hold the `id`, use `GET /outreach/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `occurred_at`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `note`, `contact_name`, `contact_email`, `station_callsign`.\n- **Filters** —\n  - `?station_id` (eq) — Events for one station\n  - `?channel` (eq) — email | phone | other\n  - `?direction` (eq) — out (we tried) | in (they answered)\n  - `?outcome` (eq) — sent | replied | bounced | no_answer | voicemail | referred | rate_received | callback_requested | declined | wrong_number | station_dark\n  - `?logged_by` (eq) — Who logged it\n  - `?campaign` (eq) — One sweep's events\n  - `?message_id` (eq) — The Message-ID we sent with\n  - `?in_reply_to` (eq) — Replies to one Message-ID\n  - `?callsign` (like) — Station callsign, partial\n  - `?state` (eq) — Two-letter state of the station\n  - `?in_network` (bool) — Only events at in-network stations\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `outreach_event`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_outreach_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Events for one station","title":"Station Id"},"description":"Events for one station"},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"email | phone | other","title":"Channel"},"description":"email | phone | other"},{"name":"direction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"out (we tried) | in (they answered)","title":"Direction"},"description":"out (we tried) | in (they answered)"},{"name":"outcome","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"sent | replied | bounced | no_answer | voicemail | referred | rate_received | callback_requested | declined | wrong_number | station_dark","title":"Outcome"},"description":"sent | replied | bounced | no_answer | voicemail | referred | rate_received | callback_requested | declined | wrong_number | station_dark"},{"name":"logged_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Who logged it","title":"Logged By"},"description":"Who logged it"},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One sweep's events","title":"Campaign"},"description":"One sweep's events"},{"name":"message_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The Message-ID we sent with","title":"Message Id"},"description":"The Message-ID we sent with"},{"name":"in_reply_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Replies to one Message-ID","title":"In Reply To"},"description":"Replies to one Message-ID"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station callsign, partial","title":"Callsign"},"description":"Station callsign, partial"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state of the station","title":"State"},"description":"Two-letter state of the station"},{"name":"in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only events at in-network stations","title":"In Network"},"description":"Only events at in-network stations"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Outreach Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/outreach-state":{"get":{"tags":["cbradio2","Outreach"],"summary":"List per-station outreach states","description":"Every per-station outreach states row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY outreach-state rows — a screen, an export, a count. When you already hold the `station_id`, use `GET /outreach-state/{station_id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `last_occurred_at`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `station_callsign`.\n- **Filters** —\n  - `?station_id` (eq) — One station\n  - `?last_outcome` (eq) — sent | replied | bounced | no_answer | voicemail | referred | rate_received | callback_requested | declined | wrong_number | station_dark\n  - `?last_channel` (eq) — email | phone | other\n  - `?awaiting_reply` (bool) — Outbound attempt with nothing inbound since\n  - `?state` (eq) — Two-letter state of the station\n  - `?in_network` (bool) — Only in-network stations\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `outreach_event`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_outreach_state_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One station","title":"Station Id"},"description":"One station"},{"name":"last_outcome","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"sent | replied | bounced | no_answer | voicemail | referred | rate_received | callback_requested | declined | wrong_number | station_dark","title":"Last Outcome"},"description":"sent | replied | bounced | no_answer | voicemail | referred | rate_received | callback_requested | declined | wrong_number | station_dark"},{"name":"last_channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"email | phone | other","title":"Last Channel"},"description":"email | phone | other"},{"name":"awaiting_reply","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Outbound attempt with nothing inbound since","title":"Awaiting Reply"},"description":"Outbound attempt with nothing inbound since"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state of the station","title":"State"},"description":"Two-letter state of the station"},{"name":"in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only in-network stations","title":"In Network"},"description":"Only in-network stations"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Outreach State Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/outreach-state/{row_id}":{"get":{"tags":["cbradio2","Outreach"],"summary":"Get one per-station outreach state","description":"One per-station outreach state, by `station_id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `station_id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /outreach-state` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `station_id` is a **404**.","operationId":"get_row_api_v1_outreach_state__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Outreach State  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/outreach/{row_id}":{"get":{"tags":["cbradio2","Outreach"],"summary":"Get one outreach event","description":"One outreach event, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /outreach` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_outreach__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Outreach  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/payment-allocations":{"get":{"tags":["cbradio2","Buys"],"summary":"List payment allocations — a remittance's share of one flight (#43)","description":"Every payment allocations — a remittance's share of one flight (#43) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY payment-allocations rows — a screen, an export, a count. When you already hold the `id`, use `GET /payment-allocations/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `buy_id`. A column this resource does not have is refused, never silently ignored.\n- **Filters** —\n  - `?buy_id` (eq) — Every remittance touching one flight\n  - `?payment_id` (eq) — One remittance's flights\n  - `?cycle` (int) — The buy's cycle\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_payment_allocations_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"buy_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Every remittance touching one flight","title":"Buy Id"},"description":"Every remittance touching one flight"},{"name":"payment_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One remittance's flights","title":"Payment Id"},"description":"One remittance's flights"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The buy's cycle","title":"Cycle"},"description":"The buy's cycle"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Payment Allocations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/payment-allocations/{row_id}":{"get":{"tags":["cbradio2","Buys"],"summary":"Get one payment allocations — a remittance's share of one flight (#43)","description":"One payment allocations — a remittance's share of one flight (#43), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /payment-allocations` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_payment_allocations__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Payment Allocations  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Buys"],"summary":"Update a payment allocations — a remittance's share of one flight (#43","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_payment_allocations__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Payment Allocations  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/payments":{"post":{"tags":["cbradio2","Buys"],"summary":"Record a remittance and what it covers","description":"Record money that arrived, and say which flights it pays for.\n\n**Use this endpoint when** a cheque, wire or agency remittance lands. It is\nthe only way a `payment` or a `payment_allocation` is created.\n\n- **The allocations must sum to the payment**, exactly. Anything else is a\n  **422** stating both totals and the difference — a lump that adds up to\n  more than itself is the double-count this table exists to prevent.\n- **One transaction.** Either the payment and all its allocations land, or\n  none of them do.\n- **One payment cannot land twice on the same buy** (UNIQUE on\n  `(payment_id, buy_id)`); two allocations naming the same flight are a\n  **422** here, before the index has to say so.\n- **Every buy must exist.** An unknown `buy_id` is a **422** naming it,\n  never a silently orphaned allocation.\n- It writes nothing to the buy. `amount_received` is derived on read, and\n  `profit` stays the recorded number (see the module docstring).","operationId":"record_api_v1_payments_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__PaymentRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Record Api V1 Payments Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"get":{"tags":["cbradio2","Buys"],"summary":"List payments — one remittance that actually arrived, whatever it covers (#43)","description":"Every payments — one remittance that actually arrived, whatever it covers (#43) row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY payments rows — a screen, an export, a count. When you already hold the `id`, use `GET /payments/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `received_on`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `payer`, `reference`, `note`.\n- **Filters** —\n  - `?payer` (eq) — Who paid, as printed\n  - `?reference` (eq) — The cheque / wire reference\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_payments_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"payer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Who paid, as printed","title":"Payer"},"description":"Who paid, as printed"},{"name":"reference","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The cheque / wire reference","title":"Reference"},"description":"The cheque / wire reference"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Payments Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/payments/{row_id}":{"get":{"tags":["cbradio2","Buys"],"summary":"Get one payments — one remittance that actually arrived, whatever it covers (#43)","description":"One payments — one remittance that actually arrived, whatever it covers (#43), by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /payments` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_payments__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Payments  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Buys"],"summary":"Update a payments — one remittance that actually arrived, whatever it covers (#43","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_payments__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Payments  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/plan":{"post":{"tags":["cbradio2","Plans"],"summary":"Price a footprint — drop nobody","description":"Price a whole footprint in one call, and drop nobody. Answers **201**.\n\n**Use this endpoint when** you are building a media plan across many\nstations and need a number for every one of them. It is the deliberate\ninverse of `POST /quote`: a quote REFUSES when no rate card wins, because a\nwrong price is worse than no price; a plan takes that refusal to the\nbenchmark waterfall instead, and records on every line which kind of number\nit got.\n\n- **Every line carries two prices.** `unit_gross` is what you show;\n  `unit_gross_expected` is what you SUM. On a card line they are equal; on a\n  benchmark line they are the cohort's median and its mean, because rates\n  are right-skewed and a median is not sum-preserving. Summing the wrong one\n  under-quotes a 58-station plan by 27%.\n- **Nothing is dropped silently.** A station that cannot be priced even from\n  a national cohort comes back in `skipped` with the reason, never omitted —\n  a footprint quietly one station short reads as a complete plan.\n- More than the station maximum is a **422** rather than a truncated\n  footprint; no active pricing policy is a **503**.\n\nA plan is not a proposal. Promote it with `POST /plan/{id}/promote` when the\nnumbers are going to a client.","operationId":"build_plan_api_v1_plan_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__PlanRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__PlanResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/plan-line-items":{"get":{"tags":["cbradio2","Plans"],"summary":"List plan line items","description":"Every plan line items row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY plan-line-items rows — a screen, an export, a count. When you already hold the `id`, use `GET /plan-line-items/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `plan_id`. A column this resource does not have is refused, never silently ignored.\n- **Filters** —\n  - `?plan_id` (eq) — Plan\n  - `?station_id` (eq) — Station\n  - `?basis` (eq) — card | benchmark\n  - `?cohort_level` (eq) — How comparable the cohort was\n- **Domains** — `basis` is one of `card`, `benchmark`; `cohort_level` is one of `state+bucket`, `state`, `bucket`, `national`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_plan_line_items_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"plan_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Plan","title":"Plan Id"},"description":"Plan"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station","title":"Station Id"},"description":"Station"},{"name":"basis","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"card | benchmark","title":"Basis"},"description":"card | benchmark"},{"name":"cohort_level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"How comparable the cohort was","title":"Cohort Level"},"description":"How comparable the cohort was"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Plan Line Items Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Plans"],"summary":"Create a plan line item","description":"Create a plan line item. Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a plan line item that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `plan_id`, `station_id`, `rate_id`, `basis`, `cohort_level`, `cohort_stations`, `daypart_id`, `days_mask`, `spots_per_week`, `weeks`, `unit_gross`, `unit_gross_expected`, `rationale`.\n- **Required** — `plan_id`, `station_id`, `basis`, `daypart_id`, `days_mask`, `spots_per_week`, `weeks`, `unit_gross`, `unit_gross_expected`. An explicit `null` counts as omitted.\n- **Domains** — `basis` is one of `card`, `benchmark`; `cohort_level` is one of `state+bucket`, `state`, `bucket`, `national`.\n- `id` is generated unless the body names it. A constraint plan_line_item refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_plan_line_items_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Plan Line Items Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/plan-line-items/{row_id}":{"get":{"tags":["cbradio2","Plans"],"summary":"Get one plan line item","description":"One plan line item, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /plan-line-items` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_plan_line_items__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Plan Line Items  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Plans"],"summary":"Update a plan line item","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_plan_line_items__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Plan Line Items  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/plan/{plan_id}/promote":{"post":{"tags":["cbradio2","Plans"],"summary":"Plan -> proposal","description":"Turn a plan the client said yes to into a proposal you can buy against.\n\n⚠️ **Not a copy, and it cannot be.** A plan is priced at a moment and time\npasses before a client agrees; a benchmark line has no rate to carry across\nat all; and a proposal's guarantee — every line names the rate that priced\nit — has to be ESTABLISHED rather than inherited. `proposal_line_item.\nrate_id` is NOT NULL precisely so that guarantee cannot be skipped, which\nis what CJ's 2026-08-26 ruling means in practice: benchmarks price plans,\nproposals require a real card.\n\n**Confirming by exception is the point.** Every card line is re-quoted\nagainst the cards live now; anything inside `tolerance` is carried\nsilently. The reviewer sees only what moved and what has no card — eleven\nthings rather than two hundred.\n\n**The proposal is created with the lines that ARE confirmable**, and the\nrest come back as `outstanding` rather than blocking it (CJ, 2026-08-26).\nA proposal with 17 real lines is useful and honest; the work list lives\nbeside it. That list is also the demand signal into the rate-ingest\npipeline — cards we need, attached to money a client has already agreed to.","operationId":"promote_api_v1_plan__plan_id__promote_post","parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","title":"Plan Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__PromoteRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__PromoteResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/plans":{"get":{"tags":["cbradio2","Plans"],"summary":"List plans","description":"Every plans row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY plans rows — a screen, an export, a count. When you already hold the `id`, use `GET /plans/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `created_at`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `name`.\n- **Filters** —\n  - `?status` (eq) — draft | sent | converted | dead\n  - `?client_id` (eq) — Client\n  - `?cycle` (eq) — Election cycle\n- **Domains** — `status` is one of `draft`, `sent`, `converted`, `dead`; `ad_type` is one of `candidate`, `issue`, `any`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_plans_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"draft | sent | converted | dead","title":"Status"},"description":"draft | sent | converted | dead"},{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Client","title":"Client Id"},"description":"Client"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Election cycle","title":"Cycle"},"description":"Election cycle"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Plans Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Plans"],"summary":"Create a plan","description":"Create a plan. Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a plan that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `name`, `client_id`, `cycle`, `status`, `flight_start`, `flight_end`, `budget`, `created_by`.\n- **Required** — `name`. An explicit `null` counts as omitted.\n- **Domains** — `status` is one of `draft`, `sent`, `converted`, `dead`; `ad_type` is one of `candidate`, `issue`, `any`.\n- `id` is generated unless the body names it. A constraint plan refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_plans_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Plans Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/plans/{row_id}":{"get":{"tags":["cbradio2","Plans"],"summary":"Get one plan","description":"One plan, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /plans` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_plans__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Plans  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Plans"],"summary":"Update a plan","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_plans__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Plans  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/prior-outreach":{"get":{"tags":["cbradio2","Outreach"],"summary":"List imported prior-outreach summaries","description":"Every imported prior-outreach summaries row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY prior-outreach rows — a screen, an export, a count. When you already hold the `station_id`, use `GET /prior-outreach/{station_id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `station_id`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `station_callsign`.\n- **Filters** —\n  - `?station_id` (eq) — One station\n  - `?emailed` (bool) — Airtable: Emailed\n  - `?called` (bool) — Airtable: Called\n  - `?called_for_rate` (bool) — Airtable: Called for rate\n  - `?requested_rate` (bool) — Airtable: Requested Rate\n  - `?rate_card_obtained` (bool) — Airtable: Rate Card Obtained\n  - `?state` (eq) — Two-letter state of the station\n  - `?in_network` (bool) — Only in-network stations\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `station_prior_outreach`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_prior_outreach_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One station","title":"Station Id"},"description":"One station"},{"name":"emailed","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Airtable: Emailed","title":"Emailed"},"description":"Airtable: Emailed"},{"name":"called","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Airtable: Called","title":"Called"},"description":"Airtable: Called"},{"name":"called_for_rate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Airtable: Called for rate","title":"Called For Rate"},"description":"Airtable: Called for rate"},{"name":"requested_rate","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Airtable: Requested Rate","title":"Requested Rate"},"description":"Airtable: Requested Rate"},{"name":"rate_card_obtained","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Airtable: Rate Card Obtained","title":"Rate Card Obtained"},"description":"Airtable: Rate Card Obtained"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state of the station","title":"State"},"description":"Two-letter state of the station"},{"name":"in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only in-network stations","title":"In Network"},"description":"Only in-network stations"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Prior Outreach Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/prior-outreach/{row_id}":{"get":{"tags":["cbradio2","Outreach"],"summary":"Get one imported prior-outreach summarie","description":"One imported prior-outreach summarie, by `station_id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `station_id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /prior-outreach` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `station_id` is a **404**.","operationId":"get_row_api_v1_prior_outreach__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Prior Outreach  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/proposal-line-items":{"get":{"tags":["cbradio2","Proposals"],"summary":"List proposal line items","description":"Every proposal line items row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY proposal-line-items rows — a screen, an export, a count. When you already hold the `id`, use `GET /proposal-line-items/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `proposal_id`. A column this resource does not have is refused, never silently ignored.\n- **Filters** —\n  - `?proposal_id` (eq) — Proposal\n  - `?station_id` (eq) — Station\n  - `?rate_id` (eq) — The rate this line was priced from\n  - `?origin` (eq) — manual | solver | imported\n  - `?network_id` (eq) — Every network line on a network\n  - `?basis` (eq) — card | network — what kind of price\n  - `?lifecycle` (eq) — planning | final, from the line's card\n  - `?has_evidence` (bool) — Lines whose price has a document behind it\n- **Domains** — `origin` is one of `manual`, `solver`, `imported`; `lifecycle` is one of `planning`, `final`; `basis` is one of `card`, `network`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_proposal_line_items_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"proposal_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Proposal","title":"Proposal Id"},"description":"Proposal"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station","title":"Station Id"},"description":"Station"},{"name":"rate_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The rate this line was priced from","title":"Rate Id"},"description":"The rate this line was priced from"},{"name":"origin","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"manual | solver | imported","title":"Origin"},"description":"manual | solver | imported"},{"name":"network_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Every network line on a network","title":"Network Id"},"description":"Every network line on a network"},{"name":"basis","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"card | network — what kind of price","title":"Basis"},"description":"card | network — what kind of price"},{"name":"lifecycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"planning | final, from the line's card","title":"Lifecycle"},"description":"planning | final, from the line's card"},{"name":"has_evidence","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Lines whose price has a document behind it","title":"Has Evidence"},"description":"Lines whose price has a document behind it"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Proposal Line Items Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Proposals"],"summary":"Create a proposal line item","description":"Create a proposal line item. Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a proposal line item that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `proposal_id`, `station_id`, `rate_id`, `daypart_id`, `days_mask`, `spots_per_week`, `weeks`, `unit_gross`, `unit_net`, `origin`, `rationale`.\n- **Required** — `proposal_id`, `station_id`, `rate_id`, `daypart_id`, `days_mask`, `spots_per_week`, `weeks`, `unit_gross`. An explicit `null` counts as omitted.\n- **Domains** — `origin` is one of `manual`, `solver`, `imported`; `lifecycle` is one of `planning`, `final`; `basis` is one of `card`, `network`.\n- `id` is generated unless the body names it. A constraint proposal_line_item refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_proposal_line_items_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Proposal Line Items Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/proposal-line-items/{row_id}":{"get":{"tags":["cbradio2","Proposals"],"summary":"Get one proposal line item","description":"One proposal line item, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /proposal-line-items` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_proposal_line_items__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Proposal Line Items  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Proposals"],"summary":"Update a proposal line item","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_proposal_line_items__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Proposal Line Items  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/proposals":{"get":{"tags":["cbradio2","Proposals"],"summary":"List proposals","description":"Every proposals row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY proposals rows — a screen, an export, a count. When you already hold the `id`, use `GET /proposals/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `created_at`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `name`.\n- **Filters** —\n  - `?status` (eq) — draft | submitted | approved | rejected | won | lost\n  - `?client_id` (eq) — Client\n  - `?cycle` (eq) — Election cycle\n- **Domains** — `status` is one of `draft`, `submitted`, `approved`, `rejected`, `won`, `lost`, `superseded`.\n- **Rows left out by default** — Include proposals that have been RETIRED. Off by default; `POST /proposals/{id}/retire` is the only writer, because a reason is required. Pass `?include_withdrawn=true` to include them. The exclusion is LIST-only: get-one still returns such a row.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_proposals_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"draft | submitted | approved | rejected | won | lost","title":"Status"},"description":"draft | submitted | approved | rejected | won | lost"},{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Client","title":"Client Id"},"description":"Client"},{"name":"cycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Election cycle","title":"Cycle"},"description":"Election cycle"},{"name":"include_withdrawn","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Include proposals that have been RETIRED. Off by default; `POST /proposals/{id}/retire` is the only writer, because a reason is required.","title":"Include Withdrawn"},"description":"Include proposals that have been RETIRED. Off by default; `POST /proposals/{id}/retire` is the only writer, because a reason is required."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Proposals Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Proposals"],"summary":"Create a proposal","description":"Create a proposal. Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a proposal that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `name`, `client_id`, `cycle`, `status`, `flight_start`, `flight_end`, `budget`, `created_by`.\n- **Required** — `name`. An explicit `null` counts as omitted.\n- **Domains** — `status` is one of `draft`, `submitted`, `approved`, `rejected`, `won`, `lost`, `superseded`.\n- `id` is generated unless the body names it. A constraint proposal refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_proposals_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Proposals Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/proposals/{proposal_id}/network-lines":{"post":{"tags":["cbradio2","Networks"],"summary":"Add a network line to a proposal, priced from its newest rate","description":"Price it, freeze it, name the document. The line's `unit_gross` is\nthe network's published price at this moment; the rate it came from is\n`network_rate_id`, which is what makes it reconstructable — the same\nguarantee a station line gets from `rate_id`.\n\nA refusal from pricing is a **422** here rather than a 200: `/quote`\nanswers a question, this creates a row, and a row cannot be created from\nno price.","operationId":"add_network_line_api_v1_proposals__proposal_id__network_lines_post","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__NetworkLineRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Network Line Api V1 Proposals  Proposal Id  Network Lines Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/proposals/{proposal_id}/reinstate":{"post":{"tags":["cbradio2","Proposals"],"summary":"Put a retired proposal back in the book","description":"Reverse a retirement, clearing the reason, the name and the moment from\nthe row and appending the act to `retirement_event`. A reason is\nrequired.","operationId":"reinstate_proposal_api_v1_proposals__proposal_id__reinstate_post","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__Reinstatement"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reinstate Proposal Api V1 Proposals  Proposal Id  Reinstate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/proposals/{proposal_id}/retire":{"post":{"tags":["cbradio2","Proposals"],"summary":"Take a proposal out of the book","description":"Retire a proposal — it leaves the list and every pipeline screen.\n\n⚠️ **This is not a `status` value.** `status` is where the proposal got to\nwith the client; retirement is whether we count it at all. The placement\nverification proposal is genuinely `won` — it *was* placed — and folding\nthe second fact into the first would lose the first.\n\n⚠️ It does **not** retire the buy placed from it, and `buy.proposal_id`\nstays UNIQUE either way, so a retired proposal still cannot be placed a\nsecond time.","operationId":"retire_proposal_api_v1_proposals__proposal_id__retire_post","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__Retirement"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Retire Proposal Api V1 Proposals  Proposal Id  Retire Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/proposals/{proposal_id}/retirement":{"patch":{"tags":["cbradio2","Proposals"],"summary":"Correct a standing retirement's reason or actor","description":"Fix a proposal's retirement record without un-retiring it. Same rules as\nthe buy endpoint — `deleted_at` untouched, old values kept on a\n`corrected` ledger row.","operationId":"correct_proposal_retirement_api_v1_proposals__proposal_id__retirement_patch","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__Correction"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Correct Proposal Retirement Api V1 Proposals  Proposal Id  Retirement Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/proposals/{proposal_id}/retirement-history":{"get":{"tags":["cbradio2","Proposals"],"summary":"A proposal's retirement ledger","operationId":"proposal_retirement_history_api_v1_proposals__proposal_id__retirement_history_get","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Proposal Retirement History Api V1 Proposals  Proposal Id  Retirement History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/proposals/{row_id}":{"get":{"tags":["cbradio2","Proposals"],"summary":"Get one proposal","description":"One proposal, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /proposals` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.\n- A row the list leaves out is still returned here: you named it, and 404 would say it never existed rather than that it was withdrawn.","operationId":"get_row_api_v1_proposals__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Proposals  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Proposals"],"summary":"Update a proposal","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_proposals__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Proposals  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/quote":{"post":{"tags":["cbradio2","Quote"],"summary":"What do we quote for this station?","description":"Price one station, or say why we will not.\n\nA station id we do not hold is a **404**, not a refusal — the caller asked\nabout one station and needs to know the id is wrong rather than believing\nthe station is unpriceable. The batch endpoint deliberately differs.","operationId":"quote_api_v1_quote_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__QuoteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__Quote"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/quote/batch":{"post":{"tags":["cbradio2","Quote"],"summary":"The same spot, priced across many stations","description":"One row per requested station, in the order asked, duplicates preserved.\n\n**An unknown station is a refusal here, not a 404.** The caller is rendering\na list; one stale id must not blank the screen. The single-quote endpoint\n404s for the opposite reason. The asymmetry is deliberate and this is the\nnote that says so.","operationId":"quote_batch_api_v1_quote_batch_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__BatchQuoteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbradio2__Quote"},"title":"Response Quote Batch Api V1 Quote Batch Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/quote/network":{"post":{"tags":["cbradio2","Networks"],"summary":"What does this network cost?","description":"A refusal is a 200 with `ok: false`, as on `/quote`; an unknown\nnetwork is a 404, as an unknown station is there.","operationId":"quote_network_api_v1_quote_network_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__NetworkQuoteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__NetworkQuote"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/rate-cards":{"get":{"tags":["cbradio2","Rate cards"],"summary":"List rate cards","description":"Every rate cards row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY rate-cards rows — a screen, an export, a count. When you already hold the `id`, use `GET /rate-cards/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `station_id`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `station_callsign`, `notes`.\n- **Filters** —\n  - `?station_id` (eq) — Station this card belongs to\n  - `?year` (int) — Card year\n  - `?price_source` (eq) — katz | salem | iheart | direct | human | observed\n  - `?lifecycle` (eq) — planning | final\n  - `?review_status` (eq) — unreviewed | needs_review | approved | rejected\n  - `?callsign` (like) — Station callsign, partial\n  - `?state` (eq) — Two-letter state of the station\n  - `?review_scope` (eq) — in_scope | out_of_scope\n  - `?has_conflicts` (bool) — Cards missing at least one stated rate\n  - `?has_blocking_conflicts` (bool) — Cards with a same-product price disagreement\n  - `?has_live_rates` (bool) — Cards holding at least one rate that has not been withdrawn\n- **Domains** — `review_status` is one of `unreviewed`, `needs_review`, `approved`, `rejected`; `lifecycle` is one of `planning`, `final`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_rate_cards_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station this card belongs to","title":"Station Id"},"description":"Station this card belongs to"},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Card year","title":"Year"},"description":"Card year"},{"name":"price_source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"katz | salem | iheart | direct | human | observed","title":"Price Source"},"description":"katz | salem | iheart | direct | human | observed"},{"name":"lifecycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"planning | final","title":"Lifecycle"},"description":"planning | final"},{"name":"review_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"unreviewed | needs_review | approved | rejected","title":"Review Status"},"description":"unreviewed | needs_review | approved | rejected"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station callsign, partial","title":"Callsign"},"description":"Station callsign, partial"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state of the station","title":"State"},"description":"Two-letter state of the station"},{"name":"review_scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"in_scope | out_of_scope","title":"Review Scope"},"description":"in_scope | out_of_scope"},{"name":"has_conflicts","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cards missing at least one stated rate","title":"Has Conflicts"},"description":"Cards missing at least one stated rate"},{"name":"has_blocking_conflicts","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cards with a same-product price disagreement","title":"Has Blocking Conflicts"},"description":"Cards with a same-product price disagreement"},{"name":"has_live_rates","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cards holding at least one rate that has not been withdrawn","title":"Has Live Rates"},"description":"Cards holding at least one rate that has not been withdrawn"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Rate Cards Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/rate-cards/{row_id}":{"get":{"tags":["cbradio2","Rate cards"],"summary":"Get one rate card","description":"One rate card, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /rate-cards` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_rate_cards__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Rate Cards  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Rate cards"],"summary":"Update a rate card","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_rate_cards__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Rate Cards  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/rate-policies":{"get":{"tags":["cbradio2","Rate policies"],"summary":"Every policy version, newest first","description":"Every version of every pricing policy, newest first.\n\n**Use this endpoint when** you want the HISTORY — which versions exist, who\ncreated each and when. For the rules a quote is priced under right now, ask\n`GET /rate-policies/{name}/active`; for the rules a PAST quote was priced\nunder, `GET /rate-policies/by-digest/{digest}`.\n\nVersions are immutable and every edit is a new row forever, so this is the\none table guaranteed to grow without bound — it pages like every generated\nlist (`limit`, default 50, max 500; `offset`; unpaged total in\n`X-Total-Count`) and refuses an unknown query parameter with a 422 rather\nthan answering the whole table.","operationId":"list_policies_api_v1_rate_policies_get","parameters":[{"name":"name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Policies Api V1 Rate Policies Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Rate policies"],"summary":"Store a new draft version","description":"Store a new version of a policy document. Answers **201**.\n\n**Use this endpoint when** the business has changed the rules. A version is\nimmutable, so editing means creating: there is no PUT or PATCH here, because\na quote records the digest it was priced under and an in-place edit would\nsilently rewrite what a past quote means.\n\nA stored version is a **draft** — it prices nothing until\n`POST /rate-policies/{name}/{version}/activate`. An invalid document is a\n**422** carrying the list of problems; call `POST /rate-policies/check`\nfirst if you want those problems without the refusal (an editor validating\non every keystroke wants a result, not a 4xx per keystroke).","operationId":"create_policy_api_v1_rate_policies_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__PolicyDraft"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Policy Api V1 Rate Policies Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/rate-policies/by-digest/{digest}":{"get":{"tags":["cbradio2","Rate policies"],"summary":"The rules a past quote was priced under","description":"Turn a quote's stamp back into the actual ordering, months later.\n\nThis is why versions are immutable rather than edited: without it, \"why did\nwe price it that way in March\" has no answer once the rules have moved.","operationId":"get_by_digest_api_v1_rate_policies_by_digest__digest__get","parameters":[{"name":"digest","in":"path","required":true,"schema":{"type":"string","title":"Digest"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get By Digest Api V1 Rate Policies By Digest  Digest  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/rate-policies/check":{"post":{"tags":["cbradio2","Rate policies"],"summary":"Validate a document without storing it","description":"Parse + evaluate every expression against a probe row.\n\nDeliberately not an error response when the document is bad: an editor\ncalling this on every keystroke wants a *result*, and a 4xx per keystroke is\na worse experience and a noisier log.","operationId":"check_policy_api_v1_rate_policies_check_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__PolicyDraft"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__CheckResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/rate-policies/{name}/active":{"get":{"tags":["cbradio2","Rate policies"],"summary":"The policy quotes are priced under now","description":"The version quotes are being priced under right now, with its document.\n\n**Use this endpoint when** you need to show or reason about the live rules —\nthe answer carries the `stamp` (name, version, digest) and the raw document.\nThe STORE is what prices; the seed file shipped in the repo is history, and\nthe two are allowed to differ once an operator has edited the rules through\nthis API.\n\nNo active version for `name` is a **404**, and it means every quote is\nrefusing until one is activated.","operationId":"get_active_api_v1_rate_policies__name__active_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Active Api V1 Rate Policies  Name  Active Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/rate-policies/{name}/{version}":{"get":{"tags":["cbradio2","Rate policies"],"summary":"One version, with its document","description":"One named version of a policy, with its document.\n\n**Use this endpoint when** you already know which version you want — from\nthe list, or from the `stamp` on a quote. If what you hold is a quote's\ndigest rather than a version, use `GET /rate-policies/by-digest/{digest}`,\nwhich answers the same question from the other direction.\n\nUnknown name or version is a **404**.","operationId":"get_version_api_v1_rate_policies__name___version__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"version","in":"path","required":true,"schema":{"type":"string","title":"Version"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Version Api V1 Rate Policies  Name   Version  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/rate-policies/{name}/{version}/activate":{"post":{"tags":["cbradio2","Rate policies"],"summary":"Make this version live","description":"Make one version live.\n\n`activated_by` is REQUIRED (cbradio2#13). This write changes how every\nsubsequent quote is priced, and it was possible to make it anonymously. It\nis still a caller-supplied string — the honest position is that this\nrecords an intent, not an authenticated identity, and it stays that way\nuntil `CBRADIO2_API_KEY` is turned on (cbradio2#1, an operator decision).\nRequiring it does not make it true; it makes the blank case impossible.","operationId":"activate_api_v1_rate_policies__name___version__activate_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"version","in":"path","required":true,"schema":{"type":"string","title":"Version"}},{"name":"activated_by","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Who is making this change. Required.","title":"Activated By"},"description":"Who is making this change. Required."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Activate Api V1 Rate Policies  Name   Version  Activate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/rates":{"get":{"tags":["cbradio2","Rates"],"summary":"List rates","description":"Every rates row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY rates rows — a screen, an export, a count. When you already hold the `id`, use `GET /rates/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `station_id`. A column this resource does not have is refused, never silently ignored.\n- **Filters** —\n  - `?station_id` (eq) — Station\n  - `?callsign` (like) — Station callsign, partial\n  - `?state` (eq) — Two-letter state of the station\n  - `?rate_card_id` (eq) — Card\n  - `?entered_by` (eq) — Rates one person typed\n  - `?lifecycle` (eq) — planning | final, from the rate's card\n  - `?price_source` (eq) — The feed that supplied the card\n  - `?has_evidence` (bool) — Only rates carrying a written-evidence reference\n  - `?daypart` (eq) — Daypart id\n  - `?duration` (int) — Spot length in seconds\n  - `?ad_type` (eq) — candidate | issue | any\n  - `?days_mask` (mask) — Days the rate must COVER, as a 7-bit mask: 1=Mon … 64=Sun, 31=M-F, 96=weekend, 127=all week. A coverage test, not equality — `31` returns every rate buyable across all of Mon-Fri, which is the same test the selection policy's `days_covered` rule applies.\n- **Rows left out by default** — Include rates that have been withdrawn. Off by default — a withdrawn rate cannot be quoted, so a screen that showed one would be offering a price the engine refuses. Pass `?include_withdrawn=true` to include them. The exclusion is LIST-only: get-one still returns such a row.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.\n- Read-only. Nothing here writes `rate`; it is what the ingest pipeline landed.","operationId":"list_rows_api_v1_rates_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station","title":"Station Id"},"description":"Station"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station callsign, partial","title":"Callsign"},"description":"Station callsign, partial"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state of the station","title":"State"},"description":"Two-letter state of the station"},{"name":"rate_card_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Card","title":"Rate Card Id"},"description":"Card"},{"name":"entered_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Rates one person typed","title":"Entered By"},"description":"Rates one person typed"},{"name":"lifecycle","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"planning | final, from the rate's card","title":"Lifecycle"},"description":"planning | final, from the rate's card"},{"name":"price_source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The feed that supplied the card","title":"Price Source"},"description":"The feed that supplied the card"},{"name":"has_evidence","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only rates carrying a written-evidence reference","title":"Has Evidence"},"description":"Only rates carrying a written-evidence reference"},{"name":"daypart","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Daypart id","title":"Daypart"},"description":"Daypart id"},{"name":"duration","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Spot length in seconds","title":"Duration"},"description":"Spot length in seconds"},{"name":"ad_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"candidate | issue | any","title":"Ad Type"},"description":"candidate | issue | any"},{"name":"days_mask","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Days the rate must COVER, as a 7-bit mask: 1=Mon … 64=Sun, 31=M-F, 96=weekend, 127=all week. A coverage test, not equality — `31` returns every rate buyable across all of Mon-Fri, which is the same test the selection policy's `days_covered` rule applies.","title":"Days Mask"},"description":"Days the rate must COVER, as a 7-bit mask: 1=Mon … 64=Sun, 31=M-F, 96=weekend, 127=all week. A coverage test, not equality — `31` returns every rate buyable across all of Mon-Fri, which is the same test the selection policy's `days_covered` rule applies."},{"name":"include_withdrawn","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Include rates that have been withdrawn. Off by default — a withdrawn rate cannot be quoted, so a screen that showed one would be offering a price the engine refuses.","title":"Include Withdrawn"},"description":"Include rates that have been withdrawn. Off by default — a withdrawn rate cannot be quoted, so a screen that showed one would be offering a price the engine refuses."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Rates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/rates/{row_id}":{"get":{"tags":["cbradio2","Rates"],"summary":"Get one rate","description":"One rate, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /rates` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.\n- A row the list leaves out is still returned here: you named it, and 404 would say it never existed rather than that it was withdrawn.","operationId":"get_row_api_v1_rates__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Rates  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/station-contacts":{"get":{"tags":["cbradio2","Stations"],"summary":"List station contacts","description":"Every station contacts row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY station-contacts rows — a screen, an export, a count. When you already hold the `id`, use `GET /station-contacts/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `station_id`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `name`, `email`, `station_callsign`.\n- **Filters** —\n  - `?station_id` (eq) — Contacts for one station\n  - `?role` (eq) — general | gm | sales_manager | general_sales_manager | program_director | billing | traffic\n  - `?source` (eq) — manual | srm | airtable | precision_trak | nielsen | fcc\n  - `?callsign` (like) — Station callsign, partial\n  - `?state` (eq) — Two-letter state of the station\n  - `?in_network` (bool) — Only contacts at in-network stations\n  - `?has_email` (bool) — Only contacts we can email\n- **Domains** — `role` is one of `general`, `gm`, `sales_manager`, `general_sales_manager`, `program_director`, `billing`, `traffic`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_station_contacts_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"station_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Contacts for one station","title":"Station Id"},"description":"Contacts for one station"},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"general | gm | sales_manager | general_sales_manager | program_director | billing | traffic","title":"Role"},"description":"general | gm | sales_manager | general_sales_manager | program_director | billing | traffic"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"manual | srm | airtable | precision_trak | nielsen | fcc","title":"Source"},"description":"manual | srm | airtable | precision_trak | nielsen | fcc"},{"name":"callsign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Station callsign, partial","title":"Callsign"},"description":"Station callsign, partial"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state of the station","title":"State"},"description":"Two-letter state of the station"},{"name":"in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only contacts at in-network stations","title":"In Network"},"description":"Only contacts at in-network stations"},{"name":"has_email","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only contacts we can email","title":"Has Email"},"description":"Only contacts we can email"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Station Contacts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Stations"],"summary":"Create a station contact","description":"Create a station contact. Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a station contact that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `station_id`, `role`, `name`, `email`, `phone`, `title`, `updated_by`.\n- **Required** — `station_id`, `role`. An explicit `null` counts as omitted.\n- **`updated_by` is mandatory and non-empty** on every write. A hand-maintained row's provenance is who touched it last, and an unsigned correction is the half of the record that gets asked for in six months.\n- **Domains** — `role` is one of `general`, `gm`, `sales_manager`, `general_sales_manager`, `program_director`, `billing`, `traffic`.\n- A collision on (`station_id`, `role`) is a **409 carrying the row already there**, not a 500.\n- `id` is generated unless the body names it. A constraint station_contact refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_station_contacts_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Station Contacts Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/station-contacts/{row_id}":{"get":{"tags":["cbradio2","Stations"],"summary":"Get one station contact","description":"One station contact, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /station-contacts` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_station_contacts__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Station Contacts  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Stations"],"summary":"Update a station contact","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_station_contacts__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Station Contacts  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/stations":{"get":{"tags":["cbradio2","Stations"],"summary":"List stations","description":"Every stations row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY stations rows — a screen, an export, a count. When you already hold the `id`, use `GET /stations/{id}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `callsign`. A column this resource does not have is refused, never silently ignored.\n- **Search** — `?q=` matches, case-insensitively, on: `callsign`, `licensee`, `format`, `county`.\n- **Filters** —\n  - `?state` (eq) — Two-letter state code\n  - `?band` (eq) — AM or FM\n  - `?in_network` (bool) — Only in-network stations\n  - `?tier` (eq) — in_network | salem | out_of_network | prospect\n  - `?format` (eq) — Programming format\n  - `?ownership_group` (eq) — Owner group\n  - `?rep_firm` (eq) — National rep firm\n  - `?status` (eq) — active | inactive | silent | divested\n  - `?sales_status` (eq) — prospect | active | lapsed | do_not_call\n  - `?review_scope` (eq) — in_scope | out_of_scope\n  - `?dma` (like) — Nielsen DMA\n- **Domains** — `sales_status` is one of `prospect`, `active`, `lapsed`, `do_not_call`.\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_stations_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Two-letter state code","title":"State"},"description":"Two-letter state code"},{"name":"band","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"AM or FM","title":"Band"},"description":"AM or FM"},{"name":"in_network","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only in-network stations","title":"In Network"},"description":"Only in-network stations"},{"name":"tier","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"in_network | salem | out_of_network | prospect","title":"Tier"},"description":"in_network | salem | out_of_network | prospect"},{"name":"format","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Programming format","title":"Format"},"description":"Programming format"},{"name":"ownership_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Owner group","title":"Ownership Group"},"description":"Owner group"},{"name":"rep_firm","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"National rep firm","title":"Rep Firm"},"description":"National rep firm"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"active | inactive | silent | divested","title":"Status"},"description":"active | inactive | silent | divested"},{"name":"sales_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"prospect | active | lapsed | do_not_call","title":"Sales Status"},"description":"prospect | active | lapsed | do_not_call"},{"name":"review_scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"in_scope | out_of_scope","title":"Review Scope"},"description":"in_scope | out_of_scope"},{"name":"dma","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Nielsen DMA","title":"Dma"},"description":"Nielsen DMA"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Stations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/stations/{row_id}":{"get":{"tags":["cbradio2","Stations"],"summary":"Get one station","description":"One station, by `id`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `id` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /stations` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `id` is a **404**.","operationId":"get_row_api_v1_stations__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Stations  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Stations"],"summary":"Update a station","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_stations__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Stations  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/target-states":{"get":{"tags":["cbradio2","Stations"],"summary":"List target states","description":"Every target states row, filtered and paginated. Returns a bare JSON array; timestamps are UTC.\n\n**Use this endpoint when** you want MANY target-states rows — a screen, an export, a count. When you already hold the `state`, use `GET /target-states/{state}` instead.\n\n- **Paging** — `limit` (default 50, maximum 500) and `offset`. Asking for more than the maximum is a 422, never a quietly shortened array. The UNPAGED total is in the `X-Total-Count` response header, so a screen can tell 50 results from 50 of 1,506.\n- **Ordering** — `?order=<column>`, `-` prefix for descending. Default `state`. A column this resource does not have is refused, never silently ignored.\n- **Filters** —\n  - `?active` (bool) — Only states currently targeted\n  - `?source` (eq) — v1_default | manual\n- An unknown query parameter is a **422** naming what this resource accepts, because a filter that was ignored is a promise about the answer that was not kept.","operationId":"list_rows_api_v1_target_states_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search","title":"Q"},"description":"Free-text search"},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Column to order by; prefix with - for descending","title":"Order"},"description":"Column to order by; prefix with - for descending"},{"name":"active","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only states currently targeted","title":"Active"},"description":"Only states currently targeted"},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"v1_default | manual","title":"Source"},"description":"v1_default | manual"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Rows Api V1 Target States Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"post":{"tags":["cbradio2","Stations"],"summary":"Create a target state","description":"Create a target state. Answers **201** with the row as stored.\n\n**Use this endpoint when** a person is adding a target state that no feed will bring — most tables here are written by the ingest pipeline, and creating a row through the API is the exception, not the norm.\n\n- **Writable columns** (an allowlist, not a filter — anything else is a 422 rather than a silently dropped key): `state`, `active`, `note`, `added_by`.\n- **Required** — `state`. An explicit `null` counts as omitted.\n- **`added_by` is mandatory and non-empty** on every write. A hand-maintained row's provenance is who touched it last, and an unsigned correction is the half of the record that gets asked for in six months.\n- A collision on (`state`) is a **409 carrying the row already there**, not a 500.\n- `state` is generated unless the body names it. A constraint target_state refuses is a 422 quoting it, and nothing is written.\n- There is **no DELETE** anywhere on this API: the fleet rule is soft delete, and 'gone' is a status a person set, not a row that stopped existing.","operationId":"create_row_api_v1_target_states_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Row Api V1 Target States Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/api/v1/target-states/{row_id}":{"get":{"tags":["cbradio2","Stations"],"summary":"Get one target state","description":"One target state, by `state`. Timestamps are UTC.\n\n**Use this endpoint when** you already hold the `state` — after a write, or from a row a list read returned. To find a row you cannot name, filter `GET /target-states` instead.\n\n- Takes **no query parameters at all**: there is nothing here to filter, order or page, and `?fields=` or a typo'd filter would otherwise read as supported. Any parameter is a 422.\n- No such `state` is a **404**.","operationId":"get_row_api_v1_target_states__row_id__get","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Row Api V1 Target States  Row Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}},"patch":{"tags":["cbradio2","Stations"],"summary":"Update a target state","description":"Update a row, and refuse to lie about whether it worked.\n\ncbradio2#14, from the cbrural lane. This was an UPDATE and then a\nSEPARATE SELECT — two independent HTTP posts to cbdatabase with no\ntransaction between them — so two reviewers deciding one card\ninterleaved like this:\n\n    A: UPDATE approved  by a@stj.media\n    B: UPDATE rejected  by b@stj.media\n    A: SELECT -> rejected / b@stj.media\n\n**A got a 200 whose body was B's decision, under B's name**, and their\nqueue repaints the row from that response — so A watched their Approve\nsettle into someone else's Reject with nothing anywhere saying so. Not\nan exotic interleaving: two reviewers open the same queue sorted the\nsame way and start at the top.\n\nThere are no transactions to reach for here, so the write carries its\nown evidence instead. The server picks the `updated_at` it is about to\nwrite, and then checks that the row it reads back still carries it. If\nit does not, someone else's write landed in between and the caller gets\na **409 with the row as it now stands** rather than a 200 with a\nstranger's decision. `If-Match` closes the front half of the same\nwindow, refusing the UPDATE outright if the row already moved.\n\nThe lost update is still a lost update — but it is now a lost update\nthat both writers are told about, which is the difference between a\nrace and a silent one.","operationId":"update_row_api_v1_target_states__row_id__patch","parameters":[{"name":"row_id","in":"path","required":true,"schema":{"type":"string","title":"Row Id"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since.","title":"If-Match"},"description":"The `updated_at` you last saw. The write is refused with a 409 if the row has moved since."},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Row Api V1 Target States  Row Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HTTPValidationError"}}}}}}},"/cbradio2/health":{"get":{"tags":["cbradio2","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HealthResponse"}}}}}}},"/cbradio2/health/detailed":{"get":{"tags":["cbradio2","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__HealthResponse"}}}}}}},"/cbradio2/prime":{"get":{"tags":["cbradio2","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__PrimeResponse"}}}}}}},"/cbradio2/status":{"get":{"tags":["cbradio2","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbradio2__StatusResponse"}}}}}}},"/cbrouter/api/v1/backup/create":{"post":{"tags":["cbrouter","Backup"],"summary":"Create sysupgrade backup","description":"Create a sysupgrade backup.","operationId":"create_backup_api_v1_backup_create_post","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Backup Api V1 Backup Create Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/files/list":{"get":{"tags":["cbrouter","Files"],"summary":"List files in a directory","description":"List files in a directory.","operationId":"list_files_api_v1_files_list_get","parameters":[{"name":"path","in":"query","required":false,"schema":{"type":"string","default":"/etc/","title":"Path"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Files Api V1 Files List Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/files/read":{"get":{"tags":["cbrouter","Files"],"summary":"Read a file from the router","description":"Read a file from the router.","operationId":"read_file_api_v1_files_read_get","parameters":[{"name":"path","in":"query","required":true,"schema":{"type":"string","title":"Path"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Read File Api V1 Files Read Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/files/stat":{"get":{"tags":["cbrouter","Files"],"summary":"Get file metadata","description":"Get file metadata.","operationId":"stat_file_api_v1_files_stat_get","parameters":[{"name":"path","in":"query","required":true,"schema":{"type":"string","title":"Path"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Stat File Api V1 Files Stat Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/files/write":{"put":{"tags":["cbrouter","Files"],"summary":"Write a file to the router","description":"Write content to a file on the router.","operationId":"write_file_api_v1_files_write_put","parameters":[{"name":"path","in":"query","required":true,"schema":{"type":"string","title":"Path"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__WriteFileRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Write File Api V1 Files Write Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/firewall/forwards":{"get":{"tags":["cbrouter","Firewall"],"summary":"Get firewall forwarding rules","description":"Get firewall forwarding rules.","operationId":"firewall_forwards_api_v1_firewall_forwards_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Firewall Forwards Api V1 Firewall Forwards Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/firewall/redirects":{"get":{"tags":["cbrouter","Firewall"],"summary":"Get firewall port redirects","description":"Get firewall port forwards/redirects.","operationId":"firewall_redirects_api_v1_firewall_redirects_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Firewall Redirects Api V1 Firewall Redirects Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/firewall/rules":{"get":{"tags":["cbrouter","Firewall"],"summary":"Get nftables ruleset","description":"Get full nftables ruleset.","operationId":"firewall_rules_api_v1_firewall_rules_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Firewall Rules Api V1 Firewall Rules Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/firewall/zones":{"get":{"tags":["cbrouter","Firewall"],"summary":"Get firewall zones","description":"Get firewall zones.","operationId":"firewall_zones_api_v1_firewall_zones_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Firewall Zones Api V1 Firewall Zones Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/network/arp":{"get":{"tags":["cbrouter","Network"],"summary":"Get ARP table","description":"Get ARP table.","operationId":"arp_api_v1_network_arp_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Arp Api V1 Network Arp Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/network/conntrack":{"get":{"tags":["cbrouter","Network"],"summary":"Get conntrack entry count","description":"Get conntrack entry count.","operationId":"conntrack_api_v1_network_conntrack_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Conntrack Api V1 Network Conntrack Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/network/dhcp-leases":{"get":{"tags":["cbrouter","Network"],"summary":"Get DHCP leases","description":"Get DHCP leases.","operationId":"dhcp_leases_api_v1_network_dhcp_leases_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Dhcp Leases Api V1 Network Dhcp Leases Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/network/interfaces":{"get":{"tags":["cbrouter","Network"],"summary":"Get network interfaces","description":"Get all network interfaces.","operationId":"interfaces_api_v1_network_interfaces_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Interfaces Api V1 Network Interfaces Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/network/listeners":{"get":{"tags":["cbrouter","Network"],"summary":"Get listening ports","description":"Get listening ports.","operationId":"listeners_api_v1_network_listeners_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Listeners Api V1 Network Listeners Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/network/routes":{"get":{"tags":["cbrouter","Network"],"summary":"Get routing table","description":"Get routing table.","operationId":"routes_api_v1_network_routes_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Routes Api V1 Network Routes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/packages/":{"get":{"tags":["cbrouter","Packages"],"summary":"List installed packages","description":"List installed packages.","operationId":"list_installed_api_v1_packages__get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Installed Api V1 Packages  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/packages/install/{name}":{"post":{"tags":["cbrouter","Packages"],"summary":"Install a package","description":"Install a package.","operationId":"install_package_api_v1_packages_install__name__post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Install Package Api V1 Packages Install  Name  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/packages/upgradable":{"get":{"tags":["cbrouter","Packages"],"summary":"List upgradable packages","description":"List packages with available upgrades.","operationId":"list_upgradable_api_v1_packages_upgradable_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Upgradable Api V1 Packages Upgradable Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/packages/upgrade/{name}":{"post":{"tags":["cbrouter","Packages"],"summary":"Upgrade a package","description":"Upgrade a package.","operationId":"upgrade_package_api_v1_packages_upgrade__name__post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Upgrade Package Api V1 Packages Upgrade  Name  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/packages/{name}":{"delete":{"tags":["cbrouter","Packages"],"summary":"Remove a package","description":"Remove a package.","operationId":"remove_package_api_v1_packages__name__delete","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Remove Package Api V1 Packages  Name  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/services/":{"get":{"tags":["cbrouter","Services"],"summary":"List init.d services","description":"List all init.d services.","operationId":"list_services_api_v1_services__get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Services Api V1 Services  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/services/{name}/{action}":{"post":{"tags":["cbrouter","Services"],"summary":"Control a service","description":"Control a service (start/stop/restart/enable/disable).","operationId":"service_action_api_v1_services__name___action__post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"action","in":"path","required":true,"schema":{"type":"string","title":"Action"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Service Action Api V1 Services  Name   Action  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/system/board":{"get":{"tags":["cbrouter","System"],"summary":"Get board information","description":"Get board/hardware information.","operationId":"board_info_api_v1_system_board_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Board Info Api V1 System Board Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/system/disk":{"get":{"tags":["cbrouter","System"],"summary":"Get disk usage","description":"Get disk usage.","operationId":"system_disk_api_v1_system_disk_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response System Disk Api V1 System Disk Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/system/dmesg":{"get":{"tags":["cbrouter","System"],"summary":"Get kernel log","description":"Get kernel log.","operationId":"system_dmesg_api_v1_system_dmesg_get","parameters":[{"name":"lines","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Lines"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response System Dmesg Api V1 System Dmesg Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/system/info":{"get":{"tags":["cbrouter","System"],"summary":"Get system information","description":"Get combined system information (board + status).","operationId":"system_info_api_v1_system_info_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response System Info Api V1 System Info Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/system/logs":{"get":{"tags":["cbrouter","System"],"summary":"Get system log","description":"Get system log.","operationId":"system_logs_api_v1_system_logs_get","parameters":[{"name":"lines","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Lines"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response System Logs Api V1 System Logs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/system/memory":{"get":{"tags":["cbrouter","System"],"summary":"Get memory usage","description":"Get memory usage.","operationId":"system_memory_api_v1_system_memory_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response System Memory Api V1 System Memory Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/system/status":{"get":{"tags":["cbrouter","System"],"summary":"Get system status","description":"Get system status (uptime, load, memory).","operationId":"system_status_api_v1_system_status_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response System Status Api V1 System Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/uci/apply":{"post":{"tags":["cbrouter","UCI"],"summary":"Apply UCI changes with rollback timer","description":"Apply UCI changes with automatic rollback timer.","operationId":"uci_apply_api_v1_uci_apply_post","parameters":[{"name":"timeout","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Timeout"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Uci Apply Api V1 Uci Apply Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/uci/confirm":{"post":{"tags":["cbrouter","UCI"],"summary":"Confirm applied UCI changes","description":"Confirm applied UCI changes (cancel rollback).","operationId":"uci_confirm_api_v1_uci_confirm_post","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Uci Confirm Api V1 Uci Confirm Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/uci/{config}":{"get":{"tags":["cbrouter","UCI"],"summary":"Get UCI config values","description":"Get UCI config values.","operationId":"uci_get_api_v1_uci__config__get","parameters":[{"name":"config","in":"path","required":true,"schema":{"type":"string","title":"Config"}},{"name":"section","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Section"}},{"name":"option","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Option"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Uci Get Api V1 Uci  Config  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}},"put":{"tags":["cbrouter","UCI"],"summary":"Set UCI values","description":"Set UCI values.","operationId":"uci_set_api_v1_uci__config__put","parameters":[{"name":"config","in":"path","required":true,"schema":{"type":"string","title":"Config"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__UCISetRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Uci Set Api V1 Uci  Config  Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}},"post":{"tags":["cbrouter","UCI"],"summary":"Add UCI section","description":"Add a new UCI section.","operationId":"uci_add_api_v1_uci__config__post","parameters":[{"name":"config","in":"path","required":true,"schema":{"type":"string","title":"Config"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__UCIAddRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Uci Add Api V1 Uci  Config  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/uci/{config}/changes":{"get":{"tags":["cbrouter","UCI"],"summary":"Get pending UCI changes","description":"Get pending UCI changes.","operationId":"uci_changes_api_v1_uci__config__changes_get","parameters":[{"name":"config","in":"path","required":true,"schema":{"type":"string","title":"Config"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Uci Changes Api V1 Uci  Config  Changes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/uci/{config}/commit":{"post":{"tags":["cbrouter","UCI"],"summary":"Commit UCI changes","description":"Commit pending UCI changes.","operationId":"uci_commit_api_v1_uci__config__commit_post","parameters":[{"name":"config","in":"path","required":true,"schema":{"type":"string","title":"Config"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Uci Commit Api V1 Uci  Config  Commit Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/uci/{config}/export":{"get":{"tags":["cbrouter","UCI"],"summary":"Export UCI config","description":"Export UCI config in text format.","operationId":"uci_export_api_v1_uci__config__export_get","parameters":[{"name":"config","in":"path","required":true,"schema":{"type":"string","title":"Config"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Uci Export Api V1 Uci  Config  Export Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/uci/{config}/show":{"get":{"tags":["cbrouter","UCI"],"summary":"Show UCI config","description":"Show UCI config in key=value format.","operationId":"uci_show_api_v1_uci__config__show_get","parameters":[{"name":"config","in":"path","required":true,"schema":{"type":"string","title":"Config"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Uci Show Api V1 Uci  Config  Show Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/uci/{config}/{section}":{"delete":{"tags":["cbrouter","UCI"],"summary":"Delete UCI section or option","description":"Delete a UCI section or option.","operationId":"uci_delete_api_v1_uci__config___section__delete","parameters":[{"name":"config","in":"path","required":true,"schema":{"type":"string","title":"Config"}},{"name":"section","in":"path","required":true,"schema":{"type":"string","title":"Section"}},{"name":"option","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Option"}},{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Uci Delete Api V1 Uci  Config   Section  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/vpn/rotate":{"post":{"tags":["cbrouter","VPN"],"summary":"Rotate VPN profile","description":"Rotate to a random VPN profile and restart.","operationId":"vpn_rotate_api_v1_vpn_rotate_post","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Vpn Rotate Api V1 Vpn Rotate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/vpn/status":{"get":{"tags":["cbrouter","VPN"],"summary":"Get VPN status","description":"Get VPN connection status.","operationId":"vpn_status_api_v1_vpn_status_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Vpn Status Api V1 Vpn Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/wireless/clients":{"get":{"tags":["cbrouter","Wireless"],"summary":"Get connected wireless clients","description":"Get connected wireless clients.","operationId":"clients_api_v1_wireless_clients_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Clients Api V1 Wireless Clients Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/wireless/radios":{"get":{"tags":["cbrouter","Wireless"],"summary":"Get wireless radio configuration","description":"Get wireless radio configuration.","operationId":"radios_api_v1_wireless_radios_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Radios Api V1 Wireless Radios Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/api/v1/wireless/status":{"get":{"tags":["cbrouter","Wireless"],"summary":"Get wireless status","description":"Get wireless status.","operationId":"wifi_status_api_v1_wireless_status_get","parameters":[{"name":"router","in":"query","required":false,"schema":{"type":"string","description":"Router IP (defaults to settings)","default":"","title":"Router"},"description":"Router IP (defaults to settings)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Wifi Status Api V1 Wireless Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HTTPValidationError"}}}}}}},"/cbrouter/health":{"get":{"tags":["cbrouter","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HealthResponse"}}}}}}},"/cbrouter/health/detailed":{"get":{"tags":["cbrouter","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__HealthResponse"}}}}}}},"/cbrouter/prime":{"get":{"tags":["cbrouter","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__PrimeResponse"}}}}}}},"/cbrouter/status":{"get":{"tags":["cbrouter","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbrouter__StatusResponse"}}}}}}},"/cbscout/api/v1/categories/{entity_type}/{entity_id}":{"get":{"tags":["cbscout","categories"],"summary":"List categories for entity","description":"List all categories for an entity.\n\nReturns lightweight category metadata without full summaries.\nUse this endpoint to discover what categories exist for an entity\nbefore retrieving full profiles.\n\nUse this endpoint when:\n- Checking what information we have about an entity\n- Building a category selector UI\n- Monitoring data coverage\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    repository: Data access layer.\n    entity_type: Type of entity.\n    entity_id: Entity identifier.\n\nReturns:\n    List of category summaries.","operationId":"list_categories_api_v1_categories__entity_type___entity_id__get","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"description":"Type of entity","title":"Entity Type"},"description":"Type of entity"},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":128,"description":"Entity identifier","title":"Entity Id"},"description":"Entity identifier"},{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"responses":{"200":{"description":"List of categories with summary metadata","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbscout__CategorySummary"},"title":"Response List Categories Api V1 Categories  Entity Type   Entity Id  Get"},"example":[{"id":"880e8400-e29b-41d4-a716-446655440003","name":"issues","item_count":3,"updated_at":"2024-11-15T10:30:00Z"},{"id":"990e8400-e29b-41d4-a716-446655440004","name":"demographics","item_count":2,"updated_at":"2024-11-15T09:00:00Z"}]}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/categories/{entity_type}/{entity_id}/{category_name}":{"get":{"tags":["cbscout","categories"],"summary":"Get category details","description":"Get a specific category with its full markdown summary.\n\nSupports hierarchical category paths (e.g., `config/system_prompt`).\n\nUse this endpoint when:\n- Retrieving a single category's summary\n- Getting the evolved markdown content for display\n- Checking category evolution history\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    repository: Data access layer.\n    entity_type: Type of entity.\n    entity_id: Entity identifier.\n    category_name: Category name (supports hierarchical paths with /).\n\nReturns:\n    Full category with summary.","operationId":"get_category_api_v1_categories__entity_type___entity_id___category_name__get","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"description":"Type of entity","title":"Entity Type"},"description":"Type of entity"},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":128,"description":"Entity identifier","title":"Entity Id"},"description":"Entity identifier"},{"name":"category_name","in":"path","required":true,"schema":{"type":"string","title":"Category Name"}},{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"responses":{"200":{"description":"Full category with markdown summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__Category"},"example":{"id":"880e8400-e29b-41d4-a716-446655440003","name":"issues","entity_id":"voter_12345","entity_type":"voter","summary":"## Issues\n\n- **Education**: Strongly supports increased funding\n- **Healthcare**: Favors ACA expansion","campaign_id":"campaign_2024","item_count":3,"evolution_count":2}}}},"404":{"description":"Category not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/checkpoints":{"get":{"tags":["cbscout","checkpoints"],"summary":"List visible checkpoints","description":"List checkpoints (newest first), optional date / project / author filters.\n\nAlways intersects with the caller's visible-author set; an\nexplicit ``author=`` for an author the caller cannot read just\nreturns an empty list (no 403 — list endpoints don't enumerate the\nauthor's existence).\n\nDate filters intentionally exclude undated checkpoints — callers\nasking for a date window don't want them mixed in.","operationId":"mcp_checkpoint_list","parameters":[{"name":"date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Inclusive lower bound on checkpoint date","title":"Date From"},"description":"Inclusive lower bound on checkpoint date"},{"name":"date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Inclusive upper bound on checkpoint date","title":"Date To"},"description":"Inclusive upper bound on checkpoint date"},{"name":"project_tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Restrict to checkpoints tagged with this project","title":"Project Tag"},"description":"Restrict to checkpoints tagged with this project"},{"name":"author","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Restrict to a single author (in addition to the permission-based visibility filter)","title":"Author"},"description":"Restrict to a single author (in addition to the permission-based visibility filter)"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"cbauth-resolved user ID (forwarded by NGINX)","title":"X-User-Id"},"description":"cbauth-resolved user ID (forwarded by NGINX)"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated permission strings from cbauth","title":"X-User-Permissions"},"description":"Comma-separated permission strings from cbauth"},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}}],"responses":{"200":{"description":"Newest-first list of checkpoint summaries, auto-filtered to authors the caller is permitted to read","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbscout__CheckpointSummary"},"title":"Response Mcp Checkpoint List"}}}},"401":{"description":"Authentication required"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/checkpoints/reindex":{"post":{"tags":["cbscout","checkpoints"],"summary":"Reindex the checkpoint mirror","description":"Trigger a full reindex of the checkpoint mirror.\n\nOperator-only: gated by the shared ``X-Reindex-Key`` header, not\nby cbauth (the sync daemon doesn't have a cbauth identity). The\nendpoint is disabled (503) when no mirror is configured.","operationId":"mcp_checkpoint_reindex","parameters":[{"name":"x-reindex-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Shared key authorizing the operator's sync daemon","title":"X-Reindex-Key"},"description":"Shared key authorizing the operator's sync daemon"}],"responses":{"200":{"description":"Reindex completed; report enumerates changes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__ReindexReport"}}}},"401":{"description":"Missing or invalid X-Reindex-Key"},"503":{"description":"Checkpoint mirror is not configured"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/checkpoints/search":{"get":{"tags":["cbscout","checkpoints"],"summary":"Full-text search across visible checkpoints","description":"Search visible checkpoints by free-text query.\n\nResults are auto-filtered to authors the caller is permitted to\nread. Returns BM25-ranked hits with snippet + score.","operationId":"mcp_checkpoint_search","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"Free-text query (BM25-ranked over title, description, content)","title":"Q"},"description":"Free-text query (BM25-ranked over title, description, content)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of hits to return","default":20,"title":"Limit"},"description":"Maximum number of hits to return"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"cbauth-resolved user ID (forwarded by NGINX)","title":"X-User-Id"},"description":"cbauth-resolved user ID (forwarded by NGINX)"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated permission strings from cbauth","title":"X-User-Permissions"},"description":"Comma-separated permission strings from cbauth"},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}}],"responses":{"200":{"description":"Ranked list of matching checkpoints (auto-filtered)","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbscout__CheckpointHit"},"title":"Response Mcp Checkpoint Search"}}}},"401":{"description":"Authentication required"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/checkpoints/{author}/{name}":{"get":{"tags":["cbscout","checkpoints"],"summary":"Read full checkpoint markdown","description":"Return the full markdown content of a checkpoint by ``(author, name)``.\n\nPermission check happens BEFORE the lookup so we don't leak\nexistence information to unauthorized callers — they see 403 on\nevery author they lack permission for, regardless of whether the\ncheckpoint exists.","operationId":"mcp_checkpoint_read","parameters":[{"name":"author","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-zA-Z0-9_-]+$","description":"Author slug (mirror's first path component)","title":"Author"},"description":"Author slug (mirror's first path component)"},{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Checkpoint name (filename stem within author scope)","title":"Name"},"description":"Checkpoint name (filename stem within author scope)"},{"name":"x-user-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"cbauth-resolved user ID (forwarded by NGINX)","title":"X-User-Id"},"description":"cbauth-resolved user ID (forwarded by NGINX)"},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated permission strings from cbauth","title":"X-User-Permissions"},"description":"Comma-separated permission strings from cbauth"},{"name":"x-user-groups","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Groups"}}],"responses":{"200":{"description":"Full checkpoint metadata + markdown content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__Checkpoint"}}}},"401":{"description":"Authentication required"},"403":{"description":"Caller lacks cbscout.read.checkpoints.<author> (or the coarse cbscout.read.checkpoints)"},"404":{"description":"Checkpoint not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/maintenance/archival":{"post":{"tags":["cbscout","maintenance"],"summary":"Run monthly archival","description":"Manually trigger monthly archival.\n\nArchival moves old items to archive and rebuilds indexes.\nThis is normally run automatically each month.\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    service: Maintenance service instance.\n\nReturns:\n    Maintenance result summary.","operationId":"run_archival_api_v1_maintenance_archival_post","parameters":[{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"responses":{"200":{"description":"Archival completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__MaintenanceResponse"},"example":{"items_processed":200,"items_archived":45,"items_deleted":10,"categories_evolved":8,"errors":[]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/maintenance/consolidation":{"post":{"tags":["cbscout","maintenance"],"summary":"Run nightly consolidation","description":"Manually trigger nightly consolidation.\n\nConsolidation merges duplicates and promotes hot items.\nThis is normally run automatically each night.\n\nUse this endpoint when:\n- Testing maintenance operations\n- Forcing cleanup after bulk imports\n- Debugging decay issues\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    service: Maintenance service instance.\n\nReturns:\n    Maintenance result summary.","operationId":"run_consolidation_api_v1_maintenance_consolidation_post","parameters":[{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"responses":{"200":{"description":"Consolidation completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__MaintenanceResponse"},"example":{"items_processed":150,"items_archived":12,"items_deleted":0,"categories_evolved":3,"errors":[]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/maintenance/status":{"get":{"tags":["cbscout","maintenance"],"summary":"Get decay status","description":"Get decay status for a campaign.\n\nReturns counts of stale items at various thresholds\nand the current decay policies.\n\nUse this endpoint when:\n- Monitoring memory health\n- Planning maintenance windows\n- Adjusting decay policies\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    service: Maintenance service instance.\n\nReturns:\n    Decay status summary.","operationId":"get_decay_status_api_v1_maintenance_status_get","parameters":[{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"responses":{"200":{"description":"Decay status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__DecayStatusResponse"},"example":{"campaign_id":"campaign_2024","stale_30_days":42,"stale_90_days":15,"stale_180_days":3,"policies":[{"category":"contact_history","retention_days":1460}]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/maintenance/summarization":{"post":{"tags":["cbscout","maintenance"],"summary":"Run weekly summarization","description":"Manually trigger weekly summarization.\n\nSummarization compresses old items and prunes stale data.\nThis is normally run automatically each week.\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    service: Maintenance service instance.\n\nReturns:\n    Maintenance result summary.","operationId":"run_summarization_api_v1_maintenance_summarization_post","parameters":[{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"responses":{"200":{"description":"Summarization completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__MaintenanceResponse"},"example":{"items_processed":80,"items_archived":25,"items_deleted":5,"categories_evolved":6,"errors":[]}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/memories":{"post":{"tags":["cbscout","memories"],"summary":"Memorize new content","description":"Store new content and extract facts.\n\nThe memorize endpoint is the primary write path for the memory system.\nIt:\n1. Stores raw content as an immutable Resource\n2. Extracts atomic facts (Items) from the content\n3. Resolves conflicts with existing facts\n4. Evolves category summaries with new information\n\nUse this endpoint when:\n- Recording canvass results\n- Storing call transcripts\n- Importing survey responses\n- Adding agent observations\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    service: Memory service instance.\n    request: Content to memorize.\n\nReturns:\n    IDs of created resources and items.","operationId":"memorize_api_v1_memories_post","parameters":[{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__MemorizeRequest"}}}},"responses":{"201":{"description":"Content successfully memorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__MemorizeResponse"},"example":{"resource_id":"550e8400-e29b-41d4-a716-446655440000","items_created":["660e8400-e29b-41d4-a716-446655440001"],"items_superseded":[],"categories_evolved":["issues"]}}}},"400":{"description":"Invalid request or missing campaign ID"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/memories/items":{"post":{"tags":["cbscout","memories"],"summary":"Add a single item","description":"Add a single fact directly.\n\nUse this endpoint when you have a pre-extracted fact that doesn't\nneed to go through the extraction pipeline. For example, when\nimporting structured data or adding agent-verified information.\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    service: Memory service instance.\n    request: Item to add.\n\nReturns:\n    Created item ID and evolution status.","operationId":"add_item_api_v1_memories_items_post","parameters":[{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__AddItemRequest"}}}},"responses":{"201":{"description":"Item successfully added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__AddItemResponse"},"example":{"item_id":"660e8400-e29b-41d4-a716-446655440001","category_evolved":true}}}},"400":{"description":"Invalid request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/memories/items/{item_id}":{"get":{"tags":["cbscout","memories"],"summary":"Get item by ID","description":"Get a specific item by ID.\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    service: Memory service instance.\n    item_id: Item identifier.\n\nReturns:\n    The item.\n\nRaises:\n    HTTPException: If item not found.","operationId":"get_item_api_v1_memories_items__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Item identifier","title":"Item Id"},"description":"Item identifier"},{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"responses":{"200":{"description":"Item details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__Item"},"example":{"id":"660e8400-e29b-41d4-a716-446655440001","item_type":"stance","entity_id":"voter_12345","entity_type":"voter","category":"issues","content":"Supports increased education funding","confidence":0.85,"valid_from":"2024-11-15T10:30:00Z","access_count":3}}}},"404":{"description":"Item not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}},"delete":{"tags":["cbscout","memories"],"summary":"Archive item","description":"Archive an item by setting its valid_until.\n\nArchived items are no longer returned in current queries but\nremain available for historical/point-in-time queries.\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    service: Memory service instance.\n    item_id: Item to archive.\n\nRaises:\n    HTTPException: If item not found.","operationId":"archive_item_api_v1_memories_items__item_id__delete","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Item identifier","title":"Item Id"},"description":"Item identifier"},{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"responses":{"204":{"description":"Item archived"},"404":{"description":"Item not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/memories/{entity_type}/{entity_id}":{"get":{"tags":["cbscout","memories"],"summary":"Retrieve entity memories","description":"Retrieve memories for an entity.\n\nThe retrieve endpoint is the primary read path for the memory system.\nIt returns token-efficient category summaries by default, with options\nto drill down to individual items or source resources.\n\nUse this endpoint when:\n- Building context for AI agent prompts\n- Looking up voter history before a call\n- Reviewing what we know about an entity\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    service: Memory service instance.\n    entity_type: Type of entity.\n    entity_id: Entity identifier.\n    categories: Optional category filter (comma-separated).\n    include_items: Include individual items in response.\n    include_resources: Include source resources in response.\n    valid_at: Point-in-time query timestamp.\n    limit: Maximum items per category.\n\nReturns:\n    Entity profile with category summaries.","operationId":"retrieve_api_v1_memories__entity_type___entity_id__get","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"description":"Type of entity (voter, household, etc.)","title":"Entity Type"},"description":"Type of entity (voter, household, etc.)"},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":128,"description":"Entity identifier","title":"Entity Id"},"description":"Entity identifier"},{"name":"categories","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated list of categories to include","title":"Categories"},"description":"Comma-separated list of categories to include"},{"name":"include_items","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to include individual items in response","default":false,"title":"Include Items"},"description":"Whether to include individual items in response"},{"name":"include_resources","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to include source resources in response","default":false,"title":"Include Resources"},"description":"Whether to include source resources in response"},{"name":"valid_at","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO timestamp for point-in-time query","title":"Valid At"},"description":"ISO timestamp for point-in-time query"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum items per category","default":100,"title":"Limit"},"description":"Maximum items per category"},{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"responses":{"200":{"description":"Entity profile with memory summaries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__EntityProfile"},"example":{"entity_id":"voter_12345","entity_type":"voter","categories":[{"name":"issues","summary":"## Issues\n\n- Supports education funding\n- Opposes toll roads","item_count":2}],"total_items":2}}}},"400":{"description":"Missing campaign ID"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/api/v1/resources/{resource_id}":{"get":{"tags":["cbscout","resources"],"summary":"Get resource by ID","description":"Get a raw resource by ID.\n\nResources contain the original, immutable source data that was\nused to extract facts. Use this endpoint when you need to see\nthe original content (e.g., for audit or reprocessing).\n\nUse this endpoint when:\n- Auditing how a fact was extracted\n- Debugging extraction issues\n- Retrieving original canvass notes\n\nArgs:\n    campaign_id: Campaign identifier from header.\n    service: Memory service instance.\n    resource_id: Resource identifier.\n\nReturns:\n    The raw resource.\n\nRaises:\n    HTTPException: If resource not found.","operationId":"get_resource_api_v1_resources__resource_id__get","parameters":[{"name":"resource_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Resource identifier","title":"Resource Id"},"description":"Resource identifier"},{"name":"x-campaign-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":64},{"type":"null"}],"description":"Campaign ID for multi-tenant isolation","title":"X-Campaign-Id"},"description":"Campaign ID for multi-tenant isolation"}],"responses":{"200":{"description":"Resource details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__Resource"},"example":{"id":"550e8400-e29b-41d4-a716-446655440000","campaign_id":"camp_2024_senate_tx","resource_type":"canvass_log","entity_id":"voter_12345","entity_type":"voter","content":"Voter supports education funding.","content_hash":"a3f2b8c1...","processed":true,"created_at":"2024-11-15T10:30:00Z","updated_at":"2024-11-15T10:30:00Z"}}}},"404":{"description":"Resource not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HTTPValidationError"}}}}}}},"/cbscout/health":{"get":{"tags":["cbscout","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HealthResponse"}}}}}}},"/cbscout/health/detailed":{"get":{"tags":["cbscout","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__HealthResponse"}}}}}}},"/cbscout/prime":{"get":{"tags":["cbscout","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__PrimeResponse"}}}}}}},"/cbscout/status":{"get":{"tags":["cbscout","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbscout__StatusResponse"}}}}}}},"/cbsearch/api/v1/artifacts/{art_id}":{"delete":{"tags":["cbsearch","Search"],"summary":"Soft-delete an artifact","operationId":"delete_artifact_api_v1_artifacts__art_id__delete","parameters":[{"name":"art_id","in":"path","required":true,"schema":{"type":"string","title":"Art Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/ask":{"post":{"tags":["cbsearch","Search"],"summary":"Run one ask turn (SSE stream)","description":"Stream the agent turn as SSE. Event types match the cbai contract\n(status / tool_call / tool_result / text / done / error), each payload\ncarrying `conversationId` so the cbloom WS relay can forward frames\nverbatim to the browser.","operationId":"ask_api_v1_ask_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-cb-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__AskRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/catalog/refresh":{"post":{"tags":["cbsearch","Search"],"summary":"Rebuild the catalog snapshot now","description":"cbcron target — internal, no user identity required (the job carries\nnone). Rebuild is idempotent and atomic.","operationId":"catalog_refresh_api_v1_catalog_refresh_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsearch/api/v1/catalog/tree":{"get":{"tags":["cbsearch","Search"],"summary":"The published catalog tree (static snapshot)","operationId":"catalog_tree_api_v1_catalog_tree_get","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/catalogs":{"get":{"tags":["cbsearch","Search"],"summary":"Catalogs this user's grants unlock","operationId":"catalogs_api_v1_catalogs_get","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-cb-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/concur/ingest":{"post":{"tags":["cbsearch","Search"],"summary":"Ingest Concur telemetry envelopes","description":"Phase 1 capture flush (docs/designs/CONCUR-CAPTURE.md): the browser's\nlocalStorage buffer lands here via the cbloom relay. Idempotent —\nsessions upsert, turns/events insert-or-ignore. Identity comes from the\nrelay headers, never from the envelope.","operationId":"concur_ingest_api_v1_concur_ingest_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-cb-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__ConcurIngestRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/concur/sessions":{"get":{"tags":["cbsearch","Search"],"summary":"List your Concur sessions","operationId":"concur_sessions_api_v1_concur_sessions_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"session-code tail, e.g. DVME","title":"Code"},"description":"session-code tail, e.g. DVME"},{"name":"tool","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tool"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/concur/sessions/{record_id}":{"get":{"tags":["cbsearch","Search"],"summary":"One session, fully hydrated","operationId":"concur_session_api_v1_concur_sessions__record_id__get","parameters":[{"name":"record_id","in":"path","required":true,"schema":{"type":"string","title":"Record Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/conversations":{"get":{"tags":["cbsearch","Search"],"summary":"List this user's conversations","operationId":"list_conversations_api_v1_conversations_get","parameters":[{"name":"catalog","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Catalog"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}},"post":{"tags":["cbsearch","Search"],"summary":"Create a conversation","operationId":"create_conversation_api_v1_conversations_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-cb-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__CreateConversationRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/conversations/{conv_id}":{"get":{"tags":["cbsearch","Search"],"summary":"Get a conversation transcript","operationId":"get_conversation_api_v1_conversations__conv_id__get","parameters":[{"name":"conv_id","in":"path","required":true,"schema":{"type":"string","title":"Conv Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-cb-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}},"patch":{"tags":["cbsearch","Search"],"summary":"Rename a conversation","operationId":"rename_conversation_api_v1_conversations__conv_id__patch","parameters":[{"name":"conv_id","in":"path","required":true,"schema":{"type":"string","title":"Conv Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__RenameRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}},"delete":{"tags":["cbsearch","Search"],"summary":"Soft-delete a conversation","operationId":"delete_conversation_api_v1_conversations__conv_id__delete","parameters":[{"name":"conv_id","in":"path","required":true,"schema":{"type":"string","title":"Conv Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/conversations/{conv_id}/move":{"post":{"tags":["cbsearch","Search"],"summary":"Move a transcript to a workspace","operationId":"move_conversation_api_v1_conversations__conv_id__move_post","parameters":[{"name":"conv_id","in":"path","required":true,"schema":{"type":"string","title":"Conv Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__MoveConversationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/intake/files":{"get":{"tags":["cbsearch","Search"],"summary":"The caller's uploaded data files","operationId":"list_files_api_v1_intake_files_get","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}},"delete":{"tags":["cbsearch","Search"],"summary":"Delete one of the caller's uploads","operationId":"delete_file_api_v1_intake_files_delete","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__KeyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/intake/health":{"get":{"tags":["cbsearch","Search"],"summary":"Broker self-check (cbfiles reachability)","description":"Answers whether the svc credential clears cbfiles' cbauth bridge —\nthe one capability this broker adds. Fail tells the operator to grant\n``cbfiles.*.*`` to cbsearch's service identity.","operationId":"intake_health_api_v1_intake_health_get","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/intake/ingest":{"post":{"tags":["cbsearch","Search"],"summary":"Ingest a probed upload into the fabric","operationId":"ingest_api_v1_intake_ingest_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-cb-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__IngestRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/intake/jobs/{job_id}":{"get":{"tags":["cbsearch","Search"],"summary":"Ingest job status","operationId":"job_status_api_v1_intake_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-cb-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/intake/probe":{"post":{"tags":["cbsearch","Search"],"summary":"Probe an upload for ingestability","operationId":"probe_api_v1_intake_probe_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-cb-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__KeyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/intake/probes":{"get":{"tags":["cbsearch","Search"],"summary":"The caller's probe ledger (status badges)","operationId":"probes_api_v1_intake_probes_get","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"x-cb-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Tenant-Id"}},{"name":"x-user-permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/intake/upload-url":{"post":{"tags":["cbsearch","Search"],"summary":"Presigned PUT for a data-file upload","operationId":"upload_url_api_v1_intake_upload_url_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__UploadUrlRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/presence":{"post":{"tags":["cbsearch","Search"],"summary":"Session heartbeat — presence + guards","description":"One live console page = one session row. Returns writership for the\nsession's transcript (FOLLOW/TAKE OVER rides this), the caller's active\nsession count, and the configured cap. Guards are soft — the console\nexplains; nothing here blocks an ask.","operationId":"presence_beat_api_v1_presence_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__PresenceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/probes":{"get":{"tags":["cbsearch","Search"],"summary":"Probe suggestions for the idle screen","operationId":"probes_api_v1_probes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsearch/api/v1/projects":{"get":{"tags":["cbsearch","Search"],"summary":"List all projects (shared, global access)","operationId":"list_projects_api_v1_projects_get","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}},"post":{"tags":["cbsearch","Search"],"summary":"Create a project","operationId":"create_project_api_v1_projects_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__CreateProjectRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/projects/{proj_id}":{"get":{"tags":["cbsearch","Search"],"summary":"Project detail — row + items","operationId":"project_detail_api_v1_projects__proj_id__get","parameters":[{"name":"proj_id","in":"path","required":true,"schema":{"type":"string","title":"Proj Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}},"patch":{"tags":["cbsearch","Search"],"summary":"Rename / re-describe a project","operationId":"update_project_api_v1_projects__proj_id__patch","parameters":[{"name":"proj_id","in":"path","required":true,"schema":{"type":"string","title":"Proj Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__UpdateProjectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}},"delete":{"tags":["cbsearch","Search"],"summary":"Soft-delete a project","description":"Pointers never cascade — targets (datasets, files, transcripts) are\nuntouched; the project row and its items just leave the tree.","operationId":"delete_project_api_v1_projects__proj_id__delete","parameters":[{"name":"proj_id","in":"path","required":true,"schema":{"type":"string","title":"Proj Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/projects/{proj_id}/items":{"post":{"tags":["cbsearch","Search"],"summary":"Add a pointer item (idempotent on kind+ref)","operationId":"add_item_api_v1_projects__proj_id__items_post","parameters":[{"name":"proj_id","in":"path","required":true,"schema":{"type":"string","title":"Proj Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__AddItemRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/projects/{proj_id}/items/{item_id}":{"delete":{"tags":["cbsearch","Search"],"summary":"Soft-delete an item (the pointer, never the target)","operationId":"delete_item_api_v1_projects__proj_id__items__item_id__delete","parameters":[{"name":"proj_id","in":"path","required":true,"schema":{"type":"string","title":"Proj Id"}},{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/sessions/index":{"post":{"tags":["cbsearch","Search"],"summary":"Rebuild the monroe_sessions catalog now","description":"cbcron target (PROJECTS.md phase 2) — recompile recordings + ledger\ninto monroe_sessions.duckdb. Idempotent full rebuild, atomic publish.","operationId":"sessions_index_refresh_api_v1_sessions_index_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsearch/api/v1/sms/index":{"post":{"tags":["cbsearch","Search"],"summary":"Rebuild the monroe_sms catalog now","description":"cbcron target (whiteboard: \"aware of txt chat\") — pull cbsms threads\nover the mesh into monroe_sms.duckdb. Idempotent full rebuild, atomic\npublish, same emitter pattern as /sessions/index.","operationId":"sms_index_refresh_api_v1_sms_index_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsearch/api/v1/transcripts/{conv_id}/artifacts":{"post":{"tags":["cbsearch","Search"],"summary":"Pin an artifact (table snapshot) to a transcript","operationId":"pin_artifact_api_v1_transcripts__conv_id__artifacts_post","parameters":[{"name":"conv_id","in":"path","required":true,"schema":{"type":"string","title":"Conv Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__PinArtifactRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}},"get":{"tags":["cbsearch","Search"],"summary":"Artifacts filed under a transcript","operationId":"transcript_artifacts_api_v1_transcripts__conv_id__artifacts_get","parameters":[{"name":"conv_id","in":"path","required":true,"schema":{"type":"string","title":"Conv Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/workspaces":{"get":{"tags":["cbsearch","Search"],"summary":"List all workspaces (shared tree)","operationId":"list_workspaces_api_v1_workspaces_get","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}},"post":{"tags":["cbsearch","Search"],"summary":"Create a workspace","operationId":"create_workspace_api_v1_workspaces_post","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__CreateWorkspaceRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/workspaces/tree":{"get":{"tags":["cbsearch","Search"],"summary":"The whole browser payload in one call","operationId":"workspace_tree_api_v1_workspaces_tree_get","parameters":[{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/api/v1/workspaces/{ws_id}":{"patch":{"tags":["cbsearch","Search"],"summary":"Rename a workspace","operationId":"rename_workspace_api_v1_workspaces__ws_id__patch","parameters":[{"name":"ws_id","in":"path","required":true,"schema":{"type":"string","title":"Ws Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__RenameWorkspaceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}},"delete":{"tags":["cbsearch","Search"],"summary":"Soft-delete a workspace","description":"Live transcripts re-file to the owner's inbox. Deleting an inbox that\nstill holds transcripts is refused (there's nowhere left to re-file).","operationId":"delete_workspace_api_v1_workspaces__ws_id__delete","parameters":[{"name":"ws_id","in":"path","required":true,"schema":{"type":"string","title":"Ws Id"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HTTPValidationError"}}}}}}},"/cbsearch/health":{"get":{"tags":["cbsearch","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HealthResponse"}}}}}}},"/cbsearch/health/detailed":{"get":{"tags":["cbsearch","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__HealthResponse"}}}}}}},"/cbsearch/prime":{"get":{"tags":["cbsearch","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__PrimeResponse"}}}}}}},"/cbsearch/status":{"get":{"tags":["cbsearch","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsearch__StatusResponse"}}}}}}},"/cbship/api/v1/capacity":{"get":{"tags":["cbship","Leases"],"summary":"Capacity","description":"What the fleet has to offer, and why any node is excluded.\n\n`memory_source` matters: `available` means the honest probe figure,\n`total` means the fallback, which over-reports on a box that is already busy.","operationId":"capacity_api_v1_capacity_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__CapacityReport"}}}}}}},"/cbship/api/v1/enroll":{"post":{"tags":["cbship","Enrollment"],"summary":"Enrol a machine from an attested probe report","description":"Accept a two-line attested artifact from `cbship attest`.\n\nUnauthenticated by design — the caller has no fleet identity yet. The\nattestation is the credential.\n\nA verified attestation is **not** an admission decision: nodes land as\n`pending` unless `enroll_auto_accept` is on. A machine's self-description\nmust never grant it privilege (docs/ATTEST.md).","operationId":"enroll_api_v1_enroll_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__EnrollResponse"}}}}}}},"/cbship/api/v1/enroll/ratelimit":{"get":{"tags":["cbship","Enrollment"],"summary":"Current rate-limiter pressure on the enroll path","description":"Pin-gated, unlike /enroll itself.\n\nRate-limited requests are not audited to the database on purpose — that write\nis the amplification — so this is where a flood in progress is visible.","operationId":"ratelimit_status_api_v1_enroll_ratelimit_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__RateLimitStatus"}}}}}}},"/cbship/api/v1/handoff/sweep":{"post":{"tags":["cbship","Maintenance"],"summary":"Re-drive every accepted node whose hand-off never landed","description":"The anti-silent-failure path, driven by cbcron.\n\nWithout this, a cbnotify outage during a promote would leave a node accepted\nand invisible to whoever was supposed to provision it.","operationId":"sweep_handoff_api_v1_handoff_sweep_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__SweepResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/leases":{"post":{"tags":["cbship","Leases"],"summary":"Allocate capacity on an enrolled node","description":"Grant a lease, or refuse with the shortfall.\n\n**Never partial, never overcommitted.** A consumer that cannot get a lease must\nwait and retry — never proceed unallocated. That is the cbvpn failure mode this\nexists to prevent: consumers picking resources independently and clobbering\neach other.\n\nA lease is a **ledger entry**, not enforcement. cbship records the claim; it\ndoes not set cgroup limits or start anything. Making it real is cbdc/cbinfra's.","operationId":"allocate_api_v1_leases_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__LeaseRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"409":{"description":"nothing can satisfy the request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__LeaseRefusal"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}},"get":{"tags":["cbship","Leases"],"summary":"List Leases","operationId":"list_leases_api_v1_leases_get","parameters":[{"name":"holder","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Holder"}},{"name":"fingerprint","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fingerprint"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":500,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__LeaseList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/leases/reap":{"post":{"tags":["cbship","Maintenance"],"summary":"Mark expired leases released (cbcron)","description":"Write the audit trail for leases that have already stopped counting.\n\nCapacity accounting excludes expired leases the moment they expire, so this is\nbookkeeping rather than the thing that frees capacity — deliberately, because a\nreaper that has to run for capacity to be correct is a reaper whose outage is a\ncapacity outage.\n\n**Reclaim is expiry-only.** cbvpn's idle-disconnect reclaimed leased-but-quiet\ndevices and killed active consumers; there is no \"looks unused\" heuristic here.","operationId":"reap_leases_api_v1_leases_reap_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":500,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__LeaseReapResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/leases/{lease_id}":{"get":{"tags":["cbship","Leases"],"summary":"Get Lease","operationId":"get_lease_api_v1_leases__lease_id__get","parameters":[{"name":"lease_id","in":"path","required":true,"schema":{"type":"string","title":"Lease Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__Lease"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/leases/{lease_id}/release":{"post":{"tags":["cbship","Leases"],"summary":"Release Lease","description":"Give capacity back. Idempotent — releasing twice is not an error.","operationId":"release_lease_api_v1_leases__lease_id__release_post","parameters":[{"name":"lease_id","in":"path","required":true,"schema":{"type":"string","title":"Lease Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__LeaseReleaseRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__Lease"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/leases/{lease_id}/renew":{"post":{"tags":["cbship","Leases"],"summary":"Renew Lease","description":"Extend a lease.\n\nRenewing is how a long-running holder keeps capacity without asking for an\nunbounded TTL up front. An already-released lease cannot be renewed — that\nwould resurrect a claim on capacity the fleet has handed to someone else.","operationId":"renew_lease_api_v1_leases__lease_id__renew_post","parameters":[{"name":"lease_id","in":"path","required":true,"schema":{"type":"string","title":"Lease Id"}},{"name":"ttl_seconds","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttl Seconds"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__Lease"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/nodes":{"get":{"tags":["cbship","Nodes"],"summary":"List Nodes","description":"List registered nodes, newest contact first.","operationId":"list_nodes_api_v1_nodes_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__NodeList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/nodes/refresh-sweep":{"post":{"tags":["cbship","Maintenance"],"summary":"Surface nodes whose capacity data has gone stale (cbcron)","description":"Report nodes that have stopped re-probing.\n\n**cbship cannot re-probe a node itself.** The probe is a binary the node runs,\nand cbship has no execution path onto it — nor should it, since reaching into a\nmachine to run commands is precisely what the charter says cbship does not do.\nSo this surfaces staleness; the node re-enrolling is what fixes it.\n\nSilence is the failure mode being closed here: without this, a node quietly\nstops reporting and allocation keeps trusting month-old numbers until something\nwedges.","operationId":"refresh_sweep_api_v1_nodes_refresh_sweep_post","parameters":[{"name":"notify","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Notify"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__RefreshSweepResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/nodes/{fingerprint}":{"get":{"tags":["cbship","Nodes"],"summary":"Get Node","operationId":"get_node_api_v1_nodes__fingerprint__get","parameters":[{"name":"fingerprint","in":"path","required":true,"schema":{"type":"string","title":"Fingerprint"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__NodeSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/nodes/{fingerprint}/handoff":{"get":{"tags":["cbship","Nodes"],"summary":"The hand-off packet for a node","description":"Everything the next owner needs, at a stable URL.\n\nThis is the contract a future cbinfra receiver consumes. It is readable for\nany known node — not just handed-off ones — so an operator can see what would\nbe handed over before deciding to accept.","operationId":"get_handoff_api_v1_nodes__fingerprint__handoff_get","parameters":[{"name":"fingerprint","in":"path","required":true,"schema":{"type":"string","title":"Fingerprint"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Handoff Api V1 Nodes  Fingerprint  Handoff Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}},"post":{"tags":["cbship","Nodes"],"summary":"Re-drive hand-off for one node","description":"Retry a hand-off by hand. `force=true` re-delivers an already-handed-off node.","operationId":"post_handoff_api_v1_nodes__fingerprint__handoff_post","parameters":[{"name":"fingerprint","in":"path","required":true,"schema":{"type":"string","title":"Fingerprint"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HandoffResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/nodes/{fingerprint}/promote":{"post":{"tags":["cbship","Nodes"],"summary":"Promote Node","description":"Operator decision on a node: accept it or reject it.\n\nThis is the human gate between \"we verified who sent this\" and \"the fleet\ntrusts this machine\". Keeping it separate from /enroll is the whole point.\n\nAccepting a node also fires the **hand-off** (step 5): cbship builds the\npacket, notifies the next owner, and stops. A delivery failure does not fail\nthis request — the node stays `accepted` with `handoff_error` set and the\nsweep retries. Check `GET /api/v1/nodes/{fingerprint}` for the outcome.","operationId":"promote_node_api_v1_nodes__fingerprint__promote_post","parameters":[{"name":"fingerprint","in":"path","required":true,"schema":{"type":"string","title":"Fingerprint"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__PromoteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__NodeSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/tokens":{"post":{"tags":["cbship","Tokens"],"summary":"Mint an enrollment token (returns the secret ONCE)","description":"Mint a token.\n\nThe response is the only time the secret is ever available — there is no\nendpoint that reveals it later, and it is never logged. A lost token is\nre-minted, not recovered.\n\nDefaults are single-use, 24h, bound to the first machine that uses it.","operationId":"mint_token_api_v1_tokens_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__TokenMintRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__TokenMintResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}},"get":{"tags":["cbship","Tokens"],"summary":"List Tokens","description":"List token metadata. Never returns secrets.","operationId":"list_tokens_api_v1_tokens_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__TokenList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/tokens/reap":{"post":{"tags":["cbship","Maintenance"],"summary":"Destroy key material for revoked/expired/exhausted tokens","description":"Driven by cbcron.\n\nPolicy already refuses a dead token; this makes the key actually gone. Without\nit, expiry and exhaustion are refusals only, and usable HMAC keys accumulate\non disk for credentials that will never be honoured again.","operationId":"reap_tokens_api_v1_tokens_reap_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__TokenReapResult"}}}}}}},"/cbship/api/v1/tokens/{token_id}":{"get":{"tags":["cbship","Tokens"],"summary":"Get Token","operationId":"get_token_api_v1_tokens__token_id__get","parameters":[{"name":"token_id","in":"path","required":true,"schema":{"type":"string","title":"Token Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__TokenSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/api/v1/tokens/{token_id}/revoke":{"post":{"tags":["cbship","Tokens"],"summary":"Revoke a token and destroy its key material","description":"Revoke a token.\n\nThe row survives — the audit of which token enrolled which machine has to\noutlive the token. Only the secret is destroyed, which is what actually stops\nit from being used.","operationId":"revoke_token_api_v1_tokens__token_id__revoke_post","parameters":[{"name":"token_id","in":"path","required":true,"schema":{"type":"string","title":"Token Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__TokenRevokeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__TokenSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HTTPValidationError"}}}}}}},"/cbship/health":{"get":{"tags":["cbship","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HealthResponse"}}}}}}},"/cbship/health/detailed":{"get":{"tags":["cbship","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__HealthResponse"}}}}}}},"/cbship/prime":{"get":{"tags":["cbship","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__PrimeResponse"}}}}}}},"/cbship/status":{"get":{"tags":["cbship","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbship__StatusResponse"}}}}}}},"/cbsme/api/v1/data/catalogs":{"get":{"tags":["cbsme","CBSME"],"summary":"Data Catalog","description":"List the federated data catalogs visible to the caller — table counts,\nrow counts, freshness. Call before writing SQL to discover catalog and\ntable names (evidence catalogs for this interview: cc_discovery,\nairtable_cc).","operationId":"data_catalog","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsme/api/v1/data/query":{"post":{"tags":["cbsme","CBSME"],"summary":"Data Query","description":"Execute read-only cross-catalog DuckDB SQL for inline evidence.\nSyntax: `SELECT ... FROM catalog_name.table_name`. Every result carries\nprovenance (query_id, sql, tables_referenced, row_count).","operationId":"data_query","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__QueryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__HTTPValidationError"}}}}}}},"/cbsme/api/v1/discovery/agenda":{"get":{"tags":["cbsme","CBSME"],"summary":"Agenda","description":"Where the SME interview stands — open questions by stage, consensus\ndecisions so far, and the next question to work. Reads live KB state, so\nit is correct across days and sessions.","operationId":"discovery_agenda","parameters":[{"name":"stage","in":"query","required":false,"schema":{"type":"string","default":"","title":"Stage"}},{"name":"engagement","in":"query","required":false,"schema":{"type":"string","default":"","title":"Engagement"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Discovery Agenda"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__HTTPValidationError"}}}}}}},"/cbsme/api/v1/discovery/lookup":{"post":{"tags":["cbsme","CBSME"],"summary":"Lookup","description":"Answer a question about the discovered business rules, with citations\n(KB record ids). Use before asserting anything about how the business\nworks.","operationId":"discovery_kb_lookup","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__LookupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Discovery Kb Lookup"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__HTTPValidationError"}}}}}}},"/cbsme/api/v1/discovery/resolve":{"post":{"tags":["cbsme","CBSME"],"summary":"Resolve","description":"Record a CONSENSUS business rule after the SME explicitly agrees to\nthe exact wording. Writes to the KB with attribution — no review gate.","operationId":"discovery_resolve","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__ResolveRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Discovery Resolve"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__HTTPValidationError"}}}}}}},"/cbsme/api/v1/engagements":{"get":{"tags":["cbsme","CBSME"],"summary":"Engagements","description":"List registered SME interview engagements.","operationId":"sme_engagements","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Sme Engagements"}}}}}}},"/cbsme/health":{"get":{"tags":["cbsme","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__HealthResponse"}}}}}}},"/cbsme/health/detailed":{"get":{"tags":["cbsme","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__HealthResponse"}}}}}}},"/cbsme/prime":{"get":{"tags":["cbsme","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__PrimeResponse"}}}}}}},"/cbsme/status":{"get":{"tags":["cbsme","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsme__StatusResponse"}}}}}}},"/cbsms/api/app/":{"get":{"tags":["cbsms","VictoryText App"],"summary":"Get Home","description":"Serve the home page.","operationId":"get_home_api_app__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsms/api/messages/sent":{"get":{"tags":["cbsms","Messages"],"summary":"Outbound SMS history","description":"Recent outbound messages, newest first. Mirror of webhooks/recent-responses\nfor the outgoing side. cbloom uses this to render Compose's 'Recent sends' list.","operationId":"get_sent_api_messages_sent_get","parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Only messages created after this ISO timestamp","title":"Since"},"description":"Only messages created after this ISO timestamp"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"recipient","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single recipient (digits-only match)","title":"Recipient"},"description":"Filter to a single recipient (digits-only match)"},{"name":"port","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single SIM/port","title":"Port"},"description":"Filter to a single SIM/port"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Sent Api Messages Sent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/messages/thread":{"delete":{"tags":["cbsms","Messages"],"summary":"Delete an entire thread (both directions) by counterparty number","description":"Wipe every outgoing + incoming message tied to the given counterparty.\n\n**Requires `confirm=true`** (issues #66/#69): a DELETE keyed only by a\nquery param reads like a fetch — cbcli binds it to the read-sounding\n`thread` command — and one wrong call wipes both directions of a thread.\nWithout `confirm`, this is a harmless dry run returning the counts that\nWOULD be deleted.\n\nMatch is on digits-only — `+1...` and `1...` (and `(616) 555-1234` etc.)\nall collide into the same thread, so the delete catches them all.\nCascades to MessageStatusReport rows tied to the outgoing messages.\n\nUsed by the cbloom /app/sms Threads tab to purge spam/noise like the\n1121611611 carrier-notifier reply loop.","operationId":"delete_thread_api_messages_thread_delete","parameters":[{"name":"remote","in":"query","required":true,"schema":{"type":"string","description":"Counterparty number (any format — digits-only key drives match)","title":"Remote"},"description":"Counterparty number (any format — digits-only key drives match)"},{"name":"confirm","in":"query","required":false,"schema":{"type":"boolean","description":"Must be true to actually delete. Without it, the call is a dry run that reports what WOULD be deleted.","default":false,"title":"Confirm"},"description":"Must be true to actually delete. Without it, the call is a dry run that reports what WOULD be deleted."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Thread Api Messages Thread Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/messages/threads":{"get":{"tags":["cbsms","Messages"],"summary":"Outbound+inbound grouped by counterparty number","description":"Unified view of OutgoingMessage + IncomingMessage grouped by remote number.\n\nThread key is digits-only — `+1...` and `1...` collide into one thread.\nEach message carries its `port` (SIM identity) so the cbloom UI can render\na per-bubble SIM badge. Threads sorted newest-activity-first.","operationId":"get_threads_api_messages_threads_get","parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Only messages with timestamp after this","title":"Since"},"description":"Only messages with timestamp after this"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Max number of threads to return","default":50,"title":"Limit"},"description":"Max number of threads to return"},{"name":"messages_per_thread","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Cap on messages returned per thread — newest kept","default":50,"title":"Messages Per Thread"},"description":"Cap on messages returned per thread — newest kept"},{"name":"remote","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"If set, return the single thread for this counterparty number","title":"Remote"},"description":"If set, return the single thread for this counterparty number"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Threads Api Messages Threads Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/messages/{direction}/{message_id}":{"delete":{"tags":["cbsms","Messages"],"summary":"Delete a single message","description":"Delete one outgoing or incoming message by id.\n\n`direction` must be `outgoing` or `incoming`. Cascades to\nMessageStatusReport rows for outgoing deletes.","operationId":"delete_message_api_messages__direction___message_id__delete","parameters":[{"name":"direction","in":"path","required":true,"schema":{"type":"string","title":"Direction"}},{"name":"message_id","in":"path","required":true,"schema":{"type":"integer","title":"Message Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Message Api Messages  Direction   Message Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/sms/delete":{"post":{"tags":["cbsms","Task Management"],"summary":"Delete SMS tasks","description":"Delete one or more SMS tasks.\n\nPermanently removes tasks and their associated messages from the queue.\nMessages already sent are not affected.\n\n- **task_ids**: List of task IDs to delete","operationId":"delete_sms_task_api_sms_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__TaskManagementRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__TaskManagementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/sms/pause":{"post":{"tags":["cbsms","Task Management"],"summary":"Pause SMS tasks","description":"Pause one or more active SMS tasks.\n\nPaused tasks stop sending messages but retain their queue position.\nUse the resume endpoint to continue sending.\n\n- **task_ids**: List of task IDs to pause","operationId":"pause_sms_task_api_sms_pause_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__TaskManagementRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__TaskManagementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/sms/resume":{"post":{"tags":["cbsms","Task Management"],"summary":"Resume paused SMS tasks","description":"Resume one or more paused SMS tasks.\n\nResumes sending from where the task was paused. Messages already\nsent are not re-sent.\n\n- **task_ids**: List of task IDs to resume","operationId":"resume_sms_task_api_sms_resume_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__TaskManagementRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__TaskManagementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/sms/send":{"post":{"tags":["cbsms","SMS"],"summary":"Send a single SMS message","description":"Send a single SMS message to a recipient.\n\nCreates a task to send the message through the Ejoin gateway device.\nThe message is queued and sent asynchronously, with status tracked via task_id.\n\n- **recipient**: Phone number in E.164 format (e.g., 16165550150)\n- **message**: SMS text content (max 160 chars for single SMS, auto-splits longer)\n- **port**: Optional specific device port/SIM to use for sending","operationId":"send_sms_api_sms_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__SMSRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__SendSMSResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/sms/send-bulk":{"post":{"tags":["cbsms","SMS"],"summary":"Send multiple SMS messages in bulk","description":"Send multiple SMS messages as a single bulk task.\n\nEfficiently sends many messages through the gateway with a single task_id\nfor tracking. Messages are processed sequentially with automatic port rotation.\n\n- **messages**: Array of {recipient, message} objects\n- Returns aggregated status counts (successful, failed, pending)","operationId":"send_bulk_sms_api_sms_send_bulk_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__BulkSMSRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__SendSMSResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/sms/send-mms":{"post":{"tags":["cbsms","SMS"],"summary":"Send an MMS message with attachments","description":"Send a multimedia message (MMS) with attachments.\n\nSupports images, audio, and video attachments. The message is queued\nand sent through the Ejoin gateway with MMS encoding.\n\n- **recipient**: Phone number in E.164 format\n- **message**: Text content of the MMS\n- **subject**: Optional MMS subject line\n- **attachments**: List of URLs to media files (images, audio, video)","operationId":"send_mms_api_sms_send_mms_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__MMSRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__SendSMSResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/sms/status":{"post":{"tags":["cbsms","Task Management"],"summary":"Get SMS task delivery status","description":"Get the delivery status of an SMS task.\n\nReturns detailed status including message counts, delivery confirmations,\nand any error information for the specified task.\n\n- **task_id**: The task ID returned from send/send-bulk endpoints","operationId":"get_sms_status_api_sms_status_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__SMSStatusRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__SMSStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/status/command":{"post":{"tags":["cbsms","Device"],"summary":"Send a command to the Ejoin device","description":"Send a command to the Ejoin gateway device.\n\nControl device ports and SIM cards with various commands:\n\n- **lock**: Lock a port to prevent automatic switching\n- **unlock**: Unlock a port for automatic switching\n- **switch**: Force switch to a different SIM\n- **reset**: Reset a port's state\n- **save**: Save current configuration\n- **reboot**: Reboot the device (use with caution)\n\nSpecify target ports in format: \"1.01\" (slot.sim) or \"1,2,3\" (multiple slots)","operationId":"send_device_command_api_status_command_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__DeviceCommandRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__DeviceCommandResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/status/device":{"get":{"tags":["cbsms","Device"],"summary":"Get Ejoin device and SIM status","description":"Get status information from the Ejoin gateway device.\n\nReturns detailed status including:\n- SIM card status (active, signal strength, carrier)\n- Port status (locked, available, in-use)\n- Message counts and queue depth\n\nUse filters to query specific ports or all SIM slots.","operationId":"get_device_status_api_status_device_get","parameters":[{"name":"ports","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Specific ports to check (e.g., '1,2-4')","title":"Ports"},"description":"Specific ports to check (e.g., '1,2-4')"},{"name":"all_sims","in":"query","required":false,"schema":{"type":"boolean","description":"Whether to get status of all SIM slots","default":false,"title":"All Sims"},"description":"Whether to get status of all SIM slots"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__DeviceStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/webhooks/recent-responses":{"get":{"tags":["cbsms","Webhooks"],"summary":"Get recent incoming SMS messages","description":"Retrieve recent incoming SMS messages.\n\nReturns the last 50 received messages, optionally filtered by timestamp.\nUseful for polling incoming messages or checking for responses.\n\n- **since**: Optional ISO timestamp to filter messages received after this time","operationId":"get_recent_responses_api_webhooks_recent_responses_get","parameters":[{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Since"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__RecentResponsesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/webhooks/sms":{"post":{"tags":["cbsms","Webhooks"],"summary":"Receive incoming SMS from device","description":"Receive an incoming SMS webhook from the Ejoin device.\n\nThis endpoint is called by the device when an SMS is received.\nSupports multiple content types:\n\n- **application/json**: `{sender, message, port, smsc, scts}`\n- **application/x-www-form-urlencoded**: Form data with same fields\n- **text/plain**: Raw device format (auto-parsed)\n\nRequires `webhook-secret` header matching configured secret.\nAlso accepts HTTP Basic Auth (password field used as secret) for Ejoin device compatibility.","operationId":"receive_sms_api_webhooks_sms_post","parameters":[{"name":"webhook-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__IncomingSMSResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/api/webhooks/status":{"post":{"tags":["cbsms","Webhooks"],"summary":"Receive SMS delivery status report","description":"Receive a delivery status report webhook from the Ejoin device.\n\nCalled by the device when an SMS delivery status changes.\nUpdates the corresponding outgoing message status in the database.\n\nPayload format (JSON):\n```json\n{\n    \"task_id\": 12345,\n    \"status\": \"delivered\",\n    \"timestamp\": \"2024-01-15T10:30:00Z\"\n}\n```\n\nRequires `webhook-secret` header matching configured secret.","operationId":"receive_status_report_api_webhooks_status_post","parameters":[{"name":"webhook-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Receive Status Report Api Webhooks Status Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HTTPValidationError"}}}}}}},"/cbsms/health":{"get":{"tags":["cbsms","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HealthResponse"}}}}}}},"/cbsms/health/detailed":{"get":{"tags":["cbsms","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__HealthResponse"}}}}}}},"/cbsms/prime":{"get":{"tags":["cbsms","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__PrimeResponse"}}}}}}},"/cbsms/status":{"get":{"tags":["cbsms","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsms__StatusResponse"}}}}}}},"/cbsnailmail/api/v1/addresses/verify":{"post":{"tags":["cbsnailmail","Addresses"],"summary":"Verify an address","description":"Check deliverability + get a normalized address. In stub mode this\nechoes the address back as deliverable (not actually verified).","operationId":"verify_address_api_v1_addresses_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__Address"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__AddressVerifyResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__HTTPValidationError"}}}}}}},"/cbsnailmail/api/v1/mailpieces":{"post":{"tags":["cbsnailmail","Mailpieces"],"summary":"Create + dispatch a mailpiece","description":"Create a mailpiece and (unless ``draft_only``) dispatch it via the\nactive provider. In stub mode the dispatch is simulated — no cost, no\nexternal call — and the piece lands in ``simulated`` status.","operationId":"create_mailpiece_api_v1_mailpieces_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__MailpieceInput"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__Mailpiece"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__HTTPValidationError"}}}}}},"get":{"tags":["cbsnailmail","Mailpieces"],"summary":"List mailpieces","operationId":"list_mailpieces_api_v1_mailpieces_get","parameters":[{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbsnailmail__MailStatus"},{"type":"null"}],"title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbsnailmail__Mailpiece"},"title":"Response List Mailpieces Api V1 Mailpieces Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__HTTPValidationError"}}}}}}},"/cbsnailmail/api/v1/mailpieces/{mp_id}":{"get":{"tags":["cbsnailmail","Mailpieces"],"summary":"Get a mailpiece","operationId":"get_mailpiece_api_v1_mailpieces__mp_id__get","parameters":[{"name":"mp_id","in":"path","required":true,"schema":{"type":"integer","title":"Mp Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__Mailpiece"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__HTTPValidationError"}}}}}},"delete":{"tags":["cbsnailmail","Mailpieces"],"summary":"Soft-delete a mailpiece","description":"Soft-delete only (fleet invariant — rows are never hard-deleted).","operationId":"delete_mailpiece_api_v1_mailpieces__mp_id__delete","parameters":[{"name":"mp_id","in":"path","required":true,"schema":{"type":"integer","title":"Mp Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__HTTPValidationError"}}}}}}},"/cbsnailmail/api/v1/providers":{"get":{"tags":["cbsnailmail","Providers"],"summary":"Active provider + opt-in gate state","description":"Report the configured vs. effective provider and the opt-in gate state.\n\n``effective`` is ``stub`` whenever no live vendor key is present, regardless\nof ``configured`` — so callers can tell at a glance whether real mail is\nbeing sent (``live: true``) or the lifecycle is only simulated.","operationId":"provider_status_api_v1_providers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsnailmail/api/v1/webhooks/lob":{"post":{"tags":["cbsnailmail","Webhooks"],"summary":"Lob delivery-status webhook","operationId":"lob_webhook_api_v1_webhooks_lob_post","parameters":[{"name":"x-cb-webhook-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Webhook-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__HTTPValidationError"}}}}}}},"/cbsnailmail/health":{"get":{"tags":["cbsnailmail","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__HealthResponse"}}}}}}},"/cbsnailmail/health/detailed":{"get":{"tags":["cbsnailmail","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__HealthResponse"}}}}}}},"/cbsnailmail/prime":{"get":{"tags":["cbsnailmail","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__PrimeResponse"}}}}}}},"/cbsnailmail/status":{"get":{"tags":["cbsnailmail","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsnailmail__StatusResponse"}}}}}}},"/cbsocial/api/accounts":{"get":{"tags":["cbsocial","Accounts"],"summary":"List WhatsApp accounts","description":"List the accounts the caller may see: those they own plus unattributed accounts in their tenant. `mine=true` narrows to owner==self. Holders of `cbsocial.admin.*` (or a broader wildcard) see every account. See cbsocial#11 for the scoping rules.","operationId":"list_accounts_api_accounts_get","parameters":[{"name":"mine","in":"query","required":false,"schema":{"type":"boolean","description":"If true, restrict to accounts owned by the caller (X-User-Id).","default":false,"title":"Mine"},"description":"If true, restrict to accounts owned by the caller (X-User-Id)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}},"post":{"tags":["cbsocial","Accounts"],"summary":"Create a new account (begins QR pairing)","description":"Create a WhatsApp account on the relay and begin QR pairing. The new account is attributed to the calling cbauth user (owner) and tenant. Poll `GET /accounts/{id}/qr` (or `qr.json`) for the pairing code, then scan it from the WhatsApp mobile app to bring the session online.","operationId":"create_account_api_accounts_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__CreateAccountBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}":{"get":{"tags":["cbsocial","Accounts"],"summary":"Get account status","description":"Return the relay's current status for one account (connection state, pairing status, metadata). Visible only to the owner, same-tenant viewers of an unattributed account, or an admin; otherwise 404.","operationId":"get_account_api_accounts__account_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}},"delete":{"tags":["cbsocial","Accounts"],"summary":"Delete a WhatsApp account","description":"Tear down an account's relay session and registration. Requires ownership (or admin). Set `wipe_messages=true` to also delete the account's captured message archive on disk.","operationId":"delete_account_api_accounts__account_id__delete","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"wipe_messages","in":"query","required":false,"schema":{"type":"boolean","description":"Also delete the account's on-disk message archive.","default":false,"title":"Wipe Messages"},"description":"Also delete the account's on-disk message archive."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}/chats":{"get":{"tags":["cbsocial","Accounts"],"summary":"List chats for an account","description":"Enumerate the chats (contacts and groups) observed under one account, each with its message count and last-activity timestamp.","operationId":"list_account_chats_api_accounts__account_id__chats_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__ChatListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}/chats/{jid}":{"get":{"tags":["cbsocial","Accounts"],"summary":"Chat detail with recent messages","description":"Return one chat's summary plus its most recent messages (newest first, bounded by `limit`). Set `include_media=true` to inline base64 media. Returns 404 if the chat does not exist under the account.","operationId":"get_account_chat_detail_api_accounts__account_id__chats__jid__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"jid","in":"path","required":true,"schema":{"type":"string","title":"Jid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"include_media","in":"query","required":false,"schema":{"type":"boolean","description":"Inline base64 media data","default":false,"title":"Include Media"},"description":"Inline base64 media data"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}/media/{jid}/{filename}":{"get":{"tags":["cbsocial","Accounts"],"summary":"Serve a media file scoped to an account","description":"Stream a stored media file for a chat under a specific account. The resolved path is constrained to the account's directory (path traversal is rejected with 403); a missing file returns 404.","operationId":"get_account_media_api_accounts__account_id__media__jid___filename__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"jid","in":"path","required":true,"schema":{"type":"string","title":"Jid"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}/messages":{"get":{"tags":["cbsocial","Accounts"],"summary":"List messages for an account","description":"Paginated, filterable list of archived messages scoped to one account, drawn from the in-memory message index. Filter by time (`since`), message `type`, and `forwarded` flag; set `include_media=true` to inline base64 media data.","operationId":"list_account_messages_api_accounts__account_id__messages_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp filter","title":"Since"},"description":"Unix timestamp filter"},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Message type filter","title":"Type"},"description":"Message type filter"},{"name":"forwarded","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Forwarded filter","title":"Forwarded"},"description":"Forwarded filter"},{"name":"include_media","in":"query","required":false,"schema":{"type":"boolean","description":"Inline base64 media data","default":false,"title":"Include Media"},"description":"Inline base64 media data"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__MessageListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}/messages/{jid}":{"get":{"tags":["cbsocial","Accounts"],"summary":"List messages for a JID under an account","description":"Same as the account messages list, further narrowed to a single chat (`jid`) within the account. Returns 404 if no such chat exists under the account.","operationId":"list_account_messages_by_jid_api_accounts__account_id__messages__jid__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"jid","in":"path","required":true,"schema":{"type":"string","title":"Jid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Since"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"forwarded","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Forwarded"}},{"name":"include_media","in":"query","required":false,"schema":{"type":"boolean","description":"Inline base64 media data","default":false,"title":"Include Media"},"description":"Inline base64 media data"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__MessageListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}/qr":{"get":{"tags":["cbsocial","Accounts"],"summary":"Pairing QR code (PNG)","description":"Return the current pairing QR code for the account as a PNG image (`Cache-Control: no-store`). Scan it from the WhatsApp mobile app to link the session. The `x-qr-gen` and `x-account-status` response headers report the QR generation counter and connection state. Use the `qr.json` variant for a JSON-wrapped payload.","operationId":"get_account_qr_api_accounts__account_id__qr_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}},"image/png":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}/qr.json":{"get":{"tags":["cbsocial","Accounts"],"summary":"Pairing QR code (JSON)","description":"JSON variant of the pairing QR endpoint — returns the QR string and status fields instead of a rendered PNG, for clients that render the code themselves.","operationId":"get_account_qr_json_api_accounts__account_id__qr_json_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}/restart":{"post":{"tags":["cbsocial","Accounts"],"summary":"Restart account session","description":"Force the relay to restart the account's WhatsApp session — the remedy for a wedged or disconnected connection. Requires ownership (or admin). A restart may re-enter the pairing state, so re-check the QR endpoints if the session does not reconnect.","operationId":"restart_account_api_accounts__account_id__restart_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}/send":{"post":{"tags":["cbsocial","Accounts"],"summary":"Send a WhatsApp message","description":"Send a text and/or media message from an explicit account to a recipient JID. Requires ownership (or admin). Prefer this when the sending account is known; use `/api/v1/send` to route by `X-User-Id` instead.","operationId":"send_message_api_accounts__account_id__send_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__src__api__accounts__SendBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/accounts/{account_id}/webhook":{"put":{"tags":["cbsocial","Accounts"],"summary":"Set the per-account incoming-message webhook (mints a fresh secret)","description":"Register (or replace) the URL the relay POSTs each incoming message to for this account. A fresh signing secret is minted on every call. Set `envelope=true` to receive messages wrapped with delivery metadata. Requires ownership (or admin).","operationId":"set_account_webhook_api_accounts__account_id__webhook_put","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__WebhookBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}},"get":{"tags":["cbsocial","Accounts"],"summary":"Read the webhook config (secret redacted)","description":"Return the account's current webhook configuration (URL + envelope flag). The signing secret is redacted. Visible to the owner, same-tenant viewers of an unattributed account, or an admin.","operationId":"get_account_webhook_api_accounts__account_id__webhook_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}},"delete":{"tags":["cbsocial","Accounts"],"summary":"Clear the webhook config","description":"Remove the account's incoming-message webhook so the relay stops forwarding. Requires ownership (or admin).","operationId":"delete_account_webhook_api_accounts__account_id__webhook_delete","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/chats":{"get":{"tags":["cbsocial","Messages"],"summary":"List all chats across accounts","description":"Enumerate every known chat (contact or group) across all accounts, each with its message count and last-activity timestamp.","operationId":"list_chats_api_chats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__ChatListResponse"}}}}}}},"/cbsocial/api/chats/{jid}":{"get":{"tags":["cbsocial","Messages"],"summary":"Chat detail with recent messages","description":"Return one chat's summary plus its most recent messages (bounded by `limit`). Set `include_media=true` to inline base64 media. Returns 404 if the chat is unknown.","operationId":"get_chat_detail_api_chats__jid__get","parameters":[{"name":"jid","in":"path","required":true,"schema":{"type":"string","title":"Jid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"include_media","in":"query","required":false,"schema":{"type":"boolean","description":"Inline base64 media data","default":false,"title":"Include Media"},"description":"Inline base64 media data"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/gists":{"get":{"tags":["cbsocial","Gists"],"summary":"List gist metadata","description":"List every chat's gist as metadata only (no summary text) — handy for an index view. Fetch `/api/gists/{jid}` for the summary itself.","operationId":"list_gists_api_gists_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsocial/api/gists/generate":{"post":{"tags":["cbsocial","Gists"],"summary":"Regenerate all gists now","description":"Synchronously regenerate the per-chat summary gists for every chat over the configured rolling window, bypassing the background schedule. Returns the per-chat generation results.","operationId":"generate_gists_api_gists_generate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsocial/api/gists/{jid}":{"get":{"tags":["cbsocial","Gists"],"summary":"Get a chat's gist","description":"Return the AI summary gist for one chat (`jid`), including the summary text. Returns 404 if no gist has been generated for that chat yet.","operationId":"get_gist_api_gists__jid__get","parameters":[{"name":"jid","in":"path","required":true,"schema":{"type":"string","title":"Jid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/media/{jid}/{filename}":{"get":{"tags":["cbsocial","Messages"],"summary":"Serve a media file (searches all accounts)","description":"Stream a stored media file for a chat, searching every account's directory for the first match. Path traversal outside an account directory is skipped; a missing file → 404. Use the account-scoped media route to disambiguate.","operationId":"get_media_api_media__jid___filename__get","parameters":[{"name":"jid","in":"path","required":true,"schema":{"type":"string","title":"Jid"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/messages":{"get":{"tags":["cbsocial","Messages"],"summary":"List messages across all accounts","description":"Paginated, filterable list of archived messages spanning every account, drawn from the in-memory message index. Filter by time (`since`), message `type`, and `forwarded` flag; set `include_media=true` to inline base64 media. Scope to one account with the Accounts routes instead.","operationId":"list_messages_api_messages_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp filter","title":"Since"},"description":"Unix timestamp filter"},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Message type filter","title":"Type"},"description":"Message type filter"},{"name":"forwarded","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Forwarded filter","title":"Forwarded"},"description":"Forwarded filter"},{"name":"include_media","in":"query","required":false,"schema":{"type":"boolean","description":"Inline base64 media data","default":false,"title":"Include Media"},"description":"Inline base64 media data"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__MessageListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/messages/{jid}":{"get":{"tags":["cbsocial","Messages"],"summary":"List messages for a JID (all accounts)","description":"Like the cross-account message list, narrowed to a single chat (`jid`) across all accounts. Returns 404 if no such chat exists in the archive.","operationId":"list_messages_by_jid_api_messages__jid__get","parameters":[{"name":"jid","in":"path","required":true,"schema":{"type":"string","title":"Jid"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Since"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"forwarded","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Forwarded"}},{"name":"include_media","in":"query","required":false,"schema":{"type":"boolean","description":"Inline base64 media data","default":false,"title":"Include Media"},"description":"Inline base64 media data"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__MessageListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/process":{"post":{"tags":["cbsocial","Processing"],"summary":"Run the consolidation pipeline","description":"Select messages matching the query filters and ask cbai to fold them into a single Markdown digest. The run is persisted to disk (manifest + consolidated Markdown) and returned along with its identifiers. An empty body consolidates the entire archive.","operationId":"process_messages_api_process_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__ProcessRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__ProcessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/processed":{"get":{"tags":["cbsocial","Processing"],"summary":"List processed runs","description":"List prior consolidation runs (newest first) by reading each run's `manifest.json`. Malformed manifests are skipped. Returns run summaries (id, timestamp, message/media counts, original query).","operationId":"list_processed_runs_api_processed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsocial/api/processed/{run_id}":{"get":{"tags":["cbsocial","Processing"],"summary":"Get a processed run (manifest + Markdown)","description":"Return one run's `manifest.json` and consolidated Markdown text. The run path is constrained to the processed directory (traversal → 403); an unknown run → 404.","operationId":"get_processed_run_api_processed__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/processed/{run_id}/media/{filename}":{"get":{"tags":["cbsocial","Processing"],"summary":"Serve media from a processed run","description":"Stream a media file copied into a run's `media/` directory. The path is constrained to the processed directory (traversal → 403); a missing file → 404.","operationId":"get_processed_media_api_processed__run_id__media__filename__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/reload":{"post":{"tags":["cbsocial","Messages"],"summary":"Reload the message archive from disk","description":"Re-scan `messages_root/<account>/<jid>/messages.jsonl` into the in-memory message index, then kick off a background gist regeneration over the refreshed data. Use after the relay has captured new conversations on disk. Returns the post-reload message and chat counts.","operationId":"reload_messages_api_reload_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsocial/api/slack/accounts":{"get":{"tags":["cbsocial","Slack"],"summary":"List Slack slots","description":"Return slots the caller can see (same-tenant unattributed + owned).\n\nAdmins (`cbsocial.admin.*`) see everything.","operationId":"list_slots_api_slack_accounts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["cbsocial","Slack"],"summary":"Register a Slack slot","description":"Register a bot slot.\n\nValidates the referenced token via Slack ``auth.test`` and records the\ndiscovered team/bot identity. The token itself is never persisted — only\nits ``secretRef`` is.","operationId":"create_slot_api_slack_accounts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__CreateSlotBody"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/slack/accounts/{slot_id}":{"get":{"tags":["cbsocial","Slack"],"summary":"Get a Slack slot","description":"Return one slot's metadata (label, workspace/team, bot identity, owner, tenant). The bot token is never included — only its `secretRef`. Visible to the owner, same-tenant viewers of an unattributed slot, or an admin; otherwise 404.","operationId":"get_slot_api_slack_accounts__slot_id__get","parameters":[{"name":"slot_id","in":"path","required":true,"schema":{"type":"string","title":"Slot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}},"delete":{"tags":["cbsocial","Slack"],"summary":"Delete a Slack slot","description":"Remove a slot from the registry. Requires ownership (or admin). The underlying SOPS token is untouched — only the slot record is deleted.","operationId":"delete_slot_api_slack_accounts__slot_id__delete","parameters":[{"name":"slot_id","in":"path","required":true,"schema":{"type":"string","title":"Slot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/slack/accounts/{slot_id}/send":{"post":{"tags":["cbsocial","Slack"],"summary":"Send a Slack message via a slot","description":"Post a message to a channel using an explicit slot's bot token (resolved from SOPS at send time). Requires ownership (or admin). Provide text and/or Block Kit blocks; optionally thread, override username/icon. Returns the Slack message `ts` and resolved channel.","operationId":"send_via_slot_api_slack_accounts__slot_id__send_post","parameters":[{"name":"slot_id","in":"path","required":true,"schema":{"type":"string","title":"Slot Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__src__api__slack__SendBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/slack/send":{"post":{"tags":["cbsocial","Slack"],"summary":"Send a Slack alert routed to the caller's slot","description":"Convenience alert path: routes to the slot owned by the caller.\n\nResolves the sending slot by ``ownerUserId == X-User-Id``. If the caller\nowns exactly one slot it is used; zero -> 404, more than one -> 400 (use\nthe explicit ``/accounts/{slot_id}/send`` route instead).","operationId":"send_alert_api_slack_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__src__api__slack__SendBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/api/v1/send":{"post":{"tags":["cbsocial","Accounts"],"summary":"Send a WhatsApp message routed by X-User-Id (cbsocial#13 contract)","description":"Send a message without naming the account explicitly: the sending account is resolved from the `X-User-Id` header. The header is required (400 if missing); zero matching accounts → 404, more than one → 400 (use `/accounts/{id}/send` instead). Matching prefers `ownerUserId`, falling back to `label` for accounts paired before the owner field landed.","operationId":"send_v1_api_v1_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__SendV1Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/gists":{"get":{"tags":["cbsocial","Pages"],"summary":"Chat gists index (HTML)","description":"Server-rendered HTML listing of all per-chat AI summary gists, for sharing outside the JSON API.","operationId":"gists_page_gists_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/cbsocial/gists/{jid}":{"get":{"tags":["cbsocial","Pages"],"summary":"Chat gist detail (HTML)","description":"Render one chat's gist as a shareable HTML page — the summary Markdown converted to HTML. Returns 404 if no gist exists for the chat.","operationId":"gist_page_gists__jid__get","parameters":[{"name":"jid","in":"path","required":true,"schema":{"type":"string","title":"Jid"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/health":{"get":{"tags":["cbsocial","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HealthResponse"}}}}}}},"/cbsocial/health/detailed":{"get":{"tags":["cbsocial","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HealthResponse"}}}}}}},"/cbsocial/prime":{"get":{"tags":["cbsocial","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__PrimeResponse"}}}}}}},"/cbsocial/processed":{"get":{"tags":["cbsocial","Pages"],"summary":"Processed runs index (HTML)","description":"Server-rendered HTML listing of all processed consolidation runs (newest first), for sharing outside the JSON API. Malformed manifests are skipped.","operationId":"runs_page_processed_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/cbsocial/processed/{run_id}":{"get":{"tags":["cbsocial","Pages"],"summary":"Processed run detail (HTML)","description":"Render one consolidation run as a shareable HTML page — the consolidated Markdown converted to HTML, with its manifest. Path traversal → 403; unknown run → 404.","operationId":"run_page_processed__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/processed/{run_id}/media/{filename}":{"get":{"tags":["cbsocial","Pages"],"summary":"Serve run media for rendered pages","description":"Stream a media file from a processed run so relative image paths resolve in the rendered HTML pages. Path traversal → 403; missing file → 404.","operationId":"run_media_processed__run_id__media__filename__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__HTTPValidationError"}}}}}}},"/cbsocial/status":{"get":{"tags":["cbsocial","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsocial__StatusResponse"}}}}}}},"/cbsquare/api/v1/checkout":{"post":{"tags":["cbsquare","CBSQUARE"],"summary":"Create a hosted donation/checkout link","description":"Create a Square hosted payment link and return its URL.","operationId":"checkout_api_v1_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsquare__CheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsquare__HTTPValidationError"}}}}}}},"/cbsquare/api/v1/config":{"get":{"tags":["cbsquare","CBSQUARE"],"summary":"Square wiring status","description":"Non-secret view of whether Square is wired (never returns the token).","operationId":"config_api_v1_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsquare/api/v1/payments":{"get":{"tags":["cbsquare","CBSQUARE"],"summary":"List recent payments","operationId":"payments_api_v1_payments_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":25,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsquare__HTTPValidationError"}}}}}}},"/cbsquare/api/v1/payments/{payment_id}":{"get":{"tags":["cbsquare","CBSQUARE"],"summary":"Get one payment","operationId":"payment_api_v1_payments__payment_id__get","parameters":[{"name":"payment_id","in":"path","required":true,"schema":{"type":"string","title":"Payment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsquare__HTTPValidationError"}}}}}}},"/cbsquare/api/v1/webhooks/square":{"post":{"tags":["cbsquare","CBSQUARE"],"summary":"Receive a Square webhook","description":"Verify the HMAC signature, then acknowledge.\n\nEvent fan-out (e.g. onto the mesh bus) is a follow-up; for now we validate\nand 200 so Square stops retrying.","operationId":"webhook_api_v1_webhooks_square_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbsquare/health":{"get":{"tags":["cbsquare","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsquare__HealthResponse"}}}}}}},"/cbsquare/health/detailed":{"get":{"tags":["cbsquare","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsquare__HealthResponse"}}}}}}},"/cbsquare/prime":{"get":{"tags":["cbsquare","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsquare__PrimeResponse"}}}}}}},"/cbsquare/status":{"get":{"tags":["cbsquare","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsquare__StatusResponse"}}}}}}},"/cbstitch/bus":{"get":{"tags":["cbstitch","CBSTITCH","CBSTITCH"],"summary":"Mesh bus consumer status","description":"Event-driven wake (issue #2): subscription state, events seen, and wakes\ndelivered. ``connected=false`` with polling intact means the bus is the only\nthing degraded — VSs still reconcile on their interval.","operationId":"bus_status_bus_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Bus Status Bus Get"}}}}}}},"/cbstitch/health":{"get":{"tags":["cbstitch","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HealthResponse"}}}}}}},"/cbstitch/health/detailed":{"get":{"tags":["cbstitch","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HealthResponse"}}}}}}},"/cbstitch/prime":{"get":{"tags":["cbstitch","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__PrimeResponse"}}}}}}},"/cbstitch/status":{"get":{"tags":["cbstitch","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__StatusResponse"}}}}}}},"/cbstitch/v":{"get":{"tags":["cbstitch","CBSTITCH"],"summary":"List virtual services","description":"name, lifecycle, last reconcile, and loop health for every known VS.","operationId":"list_services_v_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Services V Get"}}}}}}},"/cbstitch/v/fl-board-d/call/{office_code}":{"post":{"summary":"Election-Board.Call Race","description":"Operator override: mark a race called (annotation + forced re-cast).","operationId":"fl_board_d__election_board_call_race_call__office_code__post","parameters":[{"name":"office_code","in":"path","required":true,"schema":{"type":"string","title":"Office Code"}},{"name":"winner","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Winner"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Election Board Call Race Call  Office Code  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__fl_board_d__HTTPValidationError"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board-d/clear":{"post":{"summary":"Election-Board.Clear","description":"Clear the channel → boxes revert to standby; next advance re-casts.","operationId":"fl_board_d__election_board_clear_clear_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Clear Clear Post"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board-d/deck":{"get":{"summary":"Election-Board.Deck","description":"The current scene deck (channel + surface + the per-scene render plan) —\nan operator/debug view of what this board casts.","operationId":"fl_board_d__election_board_deck_deck_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Deck Deck Get"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board-d/projection":{"get":{"summary":"Election-Board.Projection View","description":"Last cbmodels projection summary this board consumed (derived layer:\nprojected winner + win-prob per headline race). Empty until a\nprojection_channel is configured and a projection has been published.","operationId":"fl_board_d__election_board_projection_view_projection_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Projection View Projection Get"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board-d/refresh":{"post":{"summary":"Election-Board.Refresh","description":"Force a re-cast on the next tick, bypassing the version gate.","operationId":"fl_board_d__election_board_refresh_refresh_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Refresh Refresh Post"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board-d/status":{"get":{"summary":"Election-Board.Status","description":"Board health + cast state: configured state/channel/surface, the last\ncast version + scenes, auto/manual race calls, the consumed projection, and\nthe reconcile loop's health counters.","operationId":"fl_board_d__election_board_status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Status Status Get"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board-d/uncall/{office_code}":{"post":{"summary":"Election-Board.Uncall Race","description":"Undo an operator /call for an office — drop the override + re-cast so the\nboard reverts to the data-layer/projection result.","operationId":"fl_board_d__election_board_uncall_race_uncall__office_code__post","parameters":[{"name":"office_code","in":"path","required":true,"schema":{"type":"string","title":"Office Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Election Board Uncall Race Uncall  Office Code  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__fl_board_d__HTTPValidationError"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board/call/{office_code}":{"post":{"summary":"Election-Board.Call Race","description":"Operator override: mark a race called (annotation + forced re-cast).","operationId":"fl_board__election_board_call_race_call__office_code__post","parameters":[{"name":"office_code","in":"path","required":true,"schema":{"type":"string","title":"Office Code"}},{"name":"winner","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Winner"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Election Board Call Race Call  Office Code  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__fl_board__HTTPValidationError"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board/clear":{"post":{"summary":"Election-Board.Clear","description":"Clear the channel → boxes revert to standby; next advance re-casts.","operationId":"fl_board__election_board_clear_clear_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Clear Clear Post"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board/deck":{"get":{"summary":"Election-Board.Deck","description":"The current scene deck (channel + surface + the per-scene render plan) —\nan operator/debug view of what this board casts.","operationId":"fl_board__election_board_deck_deck_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Deck Deck Get"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board/projection":{"get":{"summary":"Election-Board.Projection View","description":"Last cbmodels projection summary this board consumed (derived layer:\nprojected winner + win-prob per headline race). Empty until a\nprojection_channel is configured and a projection has been published.","operationId":"fl_board__election_board_projection_view_projection_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Projection View Projection Get"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board/refresh":{"post":{"summary":"Election-Board.Refresh","description":"Force a re-cast on the next tick, bypassing the version gate.","operationId":"fl_board__election_board_refresh_refresh_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Refresh Refresh Post"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board/status":{"get":{"summary":"Election-Board.Status","description":"Board health + cast state: configured state/channel/surface, the last\ncast version + scenes, auto/manual race calls, the consumed projection, and\nthe reconcile loop's health counters.","operationId":"fl_board__election_board_status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Status Status Get"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/fl-board/uncall/{office_code}":{"post":{"summary":"Election-Board.Uncall Race","description":"Undo an operator /call for an office — drop the override + re-cast so the\nboard reverts to the data-layer/projection result.","operationId":"fl_board__election_board_uncall_race_uncall__office_code__post","parameters":[{"name":"office_code","in":"path","required":true,"schema":{"type":"string","title":"Office Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Election Board Uncall Race Uncall  Office Code  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__fl_board__HTTPValidationError"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/mi-convention-board/call/{office_code}":{"post":{"summary":"Election-Board.Call Race","description":"Operator override: mark a race called (annotation + forced re-cast).","operationId":"mi_convention_board__election_board_call_race_call__office_code__post","parameters":[{"name":"office_code","in":"path","required":true,"schema":{"type":"string","title":"Office Code"}},{"name":"winner","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Winner"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Election Board Call Race Call  Office Code  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__mi_convention_board__HTTPValidationError"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/mi-convention-board/clear":{"post":{"summary":"Election-Board.Clear","description":"Clear the channel → boxes revert to standby; next advance re-casts.","operationId":"mi_convention_board__election_board_clear_clear_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Clear Clear Post"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/mi-convention-board/deck":{"get":{"summary":"Election-Board.Deck","description":"The current scene deck (channel + surface + the per-scene render plan) —\nan operator/debug view of what this board casts.","operationId":"mi_convention_board__election_board_deck_deck_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Deck Deck Get"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/mi-convention-board/projection":{"get":{"summary":"Election-Board.Projection View","description":"Last cbmodels projection summary this board consumed (derived layer:\nprojected winner + win-prob per headline race). Empty until a\nprojection_channel is configured and a projection has been published.","operationId":"mi_convention_board__election_board_projection_view_projection_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Projection View Projection Get"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/mi-convention-board/refresh":{"post":{"summary":"Election-Board.Refresh","description":"Force a re-cast on the next tick, bypassing the version gate.","operationId":"mi_convention_board__election_board_refresh_refresh_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Refresh Refresh Post"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/mi-convention-board/status":{"get":{"summary":"Election-Board.Status","description":"Board health + cast state: configured state/channel/surface, the last\ncast version + scenes, auto/manual race calls, the consumed projection, and\nthe reconcile loop's health counters.","operationId":"mi_convention_board__election_board_status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Election Board Status Status Get"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/mi-convention-board/uncall/{office_code}":{"post":{"summary":"Election-Board.Uncall Race","description":"Undo an operator /call for an office — drop the override + re-cast so the\nboard reverts to the data-layer/projection result.","operationId":"mi_convention_board__election_board_uncall_race_uncall__office_code__post","parameters":[{"name":"office_code","in":"path","required":true,"schema":{"type":"string","title":"Office Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Election Board Uncall Race Uncall  Office Code  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__mi_convention_board__HTTPValidationError"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/pipe-tagged-responses/export.csv":{"get":{"summary":".Export Csv","operationId":"pipe_tagged_responses___export_csv_export_csv_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":5000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__pipe_tagged_responses__HTTPValidationError"}}}}},"description":".Export Csv — endpoint of the pipe-tagged-responses virtual service (auto-documented; add a docstring for a richer description).","tags":["cbstitch"]}},"/cbstitch/v/pipe-tagged-responses/keys":{"get":{"summary":".Keys","description":"The DISTINCT-reply review set (UI spec §4): one row per distinct normalized\nfree-text reply (the ``tag_cache`` keys — Stage-0 opt-outs never enter the\ncache), each with its FREQUENCY across responses, a sample text, the model's\ntags + confidence, and whether it's already human-verified. Correcting one key\nfixes every duplicate via ``/override-key``. Ordered high-frequency +\nlow-confidence first, so a reviewer's minutes buy the most correctness.","operationId":"pipe_tagged_responses___keys_keys_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":500,"title":"Limit"}},{"name":"min_freq","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Min Freq"}},{"name":"uncorrected","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Uncorrected"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response  Keys Keys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__pipe_tagged_responses__HTTPValidationError"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/pipe-tagged-responses/override":{"post":{"summary":".Override","description":"Apply a single human correction IMMEDIATELY (the loop's upsert carries\n``WHERE corrected_by IS NULL`` so this is race-safe even mid-tick, and it works\nwhile the tagging drain is paused). Body: {response_id, tags:{col:val}, by}.","operationId":"pipe_tagged_responses___override_override_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response  Override Override Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__pipe_tagged_responses__HTTPValidationError"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/pipe-tagged-responses/override-key":{"post":{"summary":".Override Key","description":"Bulk correct-by-key (UI spec §4) — apply a correction to EVERY response that\nshares one normalized reply and teach the cache authoritatively, so future\nrepeats inherit it for free. This is what makes the distinct-reply review\none-click-fixes-all. Body: {norm_key | text, tags:{col:val}, by, ctx_key?}.\nReturns ``fixed`` = how many rows were back-filled. Immediate + race-safe.","operationId":"pipe_tagged_responses___override_key_override_key_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response  Override Key Override Key Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__pipe_tagged_responses__HTTPValidationError"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/pipe-tagged-responses/rows":{"get":{"summary":".Rows","operationId":"pipe_tagged_responses___rows_rows_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"uncorrected","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Uncorrected"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response  Rows Rows Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__pipe_tagged_responses__HTTPValidationError"}}}}},"description":".Rows — endpoint of the pipe-tagged-responses virtual service (auto-documented; add a docstring for a richer description).","tags":["cbstitch"]}},"/cbstitch/v/pipe-tagged-responses/run":{"post":{"summary":".Run","description":"Manual trigger — wake the reconcile loop now (a re-run is just a tick).","operationId":"pipe_tagged_responses___run_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response  Run Run Post"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/pipe-tagged-responses/spec":{"get":{"summary":".Spec","description":"Column/filter spec that drives the generic viewer.","operationId":"pipe_tagged_responses___spec_spec_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response  Spec Spec Get"}}}}},"tags":["cbstitch"]}},"/cbstitch/v/pipe-tagged-responses/status":{"get":{"summary":".Status","operationId":"pipe_tagged_responses___status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response  Status Status Get"}}}}},"description":".Status — endpoint of the pipe-tagged-responses virtual service (auto-documented; add a docstring for a richer description).","tags":["cbstitch"]}},"/cbstitch/v/pipe-tagged-responses/summary":{"get":{"summary":".Summary","operationId":"pipe_tagged_responses___summary_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response  Summary Summary Get"}}}}},"description":".Summary — endpoint of the pipe-tagged-responses virtual service (auto-documented; add a docstring for a richer description).","tags":["cbstitch"]}},"/cbstitch/v/{name}":{"get":{"tags":["cbstitch","CBSTITCH"],"summary":"Virtual service status","description":"Lifecycle + controller-state summary for one VS.","operationId":"vs_status_v__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Vs Status V  Name  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}}}},"/cbstitch/v/{name}/approve":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Approve a virtual service's code (trust boundary)","description":"Record the VS's current ``service.py`` digest as trusted (operator action;\nHMAC-signed if a key is configured, else sha256). With ``trust_mode=enforce`` the\nhost then loads it; ``warn`` stops flagging it. Optional ``?by=`` is audited.","operationId":"approve_service_v__name__approve_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"By"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Approve Service V  Name  Approve Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/cadence":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Set a virtual service's update cadence","description":"Set the reconcile interval (``interval_s``) and/or the cbcast slide\nrotation (``slide_interval_s``): persisted to the home ``config.json`` AND\napplied live — no reload. A slide-interval change re-casts so the new\nrotation reaches the channel. 400 on floor violations (interval >= 5s,\nslide >= 1s), 404 if the VS has no home.","operationId":"set_cadence_v__name__cadence_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__CadenceBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Cadence V  Name  Cadence Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/config":{"get":{"tags":["cbstitch","CBSTITCH"],"summary":"Read a virtual service's config","description":"The VS's home ``config.json`` — channel, catalog, identity/tenant, cadence,\nsensitivity, and any board-specific knobs (``focus_geoid``, …). 404 if the VS\nhas no home.","operationId":"get_config_v__name__config_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Config V  Name  Config Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}}},"put":{"tags":["cbstitch","CBSTITCH"],"summary":"Update a virtual service's config (merge + reload)","description":"Merge the body into the home ``config.json`` (durable; a ``null`` value\ndeletes the key), then hot-reload the VS so ``on_load`` re-reads it — the\ngeneric data-side CRUD (channel, catalog, tenant, board knobs). Config is not\ncode: trust is unaffected. 404 if the VS has no home; an unloaded VS gets the\nwrite without a reload.","operationId":"put_config_v__name__config_put","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Patch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Put Config V  Name  Config Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/disable":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Durably disable a virtual service","description":"Stop + unmount the VS and write a durable home marker so it stays off across\nrestarts — unlike ``/stop``, which ``start_all`` undoes on the next boot. Optional\n``?reason=`` is recorded in the marker. Re-enable with ``/enable``.","operationId":"disable_service_v__name__disable_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"reason","in":"query","required":false,"schema":{"type":"string","default":"","title":"Reason"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Disable Service V  Name  Disable Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/enable":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Re-enable a durably-disabled virtual service","description":"Clear the durable disable marker, then load + start the VS.","operationId":"enable_service_v__name__enable_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Enable Service V  Name  Enable Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/load":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Load a virtual service","description":"importlib + mount. Idempotent.","operationId":"load_service_v__name__load_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Load Service V  Name  Load Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/recast":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Force a re-render + re-cast","description":"Clear the VS's digest gate and wake it — the next tick re-renders and\nre-casts even though the data hasn't moved. Use after a layout/code deploy\n(a plain ``/tick`` noops when the data digest is unchanged). 404 if not\nloaded, 409 if the VS doesn't support it.","operationId":"recast_service_v__name__recast_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Recast Service V  Name  Recast Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}}}},"/cbstitch/v/{name}/reload":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Hot-reload a virtual service","description":"Safe two-phase swap after ``service.py`` changed. State persists.","operationId":"reload_service_v__name__reload_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reload Service V  Name  Reload Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/revoke":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Revoke a virtual service's code approval (trust boundary)","description":"Remove the VS's code approval → reverts to ``unverified`` (and refused under\n``trust_mode=enforce`` on the next load).","operationId":"revoke_service_v__name__revoke_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Revoke Service V  Name  Revoke Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/sensitivity":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Set a virtual service's sensitivity classification","description":"Set + persist the VS's sensitivity level (one of ``cbstitch.SENSITIVITY_LEVELS``)\nto its home ``config.json`` AND apply it to the running instance — for the cbloom\nclassification UI (cbloom#241). 400 on an unknown level, 404 if the VS has no home.\nDoes not affect code trust (config, not code), so no re-approval is needed.","operationId":"set_sensitivity_v__name__sensitivity_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__SensitivityBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Sensitivity V  Name  Sensitivity Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/start":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Start a virtual service loop","description":"Spawn the reconcile loop.","operationId":"start_service_v__name__start_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Start Service V  Name  Start Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/stop":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Stop a virtual service","description":"Drain the loop + unmount.","operationId":"stop_service_v__name__stop_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Stop Service V  Name  Stop Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}},"x-mcp-exclude":true}},"/cbstitch/v/{name}/tick":{"post":{"tags":["cbstitch","CBSTITCH"],"summary":"Externally poke a reconcile (cbcron)","description":"Wake the VS for one immediate reconcile — cbcron pokes this when idle.","operationId":"tick_service_v__name__tick_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Tick Service V  Name  Tick Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstitch__HTTPValidationError"}}}}}}},"/cbstt/api/v1/hello":{"get":{"tags":["cbstt","CBSTT"],"summary":"Hello world","description":"Return a fixed greeting.\n\nA trivial liveness/sanity endpoint that confirms the `/api/v1` router is\nmounted and serving. Performs no work and touches no STT engine or\ndatabase. For real health use `/health`; for STT runtime detail use\n`/api/v1/system/info`.\n\nReturns a JSON object: `{\"message\": \"Hello from cbstt\"}`.","operationId":"hello_api_v1_hello_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbstt/api/v1/jobs":{"get":{"tags":["cbstt","Jobs"],"summary":"List jobs (admin / debugging)","description":"List recent transcription jobs, newest first. Intended for operators and debugging rather than per-consumer polling (prefer `GET /jobs/{id}`). Optionally filter by `status` (one of queued · running · succeeded · failed · canceled — an unknown value yields `400`) and by `since` (an ISO-8601 timestamp; only jobs created at or after it). `limit` caps the page size (1-200, default 50). Returns `{jobs: [...], count: N}` where each entry is the same shape as `GET /jobs/{id}`.","operationId":"list_jobs_api_v1_jobs_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 timestamp","title":"Since"},"description":"ISO-8601 timestamp"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Jobs Api V1 Jobs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__HTTPValidationError"}}}}}}},"/cbstt/api/v1/jobs/{job_id}":{"get":{"tags":["cbstt","Jobs"],"summary":"Get job status","description":"Fetch the current state of a single transcription job by its `job_id` (the ULID returned at submit time). Reports `status` (queued · running · succeeded · failed · canceled), `progress` (0.0-1.0), timing (`created_at` / `started_at` / `finished_at`), the selected `model` and actual `tier_used`, detected `language`, the echoed opaque `metadata`, and — on success — the `result_url` / `cb_uri` for the published transcript. On failure, `error_code` and `error_message` explain why. Returns `404` if no such job exists. Safe to poll.","operationId":"get_job_api_v1_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Job Api V1 Jobs  Job Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__HTTPValidationError"}}}}}}},"/cbstt/api/v1/jobs/{job_id}/cancel":{"post":{"tags":["cbstt","Jobs"],"summary":"Best-effort cancel","description":"Request cancellation of a job that is still `queued` or `running`. Cancellation is **best-effort**: a queued job is dropped before it starts, but a running job may finish its current inference before the runner observes the request. On success the job transitions to `canceled` with `error_code = CANCELED_BY_USER`. Returns `404` if the job is unknown, and `409` if it is already in a terminal state (succeeded / failed / canceled) — the runner cannot reach back in time. Returns the job's `job_id`, `status`, and `error_code`.","operationId":"cancel_api_v1_jobs__job_id__cancel_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cancel Api V1 Jobs  Job Id  Cancel Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__HTTPValidationError"}}}}}}},"/cbstt/api/v1/jobs/{job_id}/result":{"get":{"tags":["cbstt","Jobs"],"summary":"Get job result","description":"Render the transcript of a **succeeded** job in the requested `format`:\n\n- `json` (default) — compact transcript JSON\n- `verbose_json` — segments, timings, and per-word detail\n- `text` — plain text only\n- `srt` — SubRip subtitles\n- `vtt` — WebVTT subtitles\n\nThe response body is the rendered transcript with a matching `Content-Type`. Returns `404` if the job is unknown or not yet `succeeded` (poll `GET /jobs/{id}` for status first), and `500` in the rare case a succeeded job has no stored result path.","operationId":"get_result_api_v1_jobs__job_id__result_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(json|verbose_json|text|srt|vtt)$","default":"json","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__HTTPValidationError"}}}}}}},"/cbstt/api/v1/models":{"get":{"tags":["cbstt","System"],"summary":"Available STT model tiers","description":"List the whisper model tiers this instance can transcribe with. The `model=` value accepted on the transcription endpoints must be one of `loaded_tiers`; omitting it uses `default`. Also echoes the configured default and fallback chain for reference. Returns a JSON object with `default`, `loaded_tiers`, `configured_default`, and `configured_fallback_chain`.","operationId":"list_loaded_models_api_v1_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Loaded Models Api V1 Models Get"}}}}}}},"/cbstt/api/v1/system/info":{"get":{"tags":["cbstt","System"],"summary":"STT runtime info","description":"Report the live STT runtime: the faster-whisper backend, configured default and fallback tiers, which tiers are currently loaded, compute settings (compute type, CPU threads, worker count), whether models preload at boot, on-disk model/data/db paths (and whether they exist), database reachability, and the process PID. Read-only introspection intended for operators and dashboards — distinct from CBApp's generic `/status`. Returns a nested JSON object keyed by `engine`, `paths`, `db`, and `process`.","operationId":"system_info_api_v1_system_info_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response System Info Api V1 System Info Get"}}}}}}},"/cbstt/api/v1/transcriptions":{"post":{"tags":["cbstt","Transcriptions"],"summary":"Submit transcription job (multipart upload)","description":"Submit an audio or video file for asynchronous transcription via a multipart form upload. The file is streamed to disk and capped at the configured upload limit (`413` if exceeded). Returns `202 Accepted` with a `job_id`, `status`, an `events_url`, an internal `result_url`, and `created_at` — then track the job via the **Jobs** endpoints.\n\n**Form fields:** `file` (required), plus optional `model` (a loaded tier name — unknown/unloaded ⇒ `400`), `metadata` (a ≤2 KB JSON object echoed back; malformed ⇒ `400`, oversize ⇒ `400`), `language` (ISO-639-1; autodetected if omitted), `word_timestamps`, `initial_prompt`, and `webhook_url`.\n\n**Idempotency:** send an `Idempotency-Key` header to make retries safe — a repeat with the same key returns the original job (`200 OK`) instead of creating a new one. Returns `503` if no STT engine is loaded.","operationId":"submit_multipart_api_v1_transcriptions_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/cbstt__Body_submit_multipart_api_v1_transcriptions_post"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Submit Multipart Api V1 Transcriptions Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__HTTPValidationError"}}}}}}},"/cbstt/api/v1/transcriptions/from-url":{"post":{"tags":["cbstt","Transcriptions"],"summary":"Submit transcription job (by URL or cb:// URI)","description":"Submit a transcription job by pointing cbstt at a remote audio/video URL instead of uploading bytes. cbstt fetches the resource (streaming, under the configured upload cap) and enqueues a job, returning `202 Accepted` with a `job_id` and tracking URLs — track via the **Jobs** endpoints.\n\nOnly `http://` / `https://` URLs are accepted; a `cb://` URI returns `501 Not Implemented` in V1 (pending cbfiles resolution). An upstream fetch error or a non-2xx response yields `400`; a body exceeding the cap yields `413`.\n\nHonours the `Idempotency-Key` header (a repeat returns the original job, `200 OK`) and the same `model` / `metadata` / `language` / `word_timestamps` / `initial_prompt` / `webhook_url` options as the multipart endpoint. Returns `503` if no STT engine is loaded.","operationId":"submit_from_url_api_v1_transcriptions_from_url_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__FromUrlRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Submit From Url Api V1 Transcriptions From Url Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__HTTPValidationError"}}}}}}},"/cbstt/health":{"get":{"tags":["cbstt","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__HealthResponse"}}}}}}},"/cbstt/health/detailed":{"get":{"tags":["cbstt","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__HealthResponse"}}}}}}},"/cbstt/prime":{"get":{"tags":["cbstt","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__PrimeResponse"}}}}}}},"/cbstt/status":{"get":{"tags":["cbstt","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbstt__StatusResponse"}}}}}}},"/cbsurveys/api/v1/domains":{"get":{"tags":["cbsurveys","Meta"],"summary":"List public send domains (for the domain picker)","description":"The domains a tenant can send survey links from — the Deploy app populates its A/B / load-balance picker from this. First = default.","operationId":"list_domains_api_v1_domains_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/r/{slug}":{"get":{"tags":["cbsurveys","Responses"],"summary":"Resolve a public survey link → renderable survey","description":"Public: resolve a base-52 slug to the ACTIVE survey it points at. The renderer (surveys.campaignbrain.dev) calls this to draw the form.","operationId":"render_public_survey_api_v1_r__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"Base-52 public link slug","title":"Slug"},"description":"Base-52 public link slug"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"404":{"description":"Unknown or inactive link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"409":{"description":"Survey not accepting responses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/r/{slug}/responses":{"post":{"tags":["cbsurveys","Responses"],"summary":"Submit a response via a public link","description":"Public: submit answers through a slug; the response is tied to the slug's recipient (row_id) so it attributes back to the cblist contact.","operationId":"submit_via_slug_api_v1_r__slug__responses_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"Base-52 public link slug","title":"Slug"},"description":"Base-52 public link slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__ResponseSubmitRequest"}}}},"responses":{"201":{"description":"Response submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__ResponseDetail"}}}},"404":{"description":"Unknown or inactive link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"409":{"description":"Survey not active, or tracked link already used","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"422":{"description":"Invalid response data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}}}}},"/cbsurveys/api/v1/surveys":{"post":{"tags":["cbsurveys","Surveys"],"summary":"Create a new survey","description":"Create a new survey in DRAFT state.\n\nThe survey starts empty with no questions. Add questions using\nPOST /surveys/{survey_id}/questions before publishing.\n\n**Metadata**: Use the metadata array to attach custom key-value pairs\nfor categorization, tracking, or integration with external systems.\nKeys must be lowercase snake_case (e.g., \"campaign_id\", \"department\").\n\n**Next Steps**:\n1. Add questions: POST /surveys/{id}/questions\n2. Publish: POST /surveys/{id}/publish","operationId":"create_survey_api_v1_surveys_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyCreate"}}}},"responses":{"201":{"description":"Survey created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"403":{"description":"Not authorized to access this resource"},"422":{"description":"Invalid request data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}}}},"get":{"tags":["cbsurveys","Surveys"],"summary":"List all surveys","description":"List all surveys owned by the authenticated user.\n\nReturns a compact list with essential metadata. Use GET /surveys/{id}\nto retrieve full details including questions.\n\n**Filtering**: Use the `state` query parameter to filter by lifecycle state.","operationId":"list_surveys_api_v1_surveys_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbsurveys__SurveyState"},{"type":"null"}],"description":"Filter by state: draft, active, inactive, or archived","title":"State"},"description":"Filter by state: draft, active, inactive, or archived"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"List of surveys owned by the authenticated user","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbsurveys__SurveyListItem"},"title":"Response List Surveys Api V1 Surveys Get"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}":{"get":{"tags":["cbsurveys","Surveys"],"summary":"Get survey details","description":"Retrieve a survey with complete question definitions.\n\nReturns the full survey object including all questions with their\ntype-specific configurations. Use this endpoint to:\n- Display the survey structure\n- Build a survey form UI\n- Validate response submissions","operationId":"get_survey_api_v1_surveys__survey_id__get","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Full survey with questions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyDetailResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}},"patch":{"tags":["cbsurveys","Surveys"],"summary":"Update survey metadata","description":"Update survey metadata (name, description, custom metadata).\n\nOnly the fields provided will be updated. Questions cannot be\nmodified through this endpoint - use the question-specific endpoints.\n\n**Note**: Archived surveys cannot be modified.","operationId":"update_survey_api_v1_surveys__survey_id__patch","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyUpdate"}}}},"responses":{"200":{"description":"Survey updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey is archived and cannot be modified","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}},"delete":{"tags":["cbsurveys","Surveys"],"summary":"Delete a survey","description":"Permanently delete a survey and all its responses.\n\n**Restrictions**: Only surveys in DRAFT or ARCHIVED state can be deleted.\nActive or inactive surveys must be archived first.\n\n**Warning**: This action is irreversible. All associated data including\nresponses will be permanently deleted.","operationId":"delete_survey_api_v1_surveys__survey_id__delete","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"204":{"description":"Survey deleted successfully"},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey must be in DRAFT or ARCHIVED state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/archive":{"post":{"tags":["cbsurveys","Surveys"],"summary":"Archive survey (any → ARCHIVED)","description":"Permanently archive a survey.\n\n**Warning**: This is a terminal state. Archived surveys:\n- Cannot be reactivated\n- Cannot accept new responses\n- Can still be read and have responses exported\n- Can be deleted permanently","operationId":"archive_survey_api_v1_surveys__survey_id__archive_post","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Survey archived","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey is already archived","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/links":{"post":{"tags":["cbsurveys","Surveys"],"summary":"Mint a public tracked link for a survey","description":"Mint a base-52 slug resolving to this survey (+ optional recipient). The public URL is https://<domain>/<slug>; hand it out via SMS/email.","operationId":"mint_link_api_v1_surveys__survey_id__links_post","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__LinkMintRequest"}}}},"responses":{"201":{"description":"Link minted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__LinkResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"403":{"description":"Not the survey owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}},"get":{"tags":["cbsurveys","Surveys"],"summary":"List a survey's minted links","description":"Every link minted for this survey (deactivated ones included), newest first — the Track view's mint history.","operationId":"list_links_api_v1_surveys__survey_id__links_get","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Minted links, newest first","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbsurveys__LinkDetail"},"title":"Response List Links Api V1 Surveys  Survey Id  Links Get"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"403":{"description":"Not the survey owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/links/{slug}":{"delete":{"tags":["cbsurveys","Surveys"],"summary":"Deactivate a minted link","description":"Soft-disable the slug — the public URL stops resolving but the row stays for audit. Responses already captured keep their attribution.","operationId":"deactivate_link_api_v1_surveys__survey_id__links__slug__delete","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"204":{"description":"Link deactivated"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"403":{"description":"Not the survey owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"404":{"description":"Survey or link not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/pause":{"post":{"tags":["cbsurveys","Surveys"],"summary":"Pause survey (ACTIVE → INACTIVE)","description":"Temporarily pause response collection.\n\nThe survey stops accepting new responses but existing data is preserved.\nUse POST /surveys/{id}/resume to reactivate.","operationId":"pause_survey_api_v1_surveys__survey_id__pause_post","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Survey paused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey must be in ACTIVE state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/publish":{"post":{"tags":["cbsurveys","Surveys"],"summary":"Publish survey (DRAFT → ACTIVE)","description":"Publish a draft survey to begin collecting responses.\n\n**Requirements**:\n- Survey must be in DRAFT state\n- Survey must have at least one question\n\n**After Publishing**:\n- Questions can no longer be modified — until the survey is un-published\n  (POST /surveys/{id}/unpublish), which is available only while it has no\n  responses\n- Response submission endpoint becomes active\n- Survey appears as \"active\" in listings","operationId":"publish_survey_api_v1_surveys__survey_id__publish_post","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Survey published and accepting responses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey must be in DRAFT state with at least one question","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/questions":{"get":{"tags":["cbsurveys","Surveys"],"summary":"List survey questions","description":"Get all questions for a survey in display order.\n\nQuestions are returned as a discriminated union - check the `type` field\nto determine the question type and available fields.","operationId":"list_questions_api_v1_surveys__survey_id__questions_get","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"List of questions in display order","content":{"application/json":{"schema":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/cbsurveys__CheckboxQuestion"},{"$ref":"#/components/schemas/cbsurveys__SingleSelectQuestion"},{"$ref":"#/components/schemas/cbsurveys__MultiSelectQuestion"},{"$ref":"#/components/schemas/cbsurveys__TextQuestion"},{"$ref":"#/components/schemas/cbsurveys__ScaleQuestion"}],"discriminator":{"propertyName":"type","mapping":{"checkbox":"#/components/schemas/CheckboxQuestion","single_select":"#/components/schemas/SingleSelectQuestion","multi_select":"#/components/schemas/MultiSelectQuestion","text":"#/components/schemas/TextQuestion","scale":"#/components/schemas/ScaleQuestion"}}},"title":"Response List Questions Api V1 Surveys  Survey Id  Questions Get"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}},"post":{"tags":["cbsurveys","Surveys"],"summary":"Add a question","description":"Add a new question to a draft survey.\n\n**Question Types**:\n\n- **checkbox**: Simple yes/no toggle. Set `default_checked` for initial state.\n\n- **single_select**: Radio button style. Requires `options` array with ≥2 items.\n  Set `allow_other` to enable free-text input.\n\n- **multi_select**: Checkbox list style. Requires `options` array with ≥2 items.\n  Use `min_selections`/`max_selections` to constrain choices.\n\n**Note**: Questions can only be added while the survey is in DRAFT state.","operationId":"add_question_api_v1_surveys__survey_id__questions_post","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__QuestionCreateRequest"}}}},"responses":{"201":{"description":"Question added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyDetailResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey must be in DRAFT state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Invalid question configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/questions/order":{"put":{"tags":["cbsurveys","Surveys"],"summary":"Reorder questions","description":"Set the display order of questions.\n\nProvide the complete list of question IDs in the desired order.\nAll existing question IDs must be included exactly once.\n\n**Example**: To move question C before A and B:\n```json\n{\"question_ids\": [\"<C-uuid>\", \"<A-uuid>\", \"<B-uuid>\"]}\n```","operationId":"reorder_questions_api_v1_surveys__survey_id__questions_order_put","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__QuestionOrderRequest"}}}},"responses":{"200":{"description":"Questions reordered successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyDetailResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey must be in DRAFT state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Question IDs don't match existing questions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/questions/{question_id}":{"put":{"tags":["cbsurveys","Surveys"],"summary":"Update a question","description":"Replace a question's configuration.\n\nThe entire question definition is replaced. Provide all required fields\nfor the question type, not just the changes.\n\n**Note**: The question type can be changed, but this replaces the entire\nquestion structure.","operationId":"update_question_api_v1_surveys__survey_id__questions__question_id__put","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"question_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Question UUID","title":"Question Id"},"description":"Question UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__QuestionCreateRequest"}}}},"responses":{"200":{"description":"Question updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyDetailResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey or question not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey must be in DRAFT state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Invalid question configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}}}},"delete":{"tags":["cbsurveys","Surveys"],"summary":"Delete a question","description":"Remove a question from a draft survey.\n\nReturns the updated survey with remaining questions.","operationId":"delete_question_api_v1_surveys__survey_id__questions__question_id__delete","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"question_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Question UUID","title":"Question Id"},"description":"Question UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Question deleted, returns updated survey","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyDetailResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey or question not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey must be in DRAFT state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/responses":{"get":{"tags":["cbsurveys","Responses"],"summary":"List responses","description":"List responses for a survey with pagination.\n\nReturns response summaries - use GET /surveys/{id}/responses/{response_id}\nfor full response details.\n\n**Pagination**: Use `limit` and `offset` for standard pagination.\nThe response includes `total` for calculating page count.\n\n**Filtering**: Use `respondent_id` to find all responses from a specific user.\n\n**Authorization**: Only the survey owner can access responses.","operationId":"list_responses_api_v1_surveys__survey_id__responses_get","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Maximum responses to return (1-1000)","default":100,"title":"Limit"},"description":"Maximum responses to return (1-1000)"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of responses to skip for pagination","default":0,"title":"Offset"},"description":"Number of responses to skip for pagination"},{"name":"respondent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by respondent ID (exact match)","title":"Respondent Id"},"description":"Filter by respondent ID (exact match)"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Paginated list of responses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__ResponseListResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this survey's responses"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}},"post":{"tags":["cbsurveys","Responses"],"summary":"Submit survey response","description":"Submit answers to a survey. **No authentication required** - this endpoint is designed for public survey collection.","operationId":"submit_response_api_v1_surveys__survey_id__responses_post","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID to submit response for","title":"Survey Id"},"description":"Survey UUID to submit response for"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__ResponseSubmitRequest"}}}},"responses":{"201":{"description":"Response submitted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__ResponseDetail"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"409":{"description":"Survey is not active (cannot accept responses)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"422":{"description":"Invalid response data or answer format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/responses/count":{"get":{"tags":["cbsurveys","Responses"],"summary":"Count responses","description":"Get the total number of responses for a survey.\n\nFaster than listing all responses when you only need the count\n(e.g., for dashboards or progress indicators).","operationId":"count_responses_api_v1_surveys__survey_id__responses_count_get","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Total response count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__ResponseCountResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this survey's responses"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/responses/{response_id}":{"get":{"tags":["cbsurveys","Responses"],"summary":"Get response details","description":"Get a specific response with full answer details.\n\nReturns all answer data including type-specific fields.\nUseful for displaying individual response details or exporting data.","operationId":"get_response_api_v1_surveys__survey_id__responses__response_id__get","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"response_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Response UUID","title":"Response Id"},"description":"Response UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Full response with all answers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__ResponseDetail"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this survey's responses"},"404":{"description":"Survey or response not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}},"delete":{"tags":["cbsurveys","Responses"],"summary":"Soft-delete a response","description":"Owner only. Sets the `deleted` flag — the response leaves every read path (lists, counts, Analyze, the single-use link guard, so the recipient's tracked link re-opens) but the row survives for audit.","operationId":"delete_response_api_v1_surveys__survey_id__responses__response_id__delete","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"response_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Response UUID","title":"Response Id"},"description":"Response UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"204":{"description":"Response soft-deleted"},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"403":{"description":"Not the survey owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"404":{"description":"Survey or response not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__responses__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/resume":{"post":{"tags":["cbsurveys","Surveys"],"summary":"Resume survey (INACTIVE → ACTIVE)","description":"Resume a paused survey to continue collecting responses.","operationId":"resume_survey_api_v1_surveys__survey_id__resume_post","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Survey resumed and accepting responses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey must be in INACTIVE state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/state":{"post":{"tags":["cbsurveys","Surveys"],"summary":"Change survey state","description":"Change the survey lifecycle state.\n\n**State Machine**:\n```\nDRAFT ◄──► ACTIVE ──► INACTIVE ──► ARCHIVED\n  │          │           │             ▲\n  │          │           └─────────────┤\n  │          └─────────────────────────┤\n  └────────────────────────────────────┘\n```\n\n**Valid Transitions**:\n- DRAFT → ACTIVE: Publish (requires ≥1 question)\n- ACTIVE → DRAFT: Un-publish to edit the questions again.\n  **Only while the survey has zero responses** — with answers on record,\n  changing the question set would change what those answers mean.\n- ACTIVE → INACTIVE: Pause response collection\n- INACTIVE → ACTIVE: Resume response collection\n- Any → ARCHIVED: Permanently close (terminal state)","operationId":"change_state_api_v1_surveys__survey_id__state_post","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__StateTransitionRequest"}}}},"responses":{"200":{"description":"State changed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Invalid state transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/transfer":{"post":{"tags":["cbsurveys","Surveys"],"summary":"Transfer a survey to another owner (administrators only)","description":"Reassign a survey to a different owner.\n\nBuilt for the case in cbsurveys#16: a contact form created by an\nintegration collected live submissions that the person who owns the site\ncould not read, because the integration owned the survey. Every read here\nis owner-scoped and there was no path from one owner to another.\n\n**Ownership is one column.** Responses, questions, and the minted link\nslugs already printed on physical material all hang off `survey_id` and are\nuntouched — which is what makes this safe to run against live data.\n\n**Administrators only** (`is_admin` on the service user). A transfer is also\nthe sanctioned way for an administrator to obtain edit rights: they cannot\nrewrite another owner's question set, but they can take ownership, and the\nowner column shows afterwards that they did.","operationId":"transfer_survey_api_v1_surveys__survey_id__transfer_post","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__TransferRequest"}}}},"responses":{"200":{"description":"Ownership reassigned; nothing else changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Caller is not an administrator","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/surveys/{survey_id}/unpublish":{"post":{"tags":["cbsurveys","Surveys"],"summary":"Un-publish survey (ACTIVE → DRAFT)","description":"Return a published survey to DRAFT so its questions can be edited.\n\n**Requirements**:\n- Survey must be in ACTIVE state\n- Survey must have **zero** responses\n\nEditing questions under a survey that already holds answers would change\nwhat those answers mean, so the door closes as soon as the first response\narrives. This covers the common case of publishing and then finding the\nwording wrong.\n\nMinted link slugs are untouched, which is the point: a printed QR code\nkeeps working across the edit (`survey_links` has no re-point path, so\narchive-and-recreate would strand printed material).\n\nUse POST /surveys/{id}/pause to stop collection without editing.","operationId":"unpublish_survey_api_v1_surveys__survey_id__unpublish_post","parameters":[{"name":"survey_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"Survey UUID","title":"Survey Id"},"description":"Survey UUID"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Survey returned to DRAFT; questions editable again","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__SurveyResponse"}}}},"401":{"description":"Missing or invalid API key"},"403":{"description":"Not authorized to access this resource"},"404":{"description":"Survey not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"409":{"description":"Survey must be ACTIVE and have zero responses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__surveys__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/users":{"post":{"tags":["cbsurveys","Users"],"summary":"Create a new user","description":"Create a new service user account.\n\nAfter creating a user, create an API key for them using\nPOST /users/me/keys (while authenticated as that user) or via CLI.\n\n**Email Uniqueness**: Each email address can only be associated with one user.","operationId":"create_user_api_v1_users_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__ServiceUserCreate"}}}},"responses":{"201":{"description":"User created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__UserResponse"}}}},"401":{"description":"Missing or invalid API key"},"409":{"description":"User with this email already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__users__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}},"get":{"tags":["cbsurveys","Users"],"summary":"List all users","description":"List all service user accounts.\n\nBy default, only active users are returned. Set `include_inactive=true`\nto also include deactivated (soft-deleted) users.","operationId":"list_users_api_v1_users_get","parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","description":"Include deactivated users in the list","default":false,"title":"Include Inactive"},"description":"Include deactivated users in the list"},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"List of all service users","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbsurveys__UserResponse"},"title":"Response List Users Api V1 Users Get"}}}},"401":{"description":"Missing or invalid API key"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/users/me":{"get":{"tags":["cbsurveys","Users"],"summary":"Get current user","description":"Get information about the currently authenticated user.\n\nThis endpoint can be used to verify API key validity and retrieve\nthe associated user account details.","operationId":"get_current_user_info_api_v1_users_me_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"Current authenticated user's information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__UserResponse"}}}},"401":{"description":"Missing or invalid API key"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/users/me/keys":{"post":{"tags":["cbsurveys","Users"],"summary":"Create API key","description":"Create a new API key for the authenticated user.\n\n**CRITICAL**: The full API key is only returned in this response.\nStore it securely immediately - it cannot be retrieved later.\n\n**Key Format**: `yasp_<43-char-random-token>`\n\n**Best Practices**:\n- Use descriptive names (e.g., \"prod-api-server\", \"staging-worker\")\n- Create separate keys for different environments/services\n- Rotate keys periodically by creating new ones and revoking old ones\n\n**Usage**: Include the key in requests via:\n- Header: `X-API-Key: yasp_xxxxx`\n- Or: `Authorization: Bearer yasp_xxxxx`","operationId":"create_api_key_api_v1_users_me_keys_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__APIKeyCreateRequest"}}}},"responses":{"201":{"description":"API key created - SAVE THE KEY VALUE!","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__APIKeyCreateResponse"}}}},"401":{"description":"Missing or invalid API key"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}},"get":{"tags":["cbsurveys","Users"],"summary":"List my API keys","description":"List all API keys belonging to the authenticated user.\n\nReturns key metadata only - the actual key values are never retrievable\nafter creation. Use the `prefix` field to identify which key is which.","operationId":"list_my_api_keys_api_v1_users_me_keys_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"List of API keys for the authenticated user","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbsurveys__APIKeyResponse"},"title":"Response List My Api Keys Api V1 Users Me Keys Get"}}}},"401":{"description":"Missing or invalid API key"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/users/me/keys/{key_id}":{"delete":{"tags":["cbsurveys","Users"],"summary":"Revoke API key","description":"Permanently revoke an API key.\n\nRevoked keys:\n- Immediately stop working for authentication\n- Cannot be reactivated\n- Remain visible in key listings (with `is_active: false`)\n\n**Use Cases**:\n- Key rotation: create new key, update systems, revoke old key\n- Security incident: immediately revoke compromised keys\n- Cleanup: remove keys for decommissioned systems","operationId":"revoke_api_key_api_v1_users_me_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Key Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"204":{"description":"API key revoked successfully"},"401":{"description":"Missing or invalid API key"},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__users__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/api/v1/users/{user_id}":{"get":{"tags":["cbsurveys","Users"],"summary":"Get user by ID","description":"Get a specific user by their UUID.","operationId":"get_user_api_v1_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"200":{"description":"User information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__UserResponse"}}}},"401":{"description":"Missing or invalid API key"},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__users__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}},"delete":{"tags":["cbsurveys","Users"],"summary":"Deactivate a user","description":"Deactivate a user account (soft delete).\n\nDeactivated users:\n- Cannot authenticate with any of their API keys\n- Are hidden from user listings by default\n- Retain all their data (surveys, responses)\n\n**Note**: This does not delete the user's data. Surveys and responses\nremain accessible to other administrators.","operationId":"deactivate_user_api_v1_users__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-cb-source","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Source"}},{"name":"x-user-email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}}],"responses":{"204":{"description":"User deactivated successfully"},"401":{"description":"Missing or invalid API key"},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__yasp__api__routes__users__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HTTPValidationError"}}}}}}},"/cbsurveys/health":{"get":{"tags":["cbsurveys","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HealthResponse"}}}}}}},"/cbsurveys/health/detailed":{"get":{"tags":["cbsurveys","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__HealthResponse"}}}}}}},"/cbsurveys/prime":{"get":{"tags":["cbsurveys","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__PrimeResponse"}}}}}}},"/cbsurveys/status":{"get":{"tags":["cbsurveys","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbsurveys__StatusResponse"}}}}}}},"/cbtags/api/v1/entities/{entity_type}/{entity_id}/tags":{"get":{"tags":["cbtags","CBTAGS"],"summary":"Tags on a record","description":"Forward lookup: return every tag bound to the record identified by the `(entity_type, entity_id)` path pair, scoped to the calling tenant. A record with no tags yields an empty list. Read-only.","operationId":"tags_for_entity_api_v1_entities__entity_type___entity_id__tags_get","parameters":[{"name":"entity_type","in":"path","required":true,"schema":{"type":"string","title":"Entity Type"}},{"name":"entity_id","in":"path","required":true,"schema":{"type":"string","title":"Entity Id"}},{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbtags__Tag"},"title":"Response Tags For Entity Api V1 Entities  Entity Type   Entity Id  Tags Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}}},"/cbtags/api/v1/stats":{"get":{"tags":["cbtags","CBTAGS"],"summary":"Tag usage stats","description":"Return every tag for the calling tenant paired with its binding `count` (how many records currently carry it). Useful for tag-cloud sizing, pruning unused tags, and admin dashboards. Read-only.","operationId":"stats_api_v1_stats_get","parameters":[{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbtags__TagUsage"},"title":"Response Stats Api V1 Stats Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}}},"/cbtags/api/v1/tags":{"get":{"tags":["cbtags","CBTAGS"],"summary":"List tags","description":"Return all tags for the calling tenant (`X-Tenant-ID`, default `default`), ordered by name. Pass the optional `category` query parameter to restrict the result to a single category. Read-only — no write auth required.","operationId":"list_tags_api_v1_tags_get","parameters":[{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbtags__Tag"},"title":"Response List Tags Api V1 Tags Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}},"post":{"tags":["cbtags","CBTAGS"],"summary":"Create (or get) a tag","description":"Create a tag for the calling tenant. **Idempotent on name:** if a tag with the same name already exists for the tenant, the existing tag is returned unchanged rather than raising a conflict. `category` and `color` are optional. Requires write auth (`X-API-Key` when `CBTAGS_API_KEY` is set; otherwise the upstream nginx cbauth gate).","operationId":"create_tag_api_v1_tags_post","parameters":[{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__TagCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__Tag"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}}},"/cbtags/api/v1/tags/by-name/{name}":{"get":{"tags":["cbtags","CBTAGS"],"summary":"Get a tag by name","description":"Resolve a tag by its human-readable name, scoped to the calling tenant. Names are unique per tenant, so this is exact. Returns **404** if no such tag exists.\n\nThis is the slug-to-id step: every other endpoint takes a `tag_id`, and without this you would have to list the tenant's tags and filter client-side just to look one up. Read-only.","operationId":"get_tag_by_name_api_v1_tags_by_name__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__Tag"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}}},"/cbtags/api/v1/tags/{tag_id}":{"get":{"tags":["cbtags","CBTAGS"],"summary":"Get a tag","description":"Fetch a single tag by id, scoped to the calling tenant. Returns **404** if no tag with that id exists for the tenant. Read-only.","operationId":"get_tag_api_v1_tags__tag_id__get","parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","title":"Tag Id"}},{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__Tag"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}},"patch":{"tags":["cbtags","CBTAGS"],"summary":"Update a tag","description":"Partially update a tag's `name`, `category`, and/or `color`. Only the fields present in the body are changed; omitted fields are left untouched. Returns the updated tag, or **404** if the tag does not exist for the tenant. Requires write auth.","operationId":"update_tag_api_v1_tags__tag_id__patch","parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","title":"Tag Id"}},{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__TagUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__Tag"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}},"delete":{"tags":["cbtags","CBTAGS"],"summary":"Delete a tag (cascades bindings)","description":"Permanently delete a tag and **cascade-remove every binding** that referenced it (record_tag rows are dropped via foreign key). Returns `{\"ok\": true}` on success, or **404** if the tag does not exist for the tenant. Requires write auth.","operationId":"delete_tag_api_v1_tags__tag_id__delete","parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","title":"Tag Id"}},{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}}},"/cbtags/api/v1/tags/{tag_id}/assign":{"post":{"tags":["cbtags","CBTAGS"],"summary":"Tag a record","description":"Bind the tag to a record identified by the polymorphic `(entity_type, entity_id)` pair in the body. **Idempotent:** re-assigning an existing binding is a no-op and still returns success. Returns `{\"ok\": true}`, or **404** if the tag does not exist for the tenant. Requires write auth.","operationId":"assign_api_v1_tags__tag_id__assign_post","parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","title":"Tag Id"}},{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__EntityRef"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}}},"/cbtags/api/v1/tags/{tag_id}/entities":{"get":{"tags":["cbtags","CBTAGS"],"summary":"Records carrying a tag","description":"Reverse lookup: return every record bound to this tag as a list of `{entity_type, entity_id, value}` objects, ordered by type then id. An unknown or unbound `tag_id` yields an empty list (no 404).\n\nPass the optional `value` query parameter to return only bindings carrying that value — this is how one tag serves both the full view and a narrower slice of it (e.g. `?value=ephemeral`). Read-only.","operationId":"entities_by_tag_api_v1_tags__tag_id__entities_get","parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","title":"Tag Id"}},{"name":"value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value"}},{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbtags__EntityBinding"},"title":"Response Entities By Tag Api V1 Tags  Tag Id  Entities Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}}},"/cbtags/api/v1/tags/{tag_id}/unassign":{"post":{"tags":["cbtags","CBTAGS"],"summary":"Untag a record","description":"Remove the binding between the tag and the record identified by the `(entity_type, entity_id)` pair in the body. **Idempotent:** unassigning a binding that does not exist is a no-op and still returns `{\"ok\": true}`. Requires write auth.","operationId":"unassign_api_v1_tags__tag_id__unassign_post","parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","title":"Tag Id"}},{"name":"x-tenant-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Tenant-Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__EntityRef"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HTTPValidationError"}}}}}}},"/cbtags/health":{"get":{"tags":["cbtags","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HealthResponse"}}}}}}},"/cbtags/health/detailed":{"get":{"tags":["cbtags","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__HealthResponse"}}}}}}},"/cbtags/prime":{"get":{"tags":["cbtags","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__PrimeResponse"}}}}}}},"/cbtags/status":{"get":{"tags":["cbtags","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtags__StatusResponse"}}}}}}},"/cbtapwatch/anomalies":{"get":{"tags":["cbtapwatch","Stats"],"summary":"Anomalies","description":"Evaluate rules against the current rolling counters.\n\nReturns newly-fired anomalies (respecting per-key cooldown). Called\nperiodically in M2.3 to push incidents to cboverseer; safe to poll.","operationId":"anomalies_anomalies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Anomalies Anomalies Get"}}}}}}},"/cbtapwatch/health":{"get":{"tags":["cbtapwatch","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtapwatch__HealthResponse"}}}}}}},"/cbtapwatch/health/detailed":{"get":{"tags":["cbtapwatch","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtapwatch__HealthResponse"}}}}}}},"/cbtapwatch/prime":{"get":{"tags":["cbtapwatch","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtapwatch__PrimeResponse"}}}}}}},"/cbtapwatch/stats":{"get":{"tags":["cbtapwatch","Stats"],"summary":"Stats","operationId":"stats_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Stats Stats Get"}}}}}}},"/cbtapwatch/stats/windows":{"get":{"tags":["cbtapwatch","Stats"],"summary":"Stats Windows","description":"Aggregate counts + percentiles over a rolling window.\n\nIf `svc` and `ep` are omitted, returns the top services by volume.\nWith `svc` (and optionally `ep`), returns a single aggregated row.","operationId":"stats_windows_stats_windows_get","parameters":[{"name":"window","in":"query","required":false,"schema":{"type":"integer","maximum":3600,"minimum":10,"description":"Window in seconds","default":300,"title":"Window"},"description":"Window in seconds"},{"name":"svc","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by svc","title":"Svc"},"description":"Filter by svc"},{"name":"ep","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by ep","title":"Ep"},"description":"Filter by ep"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Stats Windows Stats Windows Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtapwatch__HTTPValidationError"}}}}}}},"/cbtapwatch/status":{"get":{"tags":["cbtapwatch","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtapwatch__StatusResponse"}}}}}}},"/cbtenant/api/auth/login":{"post":{"tags":["cbtenant","Authentication"],"summary":"Login with OAuth2 form","description":"Authenticate user and return JWT token (OAuth2 form endpoint).\n\n**Request format:** `application/x-www-form-urlencoded`\n- `username`: User login name\n- `password`: User password\n\n**Response:** JWT Bearer token valid for 30 minutes.\n\n**Usage in subsequent requests:**\n```\nAuthorization: Bearer <access_token>\n```\n\n**Note:** For JSON-based authentication, use POST /api/auth/login/json instead.","operationId":"login_api_auth_login_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/cbtenant__Body_login_api_auth_login_post"}}},"required":true},"responses":{"200":{"description":"JWT token for authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__Token"}}}},"401":{"description":"Invalid username or password"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/auth/login/json":{"post":{"tags":["cbtenant","Authentication"],"summary":"Login with JSON credentials","description":"Authenticate user and return JWT token (JSON endpoint).\n\n**Request format:** `application/json`\n```json\n{\n    \"username\": \"admin\",\n    \"password\": \"your-password\"\n}\n```\n\n**Response:** JWT Bearer token valid for 30 minutes.\n\n**Preferred for:**\n- Programmatic/API access\n- CLI tools\n- Integration testing\n\nFor browser-based login, use POST /api/auth/login (OAuth2 form).","operationId":"login_json_api_auth_login_json_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__LoginRequest"}}},"required":true},"responses":{"200":{"description":"JWT token for authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__Token"}}}},"401":{"description":"Invalid username or password"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/auth/logout":{"post":{"tags":["cbtenant","Authentication"],"summary":"Logout (client-side)","description":"Logout endpoint for JWT-based authentication.\n\n**Note:** With stateless JWT tokens, actual logout happens client-side\nby discarding the token. This endpoint:\n- Validates the current token is still valid\n- Returns success acknowledgment\n- Can be used for audit logging\n\n**Client should:** Delete stored token after calling this endpoint.","operationId":"logout_api_auth_logout_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Logout acknowledgment","content":{"application/json":{"schema":{}}}},"401":{"description":"Not authenticated"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/auth/me":{"get":{"tags":["cbtenant","Authentication"],"summary":"Get current user","description":"Get information about the currently authenticated user.\n\n**Requires:** Valid Bearer token in Authorization header.\n\n**Returns:**\n- User ID, username, email\n- Role (admin or user)\n- Account status (is_active)\n- Timestamps (created_at, last_login)\n\n**Use cases:**\n- Verify authentication is working\n- Get current user's role for UI permissions\n- Display user info in profile","operationId":"get_current_user_info_api_auth_me_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Current user information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__UserResponse"}}}},"401":{"description":"Not authenticated or token expired"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/github/webhook":{"post":{"tags":["cbtenant","Webhooks"],"summary":"Github Webhook","description":"GitHub webhook endpoint for automated deployments.\n\nTriggers:\n- **create** event with ref_type=tag: Full upgrade to new tag (production releases)\n- **push** to main (optional): Quick pull for hotfixes (if GITHUB_WEBHOOK_DEPLOY_ON_PUSH=true)\n\nSecurity:\n- Validates X-Hub-Signature-256 header using GITHUB_WEBHOOK_SECRET\n\nSetup in GitHub:\n1. Go to repo Settings > Webhooks > Add webhook\n2. Payload URL: https://tenant.campaignbrain.dev/api/github/webhook\n3. Content type: application/json\n4. Secret: (same as GITHUB_WEBHOOK_SECRET env var)\n5. Events: \"Create\" for tags only, or add \"Push\" for all commits","operationId":"github_webhook_api_github_webhook_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__WebhookResponse"}}}}}}},"/cbtenant/api/github/webhook/status":{"get":{"tags":["cbtenant","Webhooks"],"summary":"Webhook Status","description":"Check webhook configuration status.","operationId":"webhook_status_api_github_webhook_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbtenant/api/ports":{"get":{"tags":["cbtenant","Ports"],"summary":"List all port allocations","description":"List all port allocations in the pool (32300-32399).\n\n**Each allocation shows:**\n- Port pair (frontend + backend)\n- Assigned tenant (if any)\n- Status: available, in_use, or conflict\n- System port status (actual network usage)\n\n**Conflict detection:**\nIf a port is marked \"available\" in the database but actually in use\non the system, it will show `status: conflict`.\n\n**Summary counts:**\n- `available_count`: Ports ready for allocation\n- `in_use_count`: Ports assigned to tenants","operationId":"list_port_allocations_api_ports_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"All port allocations with status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__PortsListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/ports/next-available":{"get":{"tags":["cbtenant","Ports"],"summary":"Get next available port pair","description":"Get the next available port pair for a new tenant.\n\n**Validation:**\n1. Checks database for unallocated ports\n2. Verifies ports are actually free on the system (socket bind test)\n3. Returns first port pair that passes both checks\n\n**Used by:**\n- Create tenant endpoint (auto-allocation)\n- UI for displaying next available ports\n\n**Error cases:**\n- No unallocated ports remaining in pool\n- All unallocated ports are in use by external processes","operationId":"get_next_available_ports_api_ports_next_available_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Available port pair for new tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__NextAvailablePortsResponse"}}}},"400":{"description":"No available ports in pool"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/settings":{"get":{"tags":["cbtenant","Settings"],"summary":"List all settings","description":"List all global application settings.\n\n**Secret handling:**\nSettings marked as `is_secret=true` show value as `***hidden***`.\n\n**Common settings:**\n- `anthropic_api_key`: API key for Claude Vision theme analysis\n- `github_webhook_secret`: Secret for validating GitHub webhooks\n\n**Sorted alphabetically by key.**","operationId":"list_settings_api_settings_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"All application settings","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbtenant__SettingResponse"},"title":"Response List Settings Api Settings Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/settings/{key}":{"get":{"tags":["cbtenant","Settings"],"summary":"Get a setting by key","description":"Get a specific setting by its key.\n\n**Secret handling:**\nSettings marked `is_secret=true` return `***hidden***` as the value.\n\n**Example keys:**\n- `anthropic_api_key`\n- `github_webhook_secret`","operationId":"get_setting_api_settings__key__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Setting value and metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__SettingResponse"}}}},"404":{"description":"Setting not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"put":{"tags":["cbtenant","Settings"],"summary":"Create or update a setting","description":"Create or update a setting value.\n\n**Behavior:**\n- If setting exists: Updates the value\n- If setting doesn't exist: Creates new setting with `value_type=string`\n\n**For secrets:**\nThe full value is stored but will be masked in responses.\n\n**Example:**\n```json\nPUT /api/settings/anthropic_api_key\n{\"value\": \"sk-ant-api...\"}\n```","operationId":"update_setting_api_settings__key__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__SettingUpdate"}}}},"responses":{"200":{"description":"Setting created or updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__SettingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"delete":{"tags":["cbtenant","Settings"],"summary":"Delete a setting","description":"Delete a setting by key.\n\n**Warning:** This permanently removes the setting.\nFeatures depending on this setting may stop working.","operationId":"delete_setting_api_settings__key__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Setting deleted successfully","content":{"application/json":{"schema":{}}}},"404":{"description":"Setting not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants":{"post":{"tags":["cbtenant","Tenants"],"summary":"Create a new tenant","description":"Create a new CampaignBrain tenant instance.\n\n**What this endpoint does:**\n1. Validates slug and domain are unique\n2. Auto-allocates port pair if not provided (from 32300-32399 range)\n3. Creates tenant record with status='pending'\n4. Creates default configuration record\n5. Logs creation in audit trail\n\n**After creating a tenant, you should:**\n1. Deploy DNS records: `POST /api/tenants/{tenant_id}/dns/add`\n2. Obtain SSL certificates: `POST /api/tenants/{tenant_id}/ssl/obtain`\n3. Generate NGINX configs: `POST /api/tenants/{tenant_id}/nginx/generate`\n4. Enable NGINX: `POST /api/tenants/{tenant_id}/nginx/enable`\n5. Start services: `POST /api/tenants/{tenant_id}/services/start`\n\n**Example request:**\n```json\n{\n    \"name\": \"MI-20 Clevenger Campaign\",\n    \"slug\": \"mi20-clevenger\",\n    \"domain\": \"mi20.campaignbrain.dev\",\n    \"api_domain\": \"mi20-api.campaignbrain.dev\"\n}\n```","operationId":"create_tenant_api_tenants_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantCreate"}}}},"responses":{"201":{"description":"Tenant created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantResponse"}}}},"400":{"description":"Invalid port range or ports already allocated"},"409":{"description":"Tenant with this slug or domain already exists"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"get":{"tags":["cbtenant","Tenants"],"summary":"List all tenants","description":"List all tenants with optional filtering and pagination.\n\n**Use cases:**\n- Dashboard overview of all tenants\n- Search for specific campaign by name or domain\n- Filter to see only active or stopped tenants\n\n**Filtering:**\n- `status`: Exact match on tenant status\n- `search`: Partial match on name, slug, or domain (case-insensitive)\n\n**Pagination:**\n- Results are ordered by creation date (newest first)\n- Use `offset` and `limit` for pagination\n- `total` in response indicates total matching records\n\n**Example:** `GET /api/tenants?status=active&search=clevenger&limit=10`","operationId":"list_tenants_api_tenants_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tenant status: pending, active, stopped, error","title":"Status"},"description":"Filter by tenant status: pending, active, stopped, error"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search in tenant name, slug, or domain (case-insensitive)","title":"Search"},"description":"Search in tenant name, slug, or domain (case-insensitive)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of results to return (1-100)","default":50,"title":"Limit"},"description":"Maximum number of results to return (1-100)"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Number of records to skip for pagination","default":0,"title":"Offset"},"description":"Number of records to skip for pagination"},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Paginated list of tenants","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/by-subdomain/{host}":{"get":{"tags":["cbtenant","Brand (Public)"],"summary":"Resolve a Host header to a tenant (public)","description":"Public resolver — given a Host header, return the tenant slug and core\nidentity fields. Used by Loom's edge middleware to load brand context\nbefore the user's session exists.","operationId":"get_tenant_by_subdomain_api_tenants_by_subdomain__host__get","parameters":[{"name":"host","in":"path","required":true,"schema":{"type":"string","title":"Host"}}],"responses":{"200":{"description":"Tenant slug/name/domain","content":{"application/json":{"schema":{}}}},"404":{"description":"No tenant matches this host"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/labels":{"get":{"tags":["cbtenant","Brand (Public)"],"summary":"Lightweight {slug, name, display_name} list for every tenant (public)","description":"Public lightweight tenant catalog — slugs + friendly labels only.\n\nUsed by Loom to enrich the tenant switcher dropdown (which only has\nslugs from the JWT) with friendly names. Returns only labels — no\ndomains, ports, or sensitive fields. Safe to expose without auth.","operationId":"list_tenant_labels_api_tenants_labels_get","responses":{"200":{"description":"Array of {slug, name, display_name} objects","content":{"application/json":{"schema":{}}}}}}},"/cbtenant/api/tenants/{slug}/apps":{"get":{"tags":["cbtenant","Domains & Apps"],"summary":"List all apps for a tenant (public)","operationId":"list_tenant_apps_api_tenants__slug__apps_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbtenant__TenantAppResponse"},"title":"Response List Tenant Apps Api Tenants  Slug  Apps Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"post":{"tags":["cbtenant","Domains & Apps"],"summary":"Add an app to a tenant","operationId":"create_tenant_app_api_tenants__slug__apps_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantAppCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantAppResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/apps/{app_id}":{"patch":{"tags":["cbtenant","Domains & Apps"],"summary":"Update a tenant app","operationId":"update_tenant_app_api_tenants__slug__apps__app_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","title":"App Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantAppUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantAppResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"delete":{"tags":["cbtenant","Domains & Apps"],"summary":"Delete a tenant app","operationId":"delete_tenant_app_api_tenants__slug__apps__app_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","title":"App Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/brand":{"get":{"tags":["cbtenant","Brand (Public)"],"summary":"Public brand metadata for a tenant","description":"Public — palette_light + palette_dark + logos + tagline.\n\nClients pick the palette that matches the user's mode preference\n(prefers-color-scheme or a manual toggle). Logos/tagline/favicon are\nshared across modes today — one asset set per tenant.\n\nIf a tenant has no theme row for a given mode, falls back to the\nLoom default for that mode so new tenants render predictably.","operationId":"get_tenant_brand_api_tenants__slug__brand_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Palette + logos + tagline","content":{"application/json":{"schema":{}}}},"404":{"description":"Tenant or theme not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/domains":{"get":{"tags":["cbtenant","Domains & Apps"],"summary":"List all domains for a tenant (public)","operationId":"list_tenant_domains_api_tenants__slug__domains_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbtenant__TenantDomainResponse"},"title":"Response List Tenant Domains Api Tenants  Slug  Domains Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"post":{"tags":["cbtenant","Domains & Apps"],"summary":"Add a domain to a tenant","operationId":"create_tenant_domain_api_tenants__slug__domains_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantDomainCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantDomainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/domains/{domain_id}":{"patch":{"tags":["cbtenant","Domains & Apps"],"summary":"Update a tenant domain","operationId":"update_tenant_domain_api_tenants__slug__domains__domain_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","title":"Domain Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantDomainUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantDomainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"delete":{"tags":["cbtenant","Domains & Apps"],"summary":"Delete a tenant domain","operationId":"delete_tenant_domain_api_tenants__slug__domains__domain_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","title":"Domain Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/theme/analysis/{analysis_id}":{"get":{"tags":["cbtenant","Theme Analysis"],"summary":"Poll status / fetch results of a theme analysis","operationId":"get_analysis_status_api_tenants__slug__theme_analysis__analysis_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"analysis_id","in":"path","required":true,"schema":{"type":"string","title":"Analysis Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__ThemeAnalysisStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/theme/analyze":{"post":{"tags":["cbtenant","Theme Analysis"],"summary":"Start AI theme analysis from a URL","description":"Capture a screenshot of the URL, run Claude Vision against it, and\npersist 3 palette suggestions for the tenant. Poll the status endpoint.\n\nReturns immediately with an existing cached `analysis_id` (status =\n'complete') if the same tenant+url ran successfully within the past\n`ANALYSIS_CACHE_DAYS` days — avoids spamming Claude when a user\nre-clicks the button. Pass `?force=1` to ignore the cache.","operationId":"start_theme_analysis_api_tenants__slug__theme_analyze_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Bypass the 3-day result cache and run a fresh analysis","default":false,"title":"Force"},"description":"Bypass the 3-day result cache and run a fresh analysis"},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__ThemeAnalysisRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__ThemeAnalysisStartResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/theme/history":{"get":{"tags":["cbtenant","Theme Analysis"],"summary":"List past theme analyses for a tenant","operationId":"get_analysis_history_api_tenants__slug__theme_history_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"Limit"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__AnalysisHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/theme/screenshot/{analysis_id}":{"get":{"tags":["cbtenant","Theme Analysis"],"summary":"Fetch the screenshot captured during analysis","operationId":"get_analysis_screenshot_api_tenants__slug__theme_screenshot__analysis_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"analysis_id","in":"path","required":true,"schema":{"type":"string","title":"Analysis Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/themes":{"get":{"tags":["cbtenant","Themes"],"summary":"List every theme row for a tenant (admin only)","operationId":"list_themes_api_tenants__slug__themes_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbtenant__TenantThemeResponse"},"title":"Response List Themes Api Tenants  Slug  Themes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"post":{"tags":["cbtenant","Themes"],"summary":"Create a new theme row (admin only)","operationId":"create_theme_api_tenants__slug__themes_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantThemeCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantThemeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/themes/{theme_id}":{"put":{"tags":["cbtenant","Themes"],"summary":"Update an existing theme row (admin only)","operationId":"update_theme_api_tenants__slug__themes__theme_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"theme_id","in":"path","required":true,"schema":{"type":"string","title":"Theme Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantThemeUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantThemeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"delete":{"tags":["cbtenant","Themes"],"summary":"Delete a theme row (admin only)","operationId":"delete_theme_api_tenants__slug__themes__theme_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"theme_id","in":"path","required":true,"schema":{"type":"string","title":"Theme Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{slug}/themes/{theme_id}/activate":{"post":{"tags":["cbtenant","Themes"],"summary":"Make this theme the active one (admin only)","operationId":"activate_theme_api_tenants__slug__themes__theme_id__activate_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"theme_id","in":"path","required":true,"schema":{"type":"string","title":"Theme Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantThemeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}":{"get":{"tags":["cbtenant","Tenants"],"summary":"Get tenant details","description":"Get detailed tenant information including configuration and theme.\n\n**Returns:**\n- Basic tenant info (name, slug, domain, ports, status)\n- Current configuration settings\n- Active theme colors and branding\n- Latest deployment record\n\n**Use cases:**\n- Tenant detail page in UI\n- Checking current configuration before updates\n- Verifying deployment status","operationId":"get_tenant_api_tenants__tenant_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Tenant details with configuration and theme","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantDetailResponse"}}}},"404":{"description":"Tenant not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"put":{"tags":["cbtenant","Tenants"],"summary":"Update tenant metadata","description":"Update tenant metadata (name, domain, notes, etc.).\n\n**Note:** Only provided fields will be updated.\n\n**Important:** Changing domain/api_domain requires:\n1. Update DNS records\n2. Obtain new SSL certificates\n3. Regenerate and enable NGINX configs\n4. Restart services\n\n**Cannot be changed:** slug, directory\n\n**Ports:** backend_port/frontend_port are normally auto-allocated at create\ntime. They are settable here to record or correct the port of a\nhand-provisioned instance (e.g. an API-only cbapp CRM attached to an\nexisting site tenant, cbloom#218). Changing a port only updates the record\n— update the systemd unit and restart the service separately.","operationId":"update_tenant_api_tenants__tenant_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantUpdate"}}}},"responses":{"200":{"description":"Tenant updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantResponse"}}}},"404":{"description":"Tenant not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"delete":{"tags":["cbtenant","Tenants"],"summary":"Delete a tenant","description":"Delete a tenant and optionally clean up associated resources.\n\n**Cleanup operations (controlled by flags):**\n- **remove_services** (default: true): Stops services, kills orphan processes, removes systemd units\n- **remove_nginx** (default: true): Removes NGINX configs and reloads\n- **remove_files** (default: false): Deletes entire tenant directory (DESTRUCTIVE)\n\n**Always performed:**\n- Database records deleted (cascades to config, themes, deployments, logs)\n- Port allocation released back to pool\n\n**Warning:** `remove_files=true` permanently deletes all tenant data including databases.\nThere is no undo.\n\n**Response includes cleanup results** showing what was actually removed.","operationId":"delete_tenant_api_tenants__tenant_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"remove_files","in":"query","required":false,"schema":{"type":"boolean","description":"Delete tenant directory and all files (DESTRUCTIVE, default: false)","default":false,"title":"Remove Files"},"description":"Delete tenant directory and all files (DESTRUCTIVE, default: false)"},{"name":"remove_nginx","in":"query","required":false,"schema":{"type":"boolean","description":"Remove NGINX configuration files and reload","default":true,"title":"Remove Nginx"},"description":"Remove NGINX configuration files and reload"},{"name":"remove_services","in":"query","required":false,"schema":{"type":"boolean","description":"Stop and remove systemd service files","default":true,"title":"Remove Services"},"description":"Stop and remove systemd service files"},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Tenant deleted with cleanup results","content":{"application/json":{"schema":{}}}},"404":{"description":"Tenant not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/admins":{"post":{"tags":["cbtenant","Tenants"],"summary":"Provision a first admin user for a tenant","description":"Provision a first admin user for the given tenant.\n\nBehavior:\n- Looks up the tenant by id (404 if missing).\n- If a cbauth user with this email already exists: reuse it. Otherwise\n  create one with a generated temp password.\n- Grants the user `admin` membership on the tenant via cbauth's\n  `/api/v1/users/{user_id}/tenants` endpoint (idempotent).\n- Returns a reveal-once payload: `{user_id, email, login_url,\n  temp_password, ...}`. The operator MUST capture the password —\n  it is not stored anywhere on this side. cbauth has the bcrypt hash.\n\nThe login URL is `https://<slug>-loom.campaignbrain.dev/auth/login`,\nwhich routes to cbloom via the existing nginx wildcard regex.","operationId":"create_first_admin_api_tenants__tenant_id__admins_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__FirstAdminCreate"}}}},"responses":{"201":{"description":"Admin user created and granted tenant membership","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__FirstAdminResponse"}}}},"404":{"description":"Tenant not found"},"409":{"description":"User already an admin of this tenant"},"502":{"description":"cbauth unreachable or rejected the request"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/config":{"get":{"tags":["cbtenant","Configuration"],"summary":"Get tenant configuration","description":"Get current configuration for a tenant.\n\n**Configuration includes:**\n- Application naming (app_name, app_name_short)\n- Database and service URLs\n- Ollama model settings\n- Custom environment variables\n\n**Use cases:**\n- View current settings before updating\n- Verify configuration after changes\n- Debug connection issues","operationId":"get_tenant_config_api_tenants__tenant_id__config_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Current configuration settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantConfigResponse"}}}},"404":{"description":"Tenant or configuration not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}},"put":{"tags":["cbtenant","Configuration"],"summary":"Update tenant configuration","description":"Update tenant configuration settings.\n\n**Only provided fields are updated.** Omit fields to keep current values.\n\n**After updating configuration:**\n1. Regenerate .env file: `POST /{tenant_id}/config/regenerate-env`\n2. Restart services for changes to take effect\n\n**Updatable fields:**\n- `app_name`, `app_name_short`: Branding text\n- `admin_email`: Notification recipient\n- `ollama_*`: Model and API settings\n- `custom_env`: Additional environment variables\n\n**Changes are logged** in the audit trail with user attribution.","operationId":"update_tenant_config_api_tenants__tenant_id__config_put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantConfigUpdate"}}}},"responses":{"200":{"description":"Configuration updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__TenantConfigResponse"}}}},"404":{"description":"Tenant or configuration not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/config/regenerate-env":{"post":{"tags":["cbtenant","Configuration"],"summary":"Regenerate tenant .env file","description":"Regenerate .env file from database configuration.\n\n**What this does:**\n1. Creates timestamped backup of existing .env file\n2. Builds new .env from current configuration values\n3. Includes all standard variables (APP_NAME, DB_PATH, ports, URLs)\n4. Appends custom environment variables from `custom_env`\n5. Writes to tenant's directory\n\n**After regenerating:**\n- Restart services for changes to take effect\n- `sudo systemctl restart cbtenant.{slug}-api cbtenant.{slug}-frontend`\n\n**Response includes:**\n- Path to new .env file\n- Path to backup file (if created)\n- Preview of first 500 characters\n- Custom variables applied","operationId":"regenerate_env_file_api_tenants__tenant_id__config_regenerate_env_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":".env file regenerated successfully","content":{"application/json":{"schema":{}}}},"400":{"description":"Tenant directory does not exist"},"404":{"description":"Tenant or configuration not found"},"500":{"description":"Failed to write .env file"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/dns/add":{"post":{"tags":["cbtenant","Deployment"],"summary":"Add Dns Records","description":"Add DNS A records for a tenant to the zone file.\n\nAdds records for both domain and api_domain.","operationId":"add_dns_records_api_tenants__tenant_id__dns_add_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/nginx/enable":{"post":{"tags":["cbtenant","Deployment"],"summary":"Enable Nginx Config","description":"Enable NGINX configuration for a tenant.\n\nCreates symlinks in sites-enabled and reloads NGINX.\nRequires SSL certificates to be in place.","operationId":"enable_nginx_config_api_tenants__tenant_id__nginx_enable_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/nginx/generate":{"post":{"tags":["cbtenant","Deployment"],"summary":"Generate Nginx Config","description":"Generate NGINX configuration files for a tenant.\n\nThis creates the config files but does not enable them.","operationId":"generate_nginx_config_api_tenants__tenant_id__nginx_generate_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__NginxConfigResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/pull":{"post":{"tags":["cbtenant","Upgrade"],"summary":"Quick Pull Tenant","description":"Quick pull latest changes for a tenant (hotfix deployment).\n\nThis is a fast path for small changes that don't require:\n- Database backup\n- Migrations\n- pip install\n\nJust: git pull + optional service restart.\n\nArgs:\n    restart: Which services to restart\n        - \"frontend\": Restart only frontend\n        - \"backend\": Restart only backend/API\n        - \"both\": Restart both (default)\n        - \"none\": Don't restart (manual restart needed)","operationId":"quick_pull_tenant_api_tenants__tenant_id__pull_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__PullRequest","default":{"restart":"both"}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__PullResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/services/provision":{"post":{"tags":["cbtenant","Deployment"],"summary":"Provision external services","description":"Provision external services (CBFiles, cbsurveys, Anthropic) for a tenant.\n\nThis endpoint creates:\n- **CBFiles**: User account, private bucket, API key\n- **cbsurveys**: User account, API key for survey management\n- **Anthropic**: Copies global API key to tenant config\n\nProvisioning continues even if individual services fail.\nResults are stored in the tenant's `custom_env` configuration.\n\n**Prerequisites:**\n- Admin keys must be configured in Settings:\n  - `cbfiles_admin_key`: Admin API key for CBFiles\n  - `cbsurveys_admin_key`: Admin API key for cbsurveys\n  - `anthropic_api_key`: Shared Anthropic API key\n\n**After provisioning:**\n- The tenant's .env file is automatically regenerated\n- Restart services for changes to take effect","operationId":"provision_services_api_tenants__tenant_id__services_provision_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Provisioning completed (check individual service status)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__ServiceProvisionResponse"}}}},"404":{"description":"Tenant not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/services/restart":{"post":{"tags":["cbtenant","Deployment"],"summary":"Restart Tenant Services","description":"Safely restart tenant services with port cleanup.\n\nThis is the recommended way to restart services to avoid port conflicts.","operationId":"restart_tenant_services_api_tenants__tenant_id__services_restart_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/services/start":{"post":{"tags":["cbtenant","Deployment"],"summary":"Start Tenant Services","description":"Start tenant services with port availability check.","operationId":"start_tenant_services_api_tenants__tenant_id__services_start_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/services/status":{"get":{"tags":["cbtenant","Deployment"],"summary":"Get Service Status","description":"Get current status of tenant services.","operationId":"get_service_status_api_tenants__tenant_id__services_status_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/services/stop":{"post":{"tags":["cbtenant","Deployment"],"summary":"Stop Tenant Services","description":"Stop tenant services and clean up ports.\n\nArgs:\n    force_kill: If True, also kill any orphan processes on the ports","operationId":"stop_tenant_services_api_tenants__tenant_id__services_stop_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"force_kill","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Force Kill"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/services/validate":{"post":{"tags":["cbtenant","Deployment"],"summary":"Validate external services","description":"Validate that external services are accessible for a tenant.\n\nChecks:\n- **CBFiles**: Bucket exists and is accessible\n- **cbsurveys**: API key is valid\n- **Anthropic**: API key format is correct\n\nUse this after provisioning or configuration changes to verify\nservices are properly configured.","operationId":"validate_services_api_tenants__tenant_id__services_validate_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Validation completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__ServiceValidationResponse"}}}},"404":{"description":"Tenant not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/snapshot-db":{"post":{"tags":["cbtenant","Deployment"],"summary":"Snapshot production databases","description":"Copy production cbradio and cbpulse databases into this radio tenant instance.","operationId":"snapshot_db_api_tenants__tenant_id__snapshot_db_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/ssl/obtain":{"post":{"tags":["cbtenant","Deployment"],"summary":"Obtain Ssl Certificate","description":"Obtain SSL certificate for tenant domains using certbot.\n\nThis requires DNS records to be propagated first.","operationId":"obtain_ssl_certificate_api_tenants__tenant_id__ssl_obtain_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/status":{"get":{"tags":["cbtenant","Deployment"],"summary":"Get Deployment Status","description":"Get deployment status for a tenant.\n\nChecks DNS, NGINX, SSL, and service status.","operationId":"get_deployment_status_api_tenants__tenant_id__status_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/upgrade":{"post":{"tags":["cbtenant","Upgrade"],"summary":"Upgrade Tenant","description":"Upgrade tenant to specified version (or latest).\n\nSteps:\n1. Stop services\n2. Backup database (optional)\n3. Git fetch + checkout target version\n4. Install requirements\n5. Run migrations (optional)\n6. Update version tracking\n7. Start services\n8. Health check","operationId":"upgrade_tenant_api_tenants__tenant_id__upgrade_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__UpgradeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__UpgradeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/api/tenants/{tenant_id}/version":{"get":{"tags":["cbtenant","Upgrade"],"summary":"Get Tenant Version","description":"Check tenant version status.\n\nReturns current deployed version and latest available version.","operationId":"get_tenant_version_api_tenants__tenant_id__version_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"X-User-Email","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Email"}},{"name":"X-User-Permissions","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-User-Permissions"}},{"name":"X-CB-Edge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cb-Edge-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__VersionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HTTPValidationError"}}}}}}},"/cbtenant/health":{"get":{"tags":["cbtenant","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HealthResponse"}}}}}}},"/cbtenant/health/detailed":{"get":{"tags":["cbtenant","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__HealthResponse"}}}}}}},"/cbtenant/prime":{"get":{"tags":["cbtenant","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__PrimeResponse"}}}}}}},"/cbtenant/status":{"get":{"tags":["cbtenant","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtenant__StatusResponse"}}}}}}},"/cbtiles/":{"get":{"tags":["cbtiles","Health"],"summary":"API information","description":"## API Information\n\nReturns what this host serves, and — because callers reliably guess wrong\n— where the *current* basemap actually lives.\n\n### Quick Start\n\n1. **Draw a map**: point MapLibre at `basemap.style_dark`. Not this API.\n2. **Check the basemap is fresh**: `GET /status` → `basemap.healthy`\n3. **Archived raster tile**: `GET /dark_all/10/512/384.png` (hits only)\n4. **Full docs**: `/docs`","operationId":"root__get","responses":{"200":{"description":"API information and quick links","content":{"application/json":{"schema":{},"example":{"name":"CBTiles","version":"0.3.0","status":"online","docs":"/docs"}}}}}}},"/cbtiles/health":{"get":{"tags":["cbtiles","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtiles__HealthResponse"}}}}}}},"/cbtiles/health/detailed":{"get":{"tags":["cbtiles","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtiles__HealthResponse"}}}}}}},"/cbtiles/prime":{"get":{"tags":["cbtiles","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtiles__PrimeResponse"}}}}}}},"/cbtiles/stats":{"get":{"tags":["cbtiles","Health"],"summary":"Raster archive statistics","description":"## Raster Archive Statistics\n\nHit/miss counters since process start, plus on-disk usage per style.\n\n### Response Fields\n\n| Field | Description |\n|-------|-------------|\n| `uptime_seconds` | Seconds since process start — counters reset with it |\n| `performance.hits` | Requests served from the archive |\n| `performance.misses` | Requests for tiles the archive never held |\n| `performance.hit_rate` | Fraction (0.0 to 1.0), not a percentage |\n| `downloads` | **Always zero.** Frozen when the fetch path was deleted; the keys stay so existing consumers keep their shape |\n| `storage` | Tile count and MB, total and per style |\n\n### Interpretation\n\nA low `hit_rate` here does **not** mean \"warm the cache\" — there is no\nwarming path any more. It means callers are asking for coverage this\narchive never had, and they should be pointed at the pmtiles basemap\n(see `/status`) instead.\n\n### Cost\n\n`storage` is computed by walking the archive on every call. The walk runs\non a worker thread so it does not stall tile requests, but it is not free\n— poll this on the order of minutes, not seconds.","operationId":"get_stats_stats_get","responses":{"200":{"description":"Archive hit/miss counters and on-disk usage","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Stats Stats Get"},"example":{"timestamp":"2026-08-29T12:00:00.000000","uptime_seconds":6310,"performance":{"hits":14200,"misses":1220,"hit_rate":0.9209,"total_requests":15420},"downloads":{"successful":0,"failed":0},"storage":{"total_tiles":39741,"total_size_mb":231.28,"cache_directory":"/mnt/ursa/spondr/tiles","by_style":{"dark_all":{"tiles":32578,"size_mb":136.03},"light_all":{"tiles":670,"size_mb":5.08}}}}}}}}}},"/cbtiles/status":{"get":{"tags":["cbtiles","Health"],"summary":"Server configuration and basemap freshness","description":"## Server Configuration and Basemap Freshness\n\nReports what this service is configured to serve, and — importantly —\nwhether the *other* half of cbtiles is still current.\n\n### The two halves\n\n1. **This service** serves a frozen read-only archive of the pre-cutoff\n   CartoDB raster cache. `supported_styles` lists what it can hold; a tile\n   that was never cached returns 404 forever, because the upstream was\n   retired on 2026-08-27 (CARTO watermarks keyless fetches).\n2. **The live basemap** is a self-hosted pmtiles vector archive under\n   `basemap.directory`, served by nginx byte ranges with no process in\n   the request path. This service does not serve it — it only reads its\n   metadata, because otherwise nothing in the fleet would notice it\n   going stale.\n\n### Reading `basemap`\n\n| Field | Meaning |\n|-------|---------|\n| `archive` | The build the stable symlink currently resolves to |\n| `size_mb` | A healthy region extract is ~20000MB; far less means truncated |\n| `age_days` | Days since that build was written |\n| `healthy` | `false` if missing, dangling, truncated, or past the age limit |\n| `detail` | Why, in words — quote this when escalating |\n\n**`healthy: false` is actionable**, not cosmetic: it means the monthly\nrefresh (cbcron job `c9eb6304`) is not landing, and every map in the\nfleet is drawing progressively older geography. Runbook:\n`deploy/pmtiles/README.md`.","operationId":"get_status_status_get","responses":{"200":{"description":"Server configuration, archive styles, basemap state","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Status Status Get"},"example":{"cache_directory":"/mnt/ursa/spondr/tiles","server_port":32216,"supported_styles":["dark_all","light_all","voyager"],"upstream":"retired 2026-08-27 — misses 404, nothing fetches","basemap":{"directory":"/var/lib/pmtiles","archive":"us-territories-20260826.pmtiles","size_mb":19465.4,"built":"2026-08-26T21:37:00+00:00","age_days":3,"healthy":true,"detail":"built 3d ago"},"timestamp":"2026-08-29T12:00:00.000000"}}}}}}},"/cbtiles/{style}/{z}/{x}/{y}.png":{"get":{"tags":["cbtiles","tiles"],"summary":"Fetch map tile","description":"## Fetch an Archived Raster Tile\n\nReturns a tile from the frozen pre-cutoff CartoDB raster archive.\n**Nothing is fetched.** There is no upstream, no prefetch, and no\ncache warming — a tile the archive does not already hold is a 404,\npermanently.\n\nFor new work use the pmtiles vector basemap instead; it is served by\nnginx from this same host and is the only half of cbtiles that is\nstill being refreshed. See the API description or `GET /`.\n\n### Parameters\n\n| Parameter | Description | Valid Values |\n|-----------|-------------|--------------|\n| `style` | CartoDB basemap style | dark_all, light_all, voyager, etc. |\n| `z` | Zoom level | 0-20 |\n| `x` | Tile X coordinate | 0 to 2^z - 1 |\n| `y` | Tile Y coordinate | 0 to 2^z - 1 |\n\n### What This Endpoint Does\n\n1. **Validates request** - Checks style and coordinate bounds\n2. **Checks cache** - Returns immediately if tile is cached\n3. **That is all** - misses 404. The upstream was retired 2026-08-27\n   (CARTO watermarks keyless fetches); the live basemap is the\n   self-hosted pmtiles stack on this host.\n\n### Use Cases\n\n- **Legacy raster clients**: an existing Leaflet `L.tileLayer` that\n  predates the pmtiles cutover and has not been migrated yet\n\nPre-fetching for offline use and generating fresh map images are **no\nlonger supported here** — the archive only holds what was browsed\nbefore August 2026. Use pmtiles.\n\n### Example URLs\n\n```\n/dark_all/10/512/384.png    # Zoom 10, dark basemap\n/light_all/14/4096/6144.png # Zoom 14, light basemap\n/voyager/8/128/96.png       # Zoom 8, voyager style\n```\n\n### Caching Behavior\n\n- **Cache-Control**: `public, max-age=3600` (1 hour browser cache)\n- **Disk archive**: read-only, frozen at the 2026-08 CARTO cutoff.\n  Coverage is partial and heavily skewed to `dark_all`; `GET /stats`\n  reports the real per-style counts.","operationId":"get_tile__style___z___x___y__png_get","parameters":[{"name":"style","in":"path","required":true,"schema":{"type":"string","title":"Style"}},{"name":"z","in":"path","required":true,"schema":{"type":"integer","title":"Z"}},{"name":"x","in":"path","required":true,"schema":{"type":"integer","title":"X"}},{"name":"y","in":"path","required":true,"schema":{"type":"integer","title":"Y"}}],"responses":{"200":{"description":"PNG tile image","content":{"application/json":{"schema":{}},"image/png":{}},"headers":{"Cache-Control":{"description":"Browser caching directive","schema":{"type":"string","example":"public, max-age=3600"}},"Content-Length":{"description":"Size of tile in bytes","schema":{"type":"integer","example":12847}}}},"400":{"description":"Invalid parameters","content":{"application/json":{"examples":{"invalid_style":{"summary":"Unknown tile style","value":{"detail":"Unknown tile style: foo. Valid styles: ['dark_all', 'light_all', ...]"}},"invalid_coords":{"summary":"Coordinates out of range","value":{"detail":"Invalid tile coordinates for zoom level"}},"invalid_zoom":{"summary":"Zoom level out of range","value":{"detail":"Zoom level must be between 0 and 20"}}}}}},"404":{"description":"Tile not in the archive — permanent, not transient","content":{"application/json":{"example":{"detail":"Tile not in the archive. The raster upstream is retired — use the pmtiles basemap (see /)."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtiles__HTTPValidationError"}}}}}}},"/cbtor/":{"get":{"tags":["cbtor","fetch"],"summary":"API information","description":"## API Information\n\nReturns basic API information and a directory of available endpoints.\n\n### Quick Start\n\n1. **Check health**: `GET /api/v1/health`\n2. **Fetch anonymously**: `POST /api/v1/fetch` with `{\"url\": \"https://...\"}`\n3. **View full docs**: `/docs` (Swagger UI) or `/redoc` (ReDoc)\n\n### Endpoint Summary\n\n| Endpoint | Method | Purpose |\n|----------|--------|---------|\n| `/api/v1/fetch` | POST | Fetch URL via Tor |\n| `/api/v1/health` | GET | Worker pool status |\n| `/api/v1/tor/workers` | GET | List all workers |\n| `/api/v1/tor/status` | GET | Cluster summary |\n| `/docs` | GET | Swagger UI |\n| `/redoc` | GET | ReDoc documentation |","operationId":"root__get","responses":{"200":{"description":"API information and available endpoints","content":{"application/json":{"schema":{},"example":{"name":"TOR Management API","version":"1.3.0","description":"Anonymous web fetching via Tor worker pool","endpoints":{"fetch":"/api/v1/fetch","health":"/api/v1/health","docs":"/docs"}}}}}}}},"/cbtor/api/v1/endpoints":{"get":{"tags":["cbtor","fetch","fetch"],"summary":"Get Tor check endpoint statistics","description":"## Tor Check Endpoint Statistics\n\nReturns statistics for the endpoints used to verify Tor connectivity\nduring health checks. The health manager cycles through these endpoints,\nprioritizing those with higher success rates.\n\n### What This Endpoint Does\n\n1. **Lists all check endpoints** - URLs used to verify Tor is working\n2. **Shows success/failure counts** - How reliable each endpoint is\n3. **Tracks consecutive failures** - Endpoints with 3+ failures are deprioritized\n\n### Check Endpoints\n\n| Endpoint | Check Method | Response |\n|----------|--------------|----------|\n| `check.torproject.org/api/ip` | JSON field `IsTor=true` | Authoritative Tor check |\n| `ipinfo.io/json` | JSON has `ip` field | General IP API |\n| `icanhazip.com` | Plain text IP | Simple IP echo |\n| `api.ipify.org` | Plain text IP | Simple IP echo |\n\n### Use Cases\n\n- **Debugging**: \"Why are health checks failing?\"\n- **Monitoring**: \"Are check endpoints themselves healthy?\"\n- **Tuning**: \"Which endpoints are most reliable?\"\n\n### Response Interpretation\n\n- **success_rate > 95%**: Endpoint is reliable\n- **consecutive_failures > 0**: Endpoint having temporary issues\n- **consecutive_failures >= 3**: Endpoint deprioritized in rotation","operationId":"get_endpoint_stats_api_v1_endpoints_get","responses":{"200":{"description":"Endpoint statistics","content":{"application/json":{"schema":{},"example":{"endpoints":{"https://check.torproject.org/api/ip":{"successes":150,"failures":3,"success_rate":98.0,"consecutive_failures":0}},"description":"TOR connectivity check endpoints with success/failure tracking"}}}}}}},"/cbtor/api/v1/fetch":{"post":{"tags":["cbtor","fetch","fetch"],"summary":"Fetch URL anonymously via Tor","description":"## Fetch URL Anonymously via Tor\n\nRoute any URL through the Tor network using the worker pool. Requests are\nload-balanced across healthy workers, each providing a different exit IP.\n\n### What This Endpoint Does\n\n1. **Selects worker** - Picks a healthy worker using the load balancing mode\n2. **Routes request** - Sends through Tor (TransPort for clearnet, SOCKS5 for .onion)\n3. **Returns response** - Includes body, headers, timing, and worker used\n\n### Use Cases\n\n- **Anonymous scraping**: \"Fetch this page without revealing my IP\"\n- **Tor verification**: \"Confirm requests are routed through Tor\"\n- **.onion access**: \"Fetch content from a hidden service\"\n- **IP rotation**: \"Get different exit IPs across requests\"\n\n### Routing Logic\n\n| URL Type | Method | Port | Typical Latency |\n|----------|--------|------|-----------------|\n| http/https | TransPort via WireGuard | 9040 | 1-5 seconds |\n| .onion | SOCKS5 proxy | 9050 | 10-30 seconds |\n\n### Load Balancing Modes\n\n| Mode | Behavior | Best For |\n|------|----------|----------|\n| `round_robin` | Rotate through workers | Even distribution (default) |\n| `sticky` | Same worker for same key | Session consistency |\n| `random` | Random selection | Simple distribution |\n| `least_connections` | Fewest active | High throughput |\n\n### Response Interpretation\n\n- **success=true**: Request completed, check status_code for HTTP result\n- **success=false**: Connection failed, see error field for details\n- **timing_ms**: Includes Tor circuit establishment (can be 5-30s first request)\n- **worker**: Which worker handled this (useful for debugging)\n\n### Example Usage\n\n```python\n# Basic fetch\nresponse = requests.post(\n    \"{BASE_URL}/api/v1/fetch\",\n    json={\"url\": \"https://check.torproject.org/api/ip\"},\n)\ndata = response.json()\nprint(f\"Exit IP: {data['body']}\")  # {\"IsTor\":true,\"IP\":\"185.x.x.x\"}\n\n# Sticky session (same exit IP)\nresponse = requests.post(\n    \"{BASE_URL}/api/v1/fetch\",\n    json={\"url\": \"https://example.com/login\", \"mode\": \"sticky\", \"sticky_key\": \"my-session-123\"},\n)\n```","operationId":"fetch_url_api_v1_fetch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__FetchRequest"}}},"required":true},"responses":{"200":{"description":"Fetch completed (check success field for result)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__FetchResponse"},"example":{"success":true,"worker":"worker-01","is_onion":false,"status_code":200,"body":"{\"IsTor\":true,\"IP\":\"185.220.101.20\"}","timing_ms":1234.5}}}},"400":{"description":"Invalid load balancing mode specified","content":{"application/json":{"example":{"detail":"Invalid mode: invalid. Valid modes: ['round_robin', 'sticky', 'random', 'least_connections']"}}}},"503":{"description":"No healthy workers available","content":{"application/json":{"example":{"detail":"No healthy workers available"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__HTTPValidationError"}}}}}}},"/cbtor/api/v1/health":{"get":{"tags":["cbtor","fetch","fetch"],"summary":"Get worker pool health status","description":"## Worker Pool Health Status\n\nReturns the current health state of all Tor workers in the pool.\n\n### What This Endpoint Does\n\n1. **Queries pool** - Gets current state of all 4 workers\n2. **Aggregates stats** - Counts healthy workers, calculates rates\n3. **Returns summary** - Per-worker details with state and metrics\n\n### Use Cases\n\n- **Pre-flight check**: \"Is the pool ready to receive requests?\"\n- **Monitoring**: \"How many workers are healthy right now?\"\n- **Debugging**: \"Why are my requests failing?\"\n\n### Worker States\n\n| State | Meaning | Receives Traffic |\n|-------|---------|------------------|\n| `healthy` | All checks passing | ✅ Yes |\n| `suspect` | Recent failures, being watched | ⚠️ Yes |\n| `dead` | Failed, entering recovery | ❌ No |\n| `recovery` | Recovery sequence in progress | ❌ No |\n\n### Response Interpretation\n\n- **healthy_workers < total_workers**: Some workers unavailable\n- **consecutive_failures > 0**: Worker having issues\n- **success_rate < 0.9**: Worker degraded performance\n- **connections > 10**: Worker under heavy load","operationId":"get_health_api_v1_health_get","responses":{"200":{"description":"Current health status of all workers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__api__routers__fetch__HealthResponse"},"example":{"healthy_workers":4,"total_workers":4,"mode":"round_robin","workers":{"worker-01":{"state":"healthy","ip":"17.0.0.134","connections":0,"success_rate":0.98}}}}}}}}},"/cbtor/api/v1/mode":{"post":{"tags":["cbtor","fetch","fetch"],"summary":"Set load balancing mode","description":"## Set Default Load Balancing Mode\n\nChange the default load balancing algorithm for the worker pool.\nIndividual fetch requests can still override with the `mode` parameter.\n\n### Available Modes\n\n| Mode | Algorithm | Use Case |\n|------|-----------|----------|\n| `round_robin` | Rotate through workers | Even distribution (default) |\n| `sticky` | Hash-based consistent routing | Session affinity |\n| `random` | Random selection | Simple distribution |\n| `least_connections` | Pick least busy | High throughput |\n| `failover` | Always use first healthy | Primary/backup pattern |\n\n### Example\n\n```bash\ncurl -X POST \"{BASE_URL}/api/v1/mode?mode=least_connections\"\n```","operationId":"set_mode_api_v1_mode_post","parameters":[{"name":"mode","in":"query","required":true,"schema":{"type":"string","title":"Mode"}}],"responses":{"200":{"description":"Mode set successfully","content":{"application/json":{"schema":{},"example":{"status":"ok","mode":"round_robin"}}}},"400":{"description":"Invalid mode","content":{"application/json":{"example":{"detail":"Invalid mode: invalid. Valid modes: ['round_robin', 'sticky', 'random', 'least_connections']"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__HTTPValidationError"}}}}}}},"/cbtor/api/v1/tor/status":{"get":{"tags":["cbtor","tor"],"summary":"Get Tor cluster status","description":"## Get Tor Cluster Status\n\nReturns a summary of the entire Tor worker cluster, including\naggregate counts and per-worker status.\n\n### What This Endpoint Does\n\n1. **Checks all 4 workers** - SSH to each worker in parallel\n2. **Aggregates status** - Counts running/stopped\n3. **Returns summary** - Cluster overview with worker details\n\n### Use Cases\n\n- **Dashboard**: \"Quick cluster health overview\"\n- **Monitoring**: \"How many workers are running?\"\n- **Alerting**: \"Notify if running < total\"\n\n### Response Fields\n\n| Field | Description |\n|-------|-------------|\n| `total_workers` | Total workers in cluster (always 4) |\n| `running` | Workers with Tor service running |\n| `stopped` | Workers with Tor service stopped |\n| `nodes_online` | Workers reporting \"online\" in node-side health check |\n| `workers` | Per-worker status details |\n| `workers[].node_health` | Node-side health from `/var/run/tor/health-status` |\n\n### Note\n\nThis is a direct Tor service check via SSH, separate from the\nload balancer health states. A worker could have Tor running but\nstill be in DEAD state if health checks are failing.","operationId":"get_tor_cluster_status_api_v1_tor_status_get","responses":{"200":{"description":"Cluster-wide status summary","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Tor Cluster Status Api V1 Tor Status Get"},"example":{"total_workers":4,"running":4,"stopped":0,"nodes_online":4,"workers":[{"id":1,"ip":"17.0.0.134","tor_running":true,"node_health":{"status":"online"}},{"id":2,"ip":"17.0.0.202","tor_running":true,"node_health":{"status":"online"}},{"id":3,"ip":"17.0.0.201","tor_running":true,"node_health":{"status":"online"}},{"id":4,"ip":"17.0.0.120","tor_running":true,"node_health":{"status":"online"}}]}}}}}}},"/cbtor/api/v1/tor/workers":{"get":{"tags":["cbtor","tor"],"summary":"List all Tor workers","description":"## List All Tor Workers\n\nReturns cached worker pool state — the background HealthManager updates\nthis every ~10s, so this endpoint responds instantly (no live SSH probes).\n\nFor deep per-worker inspection (live Tor status, privoxy check, exit IP),\ncall `GET /workers/{worker_number}` instead.","operationId":"list_tor_workers_api_v1_tor_workers_get","responses":{"200":{"description":"List of all workers with status","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Tor Workers Api V1 Tor Workers Get"},"example":[{"worker":1,"ip":"17.0.0.134","hostname":"taxon-worker-01","tor_running":true,"socks_port":9050,"trans_port":9040}]}}}}}},"/cbtor/api/v1/tor/workers/{worker_number}":{"get":{"tags":["cbtor","tor"],"summary":"Get specific worker status","description":"## Get Specific Worker Status\n\nReturns detailed status for a single Tor worker. Includes Tor service\nstate, port configuration, and system information.\n\n### Path Parameters\n\n- **worker_number**: Integer 1-4 corresponding to worker-01 through worker-04\n\n### What This Endpoint Does\n\n1. **Validates worker number** - Must be 1, 2, 3, or 4\n2. **Connects via SSH** - Checks Tor daemon status\n3. **Returns details** - Complete worker configuration\n\n### Use Cases\n\n- **Deep dive**: \"What's the exact state of worker-02?\"\n- **Troubleshooting**: \"Is Tor actually running on this worker?\"\n- **Configuration**: \"What ports is this worker using?\"","operationId":"get_tor_worker_api_v1_tor_workers__worker_number__get","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"Detailed worker status","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Tor Worker Api V1 Tor Workers  Worker Number  Get"},"example":{"worker":1,"ip":"17.0.0.134","hostname":"taxon-worker-01","tor_running":true,"tor_status":"running","socks_port":9050,"trans_port":9040,"control_port":9051}}}},"400":{"description":"Invalid worker number","content":{"application/json":{"example":{"detail":"Worker 99 is not a Tor worker. Tor workers: [1, 2, 3, 4]"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__HTTPValidationError"}}}}}}},"/cbtor/api/v1/tor/workers/{worker_number}/install-privoxy":{"post":{"tags":["cbtor","tor"],"summary":"Install Privoxy HTTP proxy","description":"## Install Privoxy HTTP Proxy\n\nInstalls and configures Privoxy on a Tor worker. Privoxy provides an\nHTTP proxy interface (port 3128) that forwards requests to Tor SOCKS5.\n\n### What This Endpoint Does\n\n1. **Installs Privoxy** - `opkg install privoxy`\n2. **Configures forwarding** - Sets SOCKS5 parent proxy to 127.0.0.1:9050\n3. **Starts service** - Enables and starts Privoxy daemon\n4. **Returns status** - Installation result and port number\n\n### Use Cases\n\n- **HTTP clients**: \"Use Tor without SOCKS support\"\n- **Legacy apps**: \"Old software that only speaks HTTP proxy\"\n- **Debugging**: \"Test Tor routing with curl -x http://...\"\n\n### Usage After Installation\n\n```bash\n# Direct HTTP proxy through worker\ncurl -x http://17.0.0.134:3128 https://check.torproject.org/api/ip\n\n# Or use Privoxy through WireGuard tunnel\ncurl -x http://10.201.1.2:3128 https://example.com\n```\n\n### Port Information\n\n| Port | Service | Purpose |\n|------|---------|---------|\n| 9050 | Tor SOCKS5 | Native Tor proxy |\n| 9040 | Tor TransPort | Transparent routing |\n| 3128 | Privoxy | HTTP → SOCKS bridge |","operationId":"install_privoxy_api_v1_tor_workers__worker_number__install_privoxy_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"Privoxy installed successfully","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Install Privoxy Api V1 Tor Workers  Worker Number  Install Privoxy Post"},"example":{"status":"installed","worker":1,"proxy_port":3128}}}},"400":{"description":"Invalid worker number","content":{"application/json":{"example":{"detail":"Invalid worker number"}}}},"500":{"description":"Installation failed","content":{"application/json":{"example":{"detail":"opkg install failed"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__HTTPValidationError"}}}}}}},"/cbtor/api/v1/tor/workers/{worker_number}/tor/restart":{"post":{"tags":["cbtor","tor"],"summary":"Restart Tor service","description":"## Restart Tor Service on Worker\n\nRestarts the Tor daemon on the specified worker via SSH.\nThis forces new circuit establishment.\n\n### What This Endpoint Does\n\n1. **Connects via SSH** - `ssh root@{worker_ip}`\n2. **Restarts Tor** - `/etc/init.d/tor restart`\n3. **Returns result** - Success or error message\n\n### Use Cases\n\n- **Circuit refresh**: \"Force new Tor circuits immediately\"\n- **Configuration reload**: \"Apply torrc changes\"\n- **Recovery**: \"Tor is stuck, restart it\"\n\n### Timing\n\n- Restart command returns immediately\n- Tor takes ~30 seconds to bootstrap\n- First request after restart may be slow (circuit establishment)\n\n### Alternative: NEWNYM Signal\n\nFor lighter-weight circuit rotation without full restart, the node-side\nhealth script sends NEWNYM signals via the Tor control port. This\nrotates circuits without restarting the daemon.","operationId":"restart_tor_api_v1_tor_workers__worker_number__tor_restart_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"Tor restarted successfully","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Restart Tor Api V1 Tor Workers  Worker Number  Tor Restart Post"},"example":{"status":"restarted","worker":1}}}},"500":{"description":"Failed to restart Tor","content":{"application/json":{"example":{"detail":"SSH connection failed"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__HTTPValidationError"}}}}}}},"/cbtor/api/v1/tor/workers/{worker_number}/tor/start":{"post":{"tags":["cbtor","tor"],"summary":"Start Tor service","description":"## Start Tor Service on Worker\n\nStarts the Tor daemon on the specified worker via SSH.\n\n### What This Endpoint Does\n\n1. **Connects via SSH** - `ssh root@{worker_ip}`\n2. **Starts Tor** - `/etc/init.d/tor start`\n3. **Returns result** - Success or error message\n\n### Use Cases\n\n- **Recovery**: \"Worker was offline, bring Tor back up\"\n- **Maintenance**: \"Start Tor after configuration changes\"\n- **Testing**: \"Manually control Tor service\"\n\n### Notes\n\n- Tor takes ~30 seconds to bootstrap and establish circuits\n- Health manager will detect and verify once running\n- If worker was DEAD, it will transition to RECOVERY","operationId":"start_tor_api_v1_tor_workers__worker_number__tor_start_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"Tor started successfully","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Start Tor Api V1 Tor Workers  Worker Number  Tor Start Post"},"example":{"status":"started","worker":1}}}},"500":{"description":"Failed to start Tor","content":{"application/json":{"example":{"detail":"SSH connection failed"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__HTTPValidationError"}}}}}}},"/cbtor/api/v1/tor/workers/{worker_number}/tor/stop":{"post":{"tags":["cbtor","tor"],"summary":"Stop Tor service","description":"## Stop Tor Service on Worker\n\nStops the Tor daemon on the specified worker via SSH.\n\n### What This Endpoint Does\n\n1. **Connects via SSH** - `ssh root@{worker_ip}`\n2. **Stops Tor** - `/etc/init.d/tor stop`\n3. **Returns result** - Success or error message\n\n### Use Cases\n\n- **Maintenance**: \"Take worker offline for updates\"\n- **Troubleshooting**: \"Stop and restart Tor to clear issues\"\n- **Testing**: \"Verify health manager detects stopped Tor\"\n\n### ⚠️ Warning\n\nStopping Tor will cause:\n- Worker to fail health checks\n- Transition to SUSPECT → DEAD state\n- Health manager will attempt to restart it\n\nUse `/workers/drain` instead if you want controlled removal from pool.","operationId":"stop_tor_api_v1_tor_workers__worker_number__tor_stop_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"Tor stopped successfully","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Stop Tor Api V1 Tor Workers  Worker Number  Tor Stop Post"},"example":{"status":"stopped","worker":1}}}},"500":{"description":"Failed to stop Tor","content":{"application/json":{"example":{"detail":"SSH connection failed"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__HTTPValidationError"}}}}}}},"/cbtor/api/v1/workers/drain":{"post":{"tags":["cbtor","fetch","fetch"],"summary":"Drain worker from pool","description":"## Drain Worker from Pool\n\nRemove a worker from the active pool. The worker will stop receiving new\nrequests and enter the recovery state.\n\n### What This Endpoint Does\n\n1. **Marks worker DEAD** - Removes from load balancer rotation\n2. **Initiates recovery** - Starts the recovery sequence immediately\n3. **No new traffic** - Worker won't receive requests until healthy\n\n### Use Cases\n\n- **Maintenance**: \"Take this worker offline for updates\"\n- **Troubleshooting**: \"Isolate a misbehaving worker\"\n- **Testing**: \"Force recovery sequence to run\"\n\n### Recovery Behavior\n\nAfter draining, the health manager will:\n1. Check SSH connectivity\n2. Verify SOCKS port (restart Tor if needed)\n3. Reconnect WireGuard tunnel\n4. Verify Tor circuit\n\nIf recovery succeeds, worker returns to HEALTHY. If not, exponential\nbackoff retries (1s, 2s, 4s, 8s, 16s, 32s), then reboot after 6 attempts.","operationId":"drain_worker_api_v1_workers_drain_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__WorkerAction"}}},"required":true},"responses":{"200":{"description":"Worker drained successfully","content":{"application/json":{"schema":{},"example":{"status":"drained","worker":"worker-01"}}}},"404":{"description":"Worker not found","content":{"application/json":{"example":{"detail":"Worker not found: worker-99"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__HTTPValidationError"}}}}}}},"/cbtor/api/v1/workers/enable":{"post":{"tags":["cbtor","fetch","fetch"],"summary":"Force-enable worker","description":"## Force-Enable Worker\n\nImmediately mark a worker as healthy, bypassing all health checks.\n\n### What This Endpoint Does\n\n1. **Sets state to HEALTHY** - Worker can receive traffic immediately\n2. **Resets failure counters** - Clears consecutive failures and backoff\n3. **Cancels recovery** - Stops any in-progress recovery sequence\n\n### Use Cases\n\n- **Override**: \"I know this worker is fine, bring it back\"\n- **Testing**: \"Force a worker into the pool for testing\"\n- **Recovery stuck**: \"Recovery is looping, manually restore\"\n\n### ⚠️ Caution\n\nThis bypasses health checks. If the worker is actually unhealthy:\n- Requests will fail\n- Health manager will detect failures\n- Worker will transition back to SUSPECT → DEAD\n\nOnly use when you've verified the worker is actually working.","operationId":"enable_worker_api_v1_workers_enable_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__WorkerAction"}}},"required":true},"responses":{"200":{"description":"Worker enabled successfully","content":{"application/json":{"schema":{},"example":{"status":"enabled","worker":"worker-01"}}}},"404":{"description":"Worker not found","content":{"application/json":{"example":{"detail":"Worker not found: worker-99"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__HTTPValidationError"}}}}}}},"/cbtor/health":{"get":{"tags":["cbtor","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__sysforge__core__health__HealthResponse"}}}}}}},"/cbtor/health/detailed":{"get":{"tags":["cbtor","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__sysforge__core__health__HealthResponse"}}}}}}},"/cbtor/prime":{"get":{"tags":["cbtor","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__PrimeResponse"}}}}}}},"/cbtor/status":{"get":{"tags":["cbtor","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbtor__StatusResponse"}}}}}}},"/cbunifi/api/v1/config":{"get":{"tags":["cbunifi","CBUNIFI"],"summary":"UniFi wiring status","description":"Non-secret view of whether the tunnel + key are configured.","operationId":"config_api_v1_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbunifi/api/v1/controller/health":{"get":{"tags":["cbunifi","CBUNIFI"],"summary":"Controller reachability over the tunnel","description":"Whether the UniFi console answers over the WG tunnel right now.","operationId":"controller_health_api_v1_controller_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbunifi/api/v1/protect/cameras":{"get":{"tags":["cbunifi","CBUNIFI"],"summary":"List UniFi Protect cameras","operationId":"cameras_api_v1_protect_cameras_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbunifi/api/v1/sites":{"get":{"tags":["cbunifi","CBUNIFI"],"summary":"List UniFi sites","operationId":"sites_api_v1_sites_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbunifi/api/v1/sites/{site_id}/clients":{"get":{"tags":["cbunifi","CBUNIFI"],"summary":"List clients on a site","operationId":"clients_api_v1_sites__site_id__clients_get","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__HTTPValidationError"}}}}}}},"/cbunifi/api/v1/sites/{site_id}/devices":{"get":{"tags":["cbunifi","CBUNIFI"],"summary":"List devices on a site","operationId":"devices_api_v1_sites__site_id__devices_get","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__HTTPValidationError"}}}}}}},"/cbunifi/api/v1/sites/{site_id}/guests/{mac}":{"get":{"tags":["cbunifi","CBUNIFI"],"summary":"Pending-guest lookup by MAC","description":"Is this MAC a client at the property right now? cbcaptive's never-\nauthorize-blind check + returning-guest probe. Read path — read key.","operationId":"guest_lookup_api_v1_sites__site_id__guests__mac__get","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"mac","in":"path","required":true,"schema":{"type":"string","title":"Mac"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__HTTPValidationError"}}}}}}},"/cbunifi/api/v1/sites/{site_id}/guests/{mac}/authorize":{"post":{"tags":["cbunifi","CBUNIFI"],"summary":"Authorize a guest MAC (cbcaptive portal)","description":"Response carries ``method`` (integration|legacy) — live traffic answers\nthe spec's task-0 question about which primitive actually works.","operationId":"guest_authorize_api_v1_sites__site_id__guests__mac__authorize_post","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"mac","in":"path","required":true,"schema":{"type":"string","title":"Mac"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__GuestAuthorize"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__HTTPValidationError"}}}}}}},"/cbunifi/api/v1/sites/{site_id}/guests/{mac}/unauthorize":{"post":{"tags":["cbunifi","CBUNIFI"],"summary":"Unauthorize a guest MAC (abuse/admin path)","operationId":"guest_unauthorize_api_v1_sites__site_id__guests__mac__unauthorize_post","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"mac","in":"path","required":true,"schema":{"type":"string","title":"Mac"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__HTTPValidationError"}}}}}}},"/cbunifi/api/v1/sites/{site_id}/vouchers":{"get":{"tags":["cbunifi","CBUNIFI"],"summary":"List hotspot vouchers","description":"Read path — rides the read key; codes included (desk staff re-read them).","operationId":"vouchers_list_api_v1_sites__site_id__vouchers_get","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__HTTPValidationError"}}}}}},"post":{"tags":["cbunifi","CBUNIFI"],"summary":"Generate hotspot vouchers","operationId":"vouchers_create_api_v1_sites__site_id__vouchers_post","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__VoucherCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__HTTPValidationError"}}}}}}},"/cbunifi/api/v1/sites/{site_id}/vouchers/{voucher_id}":{"delete":{"tags":["cbunifi","CBUNIFI"],"summary":"Revoke a voucher","operationId":"vouchers_delete_api_v1_sites__site_id__vouchers__voucher_id__delete","parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"voucher_id","in":"path","required":true,"schema":{"type":"string","title":"Voucher Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__HTTPValidationError"}}}}}}},"/cbunifi/health":{"get":{"tags":["cbunifi","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__HealthResponse"}}}}}}},"/cbunifi/health/detailed":{"get":{"tags":["cbunifi","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__HealthResponse"}}}}}}},"/cbunifi/prime":{"get":{"tags":["cbunifi","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__PrimeResponse"}}}}}}},"/cbunifi/status":{"get":{"tags":["cbunifi","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbunifi__StatusResponse"}}}}}}},"/cbuserguide/api/apps":{"get":{"tags":["cbuserguide","Apps"],"summary":"List all registered apps","description":"Retrieve all Campaign Brain applications registered for documentation.\n\nReturns an array of App objects, each containing the app's configuration\nincluding base URL, API URL, and registration timestamp.","operationId":"list_apps_api_apps_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbuserguide__App"},"type":"array","title":"Response List Apps Api Apps Get"}}}}}},"post":{"tags":["cbuserguide","Apps"],"summary":"Register a new app","description":"Register a Campaign Brain application for automated documentation.\n\n**Required fields:**\n- `name`: Unique identifier for the app (e.g., \"cbapp\", \"testsite\")\n- `base_url`: Root URL of the web application\n\n**Optional fields:**\n- `api_url`: Separate API endpoint if different from `{base_url}/api`\n- `username`: Login username for crawling (stored securely)\n- `password`: Login password for crawling (stored securely)\n\n**Example:**\n```json\n{\n  \"name\": \"testsite\",\n  \"base_url\": \"https://testsite.campaignbrain.dev\",\n  \"username\": \"admin\",\n  \"password\": \"admin123\"\n}\n```","operationId":"create_app_api_apps_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__AppCreate"}}},"required":true},"responses":{"201":{"description":"App registered successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__App"}}}},"400":{"description":"App with this name already exists","content":{"application/json":{"example":{"detail":"App 'testsite' already exists"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/apps/{app_id}":{"get":{"tags":["cbuserguide","Apps"],"summary":"Get app by ID","description":"Retrieve details for a specific registered application.","operationId":"get_app_api_apps__app_id__get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"integer","title":"App Id"}}],"responses":{"200":{"description":"App found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__App"}}}},"404":{"description":"App not found","content":{"application/json":{"example":{"detail":"App not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/apps/{app_id}/versions":{"get":{"tags":["cbuserguide","Versions"],"summary":"List app versions","description":"Retrieve all documentation versions for a specific app.\n\nVersions track documentation releases. Each version can contain multiple\nmodules (documentation sections). Only one version per app can be active.","operationId":"list_versions_api_apps__app_id__versions_get","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"integer","title":"App Id"}}],"responses":{"200":{"description":"List of versions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbuserguide__Version"},"title":"Response List Versions Api Apps  App Id  Versions Get"}}}},"404":{"description":"App not found","content":{"application/json":{"example":{"detail":"App not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}},"post":{"tags":["cbuserguide","Versions"],"summary":"Create a new version","description":"Create a new documentation version for an app.\n\nUse semver format for version strings (e.g., \"1.0.0\", \"2.1.0\").\nNew versions are created inactive by default.\n\n**Example:**\n```json\n{\"version\": \"1.2.0\"}\n```","operationId":"create_version_api_apps__app_id__versions_post","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"integer","title":"App Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__VersionCreate"}}}},"responses":{"201":{"description":"Version created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__Version"}}}},"404":{"description":"App not found","content":{"application/json":{"example":{"detail":"App not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/apps/{app_id}/versions/{version_id}/activate":{"put":{"tags":["cbuserguide","Versions"],"summary":"Activate a version","description":"Set a specific version as the active (published) version.\n\nOnly one version per app can be active at a time. Activating a version\ndeactivates any previously active version.\n\nUse this to publish new documentation or roll back to a previous version.","operationId":"activate_version_api_apps__app_id__versions__version_id__activate_put","parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"integer","title":"App Id"}},{"name":"version_id","in":"path","required":true,"schema":{"type":"integer","title":"Version Id"}}],"responses":{"200":{"description":"Version activated","content":{"application/json":{"schema":{},"example":{"status":"ok","message":"Version 1.2.0 is now active"}}}},"404":{"description":"Version not found or doesn't belong to app","content":{"application/json":{"example":{"detail":"Version not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/directory":{"get":{"tags":["cbuserguide","UI"],"summary":"Services Directory payload","description":"The curated, business-facing Services Directory: service cards in plain\nlanguage plus named flows — ordered chains of services with the domain noun\nthat crosses each hop. Source of the \"Services Directory\" guide in the loom\nDocs app. Curated in `data/directory/`; drift-guarded against cbcli's\nregistry by the test suite.","operationId":"services_directory_api_directory_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbuserguide/api/modules/{module_id}":{"get":{"tags":["cbuserguide","Modules"],"summary":"Get module details","description":"Retrieve a specific module by ID, including its current state and configuration.\n\nUse this to check generation progress or get paths to crawl.","operationId":"get_module_api_modules__module_id__get","parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"integer","title":"Module Id"}}],"responses":{"200":{"description":"Module found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__Module"}}}},"404":{"description":"Module not found","content":{"application/json":{"example":{"detail":"Module not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}},"patch":{"tags":["cbuserguide","Modules"],"summary":"Update module configuration","description":"Update a module's name or paths.\n\nNote: Updating paths on a module that has already been generated will not\nautomatically regenerate artifacts. Use `/api/modules/{module_id}/reset`\nfollowed by `/api/modules/{module_id}/generate` to capture the new paths.","operationId":"update_module_api_modules__module_id__patch","parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"integer","title":"Module Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__ModuleUpdate"}}}},"responses":{"200":{"description":"Module updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__Module"}}}},"404":{"description":"Module not found","content":{"application/json":{"example":{"detail":"Module not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}},"delete":{"tags":["cbuserguide","Modules"],"summary":"Delete a module","description":"Permanently delete a module and all its artifacts (screenshots, markdown).\n\nThis action cannot be undone.","operationId":"delete_module_api_modules__module_id__delete","parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"integer","title":"Module Id"}}],"responses":{"200":{"description":"Module deleted","content":{"application/json":{"schema":{},"example":{"status":"ok","message":"Module deleted"}}}},"404":{"description":"Module not found","content":{"application/json":{"example":{"detail":"Module not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/modules/{module_id}/artifacts":{"get":{"tags":["cbuserguide","Artifacts"],"summary":"List all module artifacts","description":"Retrieve all artifacts (screenshots and markdown) for a module.\n\nUse this to get a complete list of everything generated for a module,\nregardless of type.","operationId":"list_all_artifacts_api_modules__module_id__artifacts_get","parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"integer","title":"Module Id"}}],"responses":{"200":{"description":"List of all artifacts","content":{"application/json":{"schema":{}}}},"404":{"description":"Module not found","content":{"application/json":{"example":{"detail":"Module not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/modules/{module_id}/generate":{"post":{"tags":["cbuserguide","Modules"],"summary":"Start generation (INIT → GENERATING)","description":"Start the documentation generation process for a module.\n\n**State transition:** INIT → GENERATING\n\nThis endpoint:\n1. Clears any existing artifacts\n2. Transitions the module to GENERATING state\n3. Starts a background task that:\n   - Launches Playwright browser\n   - Logs into the target app\n   - Navigates to each path in the module\n   - Captures full-page screenshots\n   - Stores artifacts in the database\n\nWhen complete, the module automatically transitions to VALIDATING.\n\n**Requirements:**\n- Module must be in INIT state\n- Parent app must have valid credentials","operationId":"generate_module_api_modules__module_id__generate_post","parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"integer","title":"Module Id"}}],"responses":{"200":{"description":"Generation started, module now in GENERATING state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__Module"}}}},"400":{"description":"Invalid state transition","content":{"application/json":{"example":{"detail":"Cannot generate from state 'VALIDATING'"}}}},"404":{"description":"Module not found","content":{"application/json":{"example":{"detail":"Module not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/modules/{module_id}/markdown":{"get":{"tags":["cbuserguide","Artifacts"],"summary":"List module markdown files","description":"Retrieve all markdown documentation artifacts for a module.\n\nMarkdown files contain the generated documentation text describing\nUI elements and workflows captured during crawling.","operationId":"list_markdown_api_modules__module_id__markdown_get","parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"integer","title":"Module Id"}}],"responses":{"200":{"description":"List of markdown artifacts","content":{"application/json":{"schema":{}}}},"404":{"description":"Module not found","content":{"application/json":{"example":{"detail":"Module not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/modules/{module_id}/publish":{"post":{"tags":["cbuserguide","Modules"],"summary":"Publish module (VALIDATING → PUBLISHED)","description":"Mark a module as published after reviewing generated artifacts.\n\n**State transition:** VALIDATING → PUBLISHED\n\nUse this after reviewing screenshots and markdown to confirm they're\nready for end users. Published modules are included in the user guide.","operationId":"publish_module_api_modules__module_id__publish_post","parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"integer","title":"Module Id"}}],"responses":{"200":{"description":"Module published successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__Module"}}}},"400":{"description":"Invalid state transition","content":{"application/json":{"example":{"detail":"Cannot publish from state 'INIT'"}}}},"404":{"description":"Module not found","content":{"application/json":{"example":{"detail":"Module not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/modules/{module_id}/regenerate":{"post":{"tags":["cbuserguide","Modules"],"summary":"Regenerate module (VALIDATING → GENERATING)","description":"Discard current artifacts and regenerate from scratch.\n\n**State transition:** VALIDATING → GENERATING\n\nUse this when:\n- Screenshots are outdated after UI changes\n- Captured pages don't look right\n- Need to recapture after fixing credentials\n\nThis clears all existing artifacts and starts fresh generation.","operationId":"regenerate_module_api_modules__module_id__regenerate_post","parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"integer","title":"Module Id"}}],"responses":{"200":{"description":"Regeneration started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__Module"}}}},"400":{"description":"Invalid state transition","content":{"application/json":{"example":{"detail":"Cannot regenerate from state 'INIT'"}}}},"404":{"description":"Module not found","content":{"application/json":{"example":{"detail":"Module not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/modules/{module_id}/reset":{"post":{"tags":["cbuserguide","Modules"],"summary":"Reset module (ANY → INIT)","description":"Reset a module to its initial state, clearing all artifacts.\n\n**State transition:** ANY → INIT\n\nUse this to:\n- Start fresh after errors\n- Clear artifacts before deleting\n- Return a published module to draft state\n\nThis is the only transition that works from any state.","operationId":"reset_module_api_modules__module_id__reset_post","parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"integer","title":"Module Id"}}],"responses":{"200":{"description":"Module reset to INIT state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__Module"}}}},"404":{"description":"Module not found","content":{"application/json":{"example":{"detail":"Module not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/modules/{module_id}/screenshots":{"get":{"tags":["cbuserguide","Artifacts"],"summary":"List module screenshots","description":"Retrieve all screenshot artifacts for a module.\n\nReturns a list of screenshot metadata including filenames and paths.\nUse these paths with `/static/screenshots/{filename}` to display images.\n\nScreenshots are captured during the GENERATING phase using Playwright.","operationId":"list_module_screenshots_api_modules__module_id__screenshots_get","parameters":[{"name":"module_id","in":"path","required":true,"schema":{"type":"integer","title":"Module Id"}}],"responses":{"200":{"description":"List of screenshot artifacts","content":{"application/json":{"schema":{}}}},"404":{"description":"Module not found","content":{"application/json":{"example":{"detail":"Module not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/api/platform/catalog":{"get":{"tags":["cbuserguide","UI"],"summary":"Raw platform catalog","description":"Return the latest platform-catalog.json entries as JSON.","operationId":"platform_catalog_api_platform_catalog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbuserguide/api/versions/{version_id}/modules":{"get":{"tags":["cbuserguide","Modules"],"summary":"List modules for a version","description":"Retrieve all documentation modules (sections) for a specific version.\n\nEach module represents a logical section of documentation (e.g., \"Admin Users\",\n\"Settings\"). Modules have a state indicating their progress through the\ngeneration workflow.","operationId":"list_modules_api_versions__version_id__modules_get","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"integer","title":"Version Id"}}],"responses":{"200":{"description":"List of modules with their current states","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbuserguide__Module"},"title":"Response List Modules Api Versions  Version Id  Modules Get"}}}},"404":{"description":"Version not found","content":{"application/json":{"example":{"detail":"Version not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}},"post":{"tags":["cbuserguide","Modules"],"summary":"Create a new module","description":"Create a documentation module (section) for a version.\n\n**Required fields:**\n- `name`: Human-readable name (e.g., \"Admin Users\", \"Settings\")\n- `paths`: List of URL paths to crawl (e.g., [\"/users\", \"/users/new\"])\n\nModules are created in INIT state, ready for generation.\n\n**Example:**\n```json\n{\n  \"name\": \"admin-users\",\n  \"paths\": [\"/users\", \"/users/new\", \"/users/{id}/edit\"]\n}\n```","operationId":"create_module_api_versions__version_id__modules_post","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"integer","title":"Version Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__ModuleCreate"}}}},"responses":{"201":{"description":"Module created in INIT state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__Module"}}}},"404":{"description":"Version not found","content":{"application/json":{"example":{"detail":"Version not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HTTPValidationError"}}}}}}},"/cbuserguide/health":{"get":{"tags":["cbuserguide","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HealthResponse"}}}}}}},"/cbuserguide/health/detailed":{"get":{"tags":["cbuserguide","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__HealthResponse"}}}}}}},"/cbuserguide/platform":{"get":{"tags":["cbuserguide","UI"],"summary":"Hierarchical platform browse","description":"Browse the Campaign Brain platform catalog hierarchically (platform → tier →\ncategory → service/app cards) with an embedded interactive dependency graph.\n\nCards link out to each service's CLAUDE.md on GitHub, its OpenAPI docs, and —\nfor cbloom flash-apps — the live app URL. Client-side search and filter by name,\ncategory, tier and type. Data is sourced from\n`cbmaintain/inventory/latest/platform-catalog.json`.","operationId":"platform_browse_platform_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/cbuserguide/prime":{"get":{"tags":["cbuserguide","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__PrimeResponse"}}}}}}},"/cbuserguide/screenshots":{"get":{"tags":["cbuserguide","Screenshots"],"summary":"Browse all screenshots","description":"Returns an HTML page displaying all captured screenshots as a thumbnail gallery.","operationId":"list_screenshots_screenshots_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/cbuserguide/status":{"get":{"tags":["cbuserguide","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbuserguide__StatusResponse"}}}}}}},"/cbvoice/api/v1/hello":{"get":{"tags":["cbvoice","CBVOICE"],"summary":"Liveness greeting","description":"Return a fixed greeting payload. The lightest possible liveness probe for the cbvoice service itself: it makes **no** upstream mesh calls and requires **no** authentication, so a 200 here confirms only that the FastAPI app is up and routing. For a real connectivity check across the mesh hop, use `GET /api/v1/voice/ping` instead.\n\n**Response:** `{\"message\": \"Hello from cbvoice\"}`","operationId":"hello_api_v1_hello_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbvoice/api/v1/voice/ping":{"get":{"tags":["cbvoice","CBVOICE"],"summary":"Connectivity smoke test (spoken)","description":"End-to-end smoke test for the voice transport. Exercises the full `cbvoice → mesh` fan-out by fetching live voter-file stats from **cbmodels** (`/api/v1/voters/stats`) and, when a bearer is supplied, resolving the caller's identity from **cbauth** (`/api/v1/users/me`). Folds both into a single ready-to-speak `spoken` sentence.\n\n### Authentication\nAuth-optional and fail-soft. Pass `Authorization: Bearer <token>` to be greeted by email; omit it (or send an invalid token) to be greeted as *\"stranger\"* — neither case fails the request.\n\n### Response shape\n- `spoken` — a sentence the Siri Shortcut reads aloud verbatim, e.g. *\"Campaign Brain voice is ready, hello stranger. The MI voter file has 7,500,000 voters.\"*\n- `raw` — the structured `{stats, email}` behind the sentence, for debugging or richer UI rendering.","operationId":"voice_ping_api_v1_voice_ping_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Voice Ping Api V1 Voice Ping Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvoice__HTTPValidationError"}}}}}}},"/cbvoice/health":{"get":{"tags":["cbvoice","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvoice__HealthResponse"}}}}}}},"/cbvoice/health/detailed":{"get":{"tags":["cbvoice","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvoice__HealthResponse"}}}}}}},"/cbvoice/prime":{"get":{"tags":["cbvoice","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvoice__PrimeResponse"}}}}}}},"/cbvoice/status":{"get":{"tags":["cbvoice","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvoice__StatusResponse"}}}}}}},"/cbvpn/":{"get":{"tags":["cbvpn","Health"],"summary":"Service info","description":"API root with service discovery information.\n\nReturns service metadata and available endpoints for client discovery.\nUse this to programmatically discover API capabilities and resources.","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbvpn/api/v1/banks/":{"get":{"tags":["cbvpn","banks"],"summary":"List all VPN banks","description":"List all active VPN banks with worker assignments and health status.\n\n## What This Returns\n\nReturns all configured VPN banks, each containing:\n- Worker assignments and their VPN status\n- Geographic filter configuration, and whether each worker's deployed\n  profile actually satisfies it (`geo_match`)\n- `worker_endpoints` — the per-worker proxies verified to be carrying\n  traffic. **Route through these.**\n- `endpoint` — the aggregate HAProxy URL, TCP-probed at read time and\n  **null when nothing is listening on it**\n- Health metrics (workers up/down, success rate, workers_wrong_geo)\n\n## Use Cases\n\n- **Capacity Check**: See available VPN regions before submitting geo-routed jobs\n- **Health Monitoring**: Identify degraded regions needing attention\n- **Resource Planning**: View worker allocation across geographic regions\n\n## Response Fields\n\n| Field | Description |\n|-------|-------------|\n| `tag` | Unique bank identifier (use in other endpoints) |\n| `workers` | List of assigned worker numbers |\n| `filter` | Geographic filter (e.g., \"us:ca\") |\n| `status` | \"active\", \"degraded\", \"idle\", or \"error\" |\n| `endpoint` | Aggregate HAProxy URL, or **null** if nothing is listening |\n| `endpoint_status` | \"listening\", \"unreachable\", or \"unverified\" |\n| `endpoint_detail` | Why the endpoint is unusable, when it is |\n| `worker_endpoints` | Per-worker proxy URLs that are verified to work |\n| `health` | Worker counts, success rate, and `workers_wrong_geo` |\n\n## For LLMs\n\nFilter by `health.success_rate < 0.8` to identify degraded banks.\nCheck `status == \"error\"` for banks needing immediate attention.","operationId":"list_banks_api_v1_banks__get","responses":{"200":{"description":"List of all active banks with worker assignments","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbvpn__Bank"},"type":"array","title":"Response List Banks Api V1 Banks  Get"}}}}}},"post":{"tags":["cbvpn","banks"],"summary":"Create VPN bank","description":"Create a new VPN bank with workers assigned to a geographic region.\n\n## What This Does\n\n1. Validates the geographic filter against available VPN profiles\n2. Checks requested workers are not already allocated\n3. Assigns VPN profiles matching the filter to each worker\n4. Starts VPN tunnels on all assigned workers\n5. Attempts to configure an HAProxy backend on a unique port, then\n   **verifies the port is actually listening**\n6. Returns the new bank with connection details — `status` is \"degraded\"\n   rather than \"active\" if the endpoint did not materialise, with the\n   reason in `endpoint_detail`. The workers are still deployed and usable\n   via `worker_endpoints`.\n\n## Request Body\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `name` | string | Human-readable bank name (e.g., \"California Research\") |\n| `workers` | list[int] | Worker numbers 1-16 to assign |\n| `filter` | string | Geographic filter (e.g., \"us:ca\", \"de\", \"us:ny\") |\n\n## Geographic Filters\n\n| Format | Example | Description |\n|--------|---------|-------------|\n| `{country}` | `de` | Any exit in country |\n| `{country}:{state}` | `us:ca` | US state-specific exit |\n| `{country}::{type}` | `us::p2p` | Any exit in country carrying a feature |\n| `{country}:{state}:{type}` | `us:mi:p2p` | Both |\n\n`type` is a **Proton server feature** — `p2p`, `tor`, `streaming`,\n`secure_core`, `ipv6` — resolved against the harvested inventory, not\nmatched against profile filenames. Note the empty state slot in `us::p2p`:\n`type` is always the third field. An unknown type is rejected rather than\nsilently matching nothing.\n\n## Use Cases\n\n- **Regional Research**: Create dedicated capacity for a specific geography\n- **A/B Testing**: Compare results from different geographic perspectives\n- **Compliance**: Route traffic through required jurisdictions\n\n## Example\n\n```json\nPOST /api/v1/banks\n{\n    \"name\": \"California Research\",\n    \"workers\": [1, 2, 3, 4],\n    \"filter\": \"us:ca\"\n}\n```\n\n## Response\n\nReturns the created Bank object with:\n- `tag`: Normalized identifier for future API calls\n- `worker_endpoints`: proxy URLs to route through\n- `endpoint`: aggregate HAProxy URL, or null if the port is not served\n- `worker_assignments`: VPN profiles assigned to each worker","operationId":"create_bank_api_v1_banks__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__BankCreate"}}},"required":true},"responses":{"201":{"description":"Bank created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__Bank"}}}},"400":{"description":"Invalid bank configuration","content":{"application/json":{"examples":{"invalid_filter":{"value":{"detail":"Invalid geo filter format: 'xyz'"}},"workers_in_use":{"value":{"detail":"Workers [1, 2] already allocated to bank 'other'"}}}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/banks/reconcile-endpoints":{"post":{"tags":["cbvpn","banks"],"summary":"Regenerate the master's bank frontends from bank state","description":"Make the master's HAProxy match current bank state.\n\n## What This Does\n\nRenders a `listen` block per non-idle bank — balancing TCP across that\nbank's workers' forward proxies — validates it on the master with\n`haproxy -c`, installs it, reloads, and then **probes each port** to report\nwhat actually answers.\n\nOnly the region between cbvpn's markers in `/etc/haproxy.cfg` is rewritten;\nthe master's own frontends are preserved. A no-op reconcile does not reload\nHAProxy.\n\n## When To Use It\n\nBank writes already reconcile, so this is for the cases where nothing wrote\na bank:\n\n- After a master reboot or a hand-edit dropped the frontends\n- After upgrading to a version that generates them at all — every bank\n  created before #59 recorded a port that nothing ever served\n- As a health action when `GET /banks/` reports `endpoint: null` while the\n  bank looks otherwise healthy\n\n## Response\n\n| Field | Description |\n|-------|-------------|\n| `banks` | Per bank: tag, port, workers, and whether the port answers |\n| `ports_configured` | Ports the generated config binds |\n| `ports_listening` | Ports that actually accepted a connection |\n| `detail` | Present only if a configured port does not answer |\n\nIdle banks are intentionally absent: idle means the VPN is stopped, so an\nendpoint that accepts and then fails would be worse than none.\n\nReturns 502 if the config could not be installed — in which case the\nprevious config has been restored on the master.","operationId":"reconcile_endpoints_api_v1_banks_reconcile_endpoints_post","responses":{"200":{"description":"Reconciled; reports which ports answer","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Reconcile Endpoints Api V1 Banks Reconcile Endpoints Post"}}}},"502":{"description":"Config could not be installed on the master"}}}},"/cbvpn/api/v1/banks/{bank_tag}":{"get":{"tags":["cbvpn","banks"],"summary":"Get bank by tag","description":"Get specific VPN bank details by its unique tag.\n\n## What This Returns\n\nComplete bank configuration including:\n- All assigned workers with their current VPN profiles\n- VPN connection status for each worker\n- Exit IPs currently in use\n- Health metrics\n\n## Use Cases\n\n- **Pre-flight Check**: Verify bank health before routing traffic\n- **Troubleshooting**: Inspect worker-level details when jobs fail\n- **Endpoint Discovery**: Get routable proxy URLs (`worker_endpoints`)\n\n## Path Parameters\n\n| Parameter | Description |\n|-----------|-------------|\n| `bank_tag` | Normalized bank name (lowercase, no spaces) |\n\n## Response Fields\n\nSee `GET /api/v1/banks` for field descriptions. Additionally includes:\n- `worker_assignments`: Detailed per-worker VPN status and exit IPs","operationId":"get_bank_api_v1_banks__bank_tag__get","parameters":[{"name":"bank_tag","in":"path","required":true,"schema":{"type":"string","title":"Bank Tag"}}],"responses":{"200":{"description":"Bank details with worker assignments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__Bank"}}}},"404":{"description":"Bank not found","content":{"application/json":{"example":{"detail":"Bank 'california' not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}},"put":{"tags":["cbvpn","banks"],"summary":"Update bank configuration","description":"Update an existing VPN bank's configuration.\n\n## What Can Be Updated\n\n| Field | Description | Requires Idle? |\n|-------|-------------|----------------|\n| `workers` | Add/remove workers | Yes |\n| `filter` | Change geographic filter | Yes |\n| `status` | Set to \"active\" or \"idle\" | No |\n\n## Use Cases\n\n- **Scale Up**: Add more workers to a busy region\n- **Scale Down**: Remove workers for reallocation\n- **Pause**: Set status to \"idle\" for maintenance\n\n## Example\n\n```json\nPUT /api/v1/banks/california\n{\n    \"workers\": [1, 2, 3, 4, 5],\n    \"status\": \"active\"\n}\n```","operationId":"update_bank_api_v1_banks__bank_tag__put","parameters":[{"name":"bank_tag","in":"path","required":true,"schema":{"type":"string","title":"Bank Tag"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__BankUpdate"}}}},"responses":{"200":{"description":"Bank updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__Bank"}}}},"400":{"description":"Invalid update parameters","content":{"application/json":{"example":{"detail":"Cannot change filter while bank is active"}}}},"404":{"description":"Bank not found","content":{"application/json":{"example":{"detail":"Bank 'california' not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}},"delete":{"tags":["cbvpn","banks"],"summary":"Delete bank and release workers","description":"Delete a VPN bank and release all assigned workers.\n\n## What This Does\n\n1. Stops VPN tunnels on all assigned workers\n2. Removes HAProxy backend configuration\n3. Marks workers as available for reallocation\n4. Deletes the bank from the registry\n\n## Use Cases\n\n- **Cleanup**: Remove banks no longer needed\n- **Reallocation**: Free workers for different geographic region\n- **Troubleshooting**: Delete and recreate a problematic bank\n\n## Warning\n\nThis immediately stops all VPN tunnels. Any in-flight requests through\nthis bank's endpoint will fail. Ensure no active jobs are using this bank.","operationId":"delete_bank_api_v1_banks__bank_tag__delete","parameters":[{"name":"bank_tag","in":"path","required":true,"schema":{"type":"string","title":"Bank Tag"}}],"responses":{"204":{"description":"Bank deleted, workers released"},"404":{"description":"Bank not found","content":{"application/json":{"example":{"detail":"Bank 'california' not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/banks/{bank_tag}/cycle":{"post":{"tags":["cbvpn","banks"],"summary":"Cycle bank to fresh random profiles (same filter)","description":"Rotate every worker in a bank to a fresh random profile from the bank's filter.\n\nPicks new profiles via the same `country/state/type` filter the bank was\ncreated with, redeploys to each worker, restarts OpenVPN, and (when\n`validate=true`) confirms each worker has a usable exit IP. Workers whose\nexit IP comes back empty get retried with another random profile up to\n`max_attempts` times before being marked failed in the response.\n\n## Query Parameters\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| `validate` | true | Re-check exit IP after deploy; retry on empty |\n| `max_attempts` | 3 | Max profile attempts per worker before giving up |\n\n## When To Use\n\n- A bank's worker(s) report `vpn_status=up` but proxy traffic doesn't pass\n  (provider-side endpoint stale/blocked).\n- Periodic rotation for fresh exit geography within the same country.","operationId":"cycle_bank_api_v1_banks__bank_tag__cycle_post","parameters":[{"name":"bank_tag","in":"path","required":true,"schema":{"type":"string","title":"Bank Tag"}},{"name":"validate","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Validate"}},{"name":"max_attempts","in":"query","required":false,"schema":{"type":"integer","default":3,"title":"Max Attempts"}}],"responses":{"200":{"description":"Bank workers rotated to new profiles","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cycle Bank Api V1 Banks  Bank Tag  Cycle Post"},"example":{"bank_tag":"canadaprimary","filter":"country=ca","status":"active","workers":[{"ok":true,"profile":"ca-mtl-117.protonvpn.udp.ovpn","exit_ip":"185.156.46.144","country":"CA","city":"Montreal"}]}}}},"404":{"description":"Bank not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/banks/{bank_tag}/idle":{"post":{"tags":["cbvpn","banks"],"summary":"Idle bank (pause VPN)","description":"Pause a VPN bank by stopping tunnels while preserving worker assignments.\n\n## What This Does\n\n1. Stops VPN tunnels on all bank workers\n2. Optionally stops HAProxy backends (if `stop_proxy=true`)\n3. Keeps workers assigned to the bank\n4. Sets bank status to \"idle\"\n\n## Query Parameters\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `stop_proxy` | bool | false | Also stop HAProxy backends |\n\n## Use Cases\n\n- **Maintenance**: Pause traffic without losing configuration\n- **Cost Reduction**: Stop tunnels during low-usage periods\n- **Troubleshooting**: Isolate bank while investigating issues\n\n## Difference from Delete\n\n- **Idle**: Keeps worker assignments, can resume quickly\n- **Delete**: Releases workers, requires full recreation","operationId":"idle_bank_api_v1_banks__bank_tag__idle_post","parameters":[{"name":"bank_tag","in":"path","required":true,"schema":{"type":"string","title":"Bank Tag"}},{"name":"stop_proxy","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Stop Proxy"}}],"responses":{"200":{"description":"Bank idled successfully","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Idle Bank Api V1 Banks  Bank Tag  Idle Post"},"example":{"status":"idle","workers_stopped":4}}}},"404":{"description":"Bank not found","content":{"application/json":{"example":{"detail":"Bank 'california' not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/banks/{bank_tag}/resume":{"post":{"tags":["cbvpn","banks"],"summary":"Resume bank (restart VPN)","description":"Resume an idled VPN bank by restarting tunnels.\n\n## What This Does\n\n1. Restarts VPN tunnels on all bank workers\n2. Optionally starts HAProxy backends (if `start_proxy=true`)\n3. Verifies tunnel connectivity\n4. Sets bank status to \"active\"\n\n## Query Parameters\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `start_proxy` | bool | true | Also start HAProxy backends |\n\n## Use Cases\n\n- **Post-Maintenance**: Bring bank back online after work\n- **Scheduled Activation**: Resume during high-usage periods\n- **Recovery**: Restart after fixing configuration issues\n\n## Response\n\nReturns status and count of workers successfully started.\nCheck `health` endpoint if some workers fail to resume.","operationId":"resume_bank_api_v1_banks__bank_tag__resume_post","parameters":[{"name":"bank_tag","in":"path","required":true,"schema":{"type":"string","title":"Bank Tag"}},{"name":"start_proxy","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Start Proxy"}}],"responses":{"200":{"description":"Bank resumed successfully","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Resume Bank Api V1 Banks  Bank Tag  Resume Post"},"example":{"status":"active","workers_started":4}}}},"404":{"description":"Bank not found","content":{"application/json":{"example":{"detail":"Bank 'california' not found"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/cluster/":{"get":{"tags":["cbvpn","cluster"],"summary":"Get cluster state","description":"Get complete cluster state including all workers and banks.\n\n## What This Returns\n\nComprehensive cluster overview:\n- Master device status and connectivity\n- All 15 workers with VPN/proxy status\n- All active banks with worker assignments\n- Aggregate capacity metrics\n\n## Use Cases\n\n- **Dashboard**: Overview of entire VPN infrastructure\n- **Capacity Planning**: See total vs available workers\n- **Health Check**: Quick assessment of cluster state\n\n## Response Fields\n\n| Field | Description |\n|-------|-------------|\n| `master` | Master device info (IP, status) |\n| `workers` | List of all worker states |\n| `banks` | List of all active banks |\n| `total_workers` | Total worker count (15) |\n| `workers_available` | Workers not assigned to banks |\n| `workers_allocated` | Workers assigned to banks |\n| `active_banks` | Number of banks with status=\"active\" |\n\n## For LLMs\n\n- Use `workers_available` to determine if new banks can be created\n- If `workers_available == 0`, need to delete a bank before creating new one\n- Check `active_banks` count vs expected for health verification","operationId":"get_cluster_state_api_v1_cluster__get","responses":{"200":{"description":"Complete cluster state with workers and banks","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__ClusterState"}}}}}}},"/cbvpn/api/v1/cluster/health":{"get":{"tags":["cbvpn","cluster"],"summary":"Cluster health check","description":"Get cluster health metrics for monitoring and alerting.\n\n## What This Returns\n\nHealth summary for the entire VPN cluster:\n- Master device connectivity\n- Worker health counts\n- Bank health counts\n- Overall status assessment\n\n## Use Cases\n\n- **Monitoring**: Periodic health checks for alerting\n- **Pre-Operation Check**: Verify cluster before changes\n- **Troubleshooting**: Identify unhealthy components\n\n## Health Status Values\n\n| Status | Meaning |\n|--------|---------|\n| `healthy` | All components operational |\n| `degraded` | Some workers/banks unhealthy but functional |\n| `unhealthy` | Master unreachable or majority of workers down |\n\n## For LLMs\n\n- `status == \"healthy\"` - safe to proceed with operations\n- `status == \"degraded\"` - operations may be slower or less reliable\n- `status == \"unhealthy\"` - do not submit geo-routed jobs","operationId":"cluster_health_api_v1_cluster_health_get","responses":{"200":{"description":"Cluster health metrics","content":{"application/json":{"schema":{},"example":{"status":"healthy","master_reachable":true,"workers_total":15,"workers_healthy":14,"workers_unhealthy":1,"banks_total":3,"banks_healthy":3}}}}}}},"/cbvpn/api/v1/cluster/idle":{"post":{"tags":["cbvpn","cluster"],"summary":"Idle cluster workers","description":"Idle workers by stopping VPN tunnels across the cluster.\n\n## What This Does\n\n1. Stops VPN tunnels on specified workers (or all if not specified)\n2. Optionally stops proxy services\n3. Banks remain configured but inactive\n\n## Request Body\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `workers` | list[int] | all | Specific workers to idle (1-15) |\n| `stop_proxy` | bool | false | Also stop proxy services |\n\n## Use Cases\n\n- **Maintenance Window**: Pause all VPN for infrastructure work\n- **Cost Reduction**: Stop tunnels during off-hours\n- **Emergency**: Quickly stop all VPN traffic\n\n## Example\n\n```json\nPOST /api/v1/cluster/idle\n{\n    \"workers\": [1, 2, 3, 4, 5],\n    \"stop_proxy\": true\n}\n```\n\n## Warning\n\nAll traffic through affected workers will stop immediately.\nEnsure no active jobs are running before idling.","operationId":"idle_cluster_api_v1_cluster_idle_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__ClusterIdleRequest"}}},"required":true},"responses":{"200":{"description":"Workers idled successfully","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Idle Cluster Api V1 Cluster Idle Post"},"example":{"status":"idle","workers_stopped":15,"vpn_stopped":15,"proxy_stopped":0}}}},"400":{"description":"Invalid request","content":{"application/json":{"example":{"detail":"Invalid worker list"}}}},"500":{"description":"Cluster operation failed","content":{"application/json":{"example":{"detail":"Failed to stop workers: connection timeout"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/cluster/reset":{"post":{"tags":["cbvpn","cluster"],"summary":"Reset cluster to clean state","description":"Reset cluster to clean idle state with all resources released.\n\n## What This Does\n\n1. Releases all banks (frees worker assignments)\n2. Stops VPN tunnels on all workers\n3. Stops proxy services on all workers\n4. Clears all cached state\n\n## Use Cases\n\n- **Fresh Start**: Begin with clean configuration\n- **Emergency Recovery**: Clear all state after issues\n- **End of Day**: Complete shutdown of VPN infrastructure\n\n## Warning\n\nThis is a destructive operation:\n- All bank configurations will be lost\n- All active traffic will be terminated\n- Workers return to unallocated state\n\n## Response\n\nReturns counts of released/stopped components.\nCluster state will show all workers as idle and unallocated.","operationId":"reset_cluster_api_v1_cluster_reset_post","responses":{"200":{"description":"Cluster reset successfully","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Reset Cluster Api V1 Cluster Reset Post"},"example":{"status":"reset","banks_released":3,"workers_stopped":15,"proxies_stopped":15}}}},"500":{"description":"Reset operation failed","content":{"application/json":{"example":{"detail":"Failed to release bank: workers busy"}}}}}}},"/cbvpn/api/v1/cluster/resume":{"post":{"tags":["cbvpn","cluster"],"summary":"Resume cluster workers","description":"Resume workers by starting VPN tunnels across the cluster.\n\n## What This Does\n\n1. Starts VPN tunnels on specified workers (or all if not specified)\n2. Optionally starts proxy services\n3. Restores banks to active state\n\n## Request Body\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `workers` | list[int] | all | Specific workers to resume (1-15) |\n| `start_proxy` | bool | true | Also start proxy services |\n\n## Use Cases\n\n- **Post-Maintenance**: Bring cluster back online\n- **Morning Startup**: Resume after overnight idle\n- **Recovery**: Restart after issue resolution\n\n## Example\n\n```json\nPOST /api/v1/cluster/resume\n{\n    \"workers\": [1, 2, 3, 4, 5],\n    \"start_proxy\": true\n}\n```\n\n## Response\n\nReturns counts of successfully started components.\nCheck individual workers if counts don't match expectations.","operationId":"resume_cluster_api_v1_cluster_resume_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__ClusterResumeRequest"}}},"required":true},"responses":{"200":{"description":"Workers resumed successfully","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Resume Cluster Api V1 Cluster Resume Post"},"example":{"status":"active","workers_started":15,"vpn_started":15,"proxy_started":15}}}},"400":{"description":"Invalid request","content":{"application/json":{"example":{"detail":"Invalid worker list"}}}},"500":{"description":"Cluster operation failed","content":{"application/json":{"example":{"detail":"Failed to start workers: VPN config missing"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/cluster/vpn-countries":{"get":{"tags":["cbvpn","cluster"],"summary":"Get Vpn Countries","description":"Get available VPN countries and states.\n\n## Overview\n\nReturns all available VPN exit locations including:\n- 126 countries organized by region\n- 17 US states with dedicated exit nodes\n- Geographic filter format examples\n\n## Geographic Filter Format\n\n| Format | Example | Description |\n|--------|---------|-------------|\n| `{country}` | `de` | Country only |\n| `{country}:{state}` | `us:ca` | US with state |\n\n## Response Structure\n\n```json\n{\n    \"countries\": {\"us\": {\"code\": \"us\", \"name\": \"United States\", \"region\": \"americas\"}, ...},\n    \"us_states\": {\"ca\": {\"code\": \"ca\", \"name\": \"California\"}, ...},\n    \"regions\": {\"europe\": \"Europe\", \"americas\": \"Americas\", ...},\n    \"total_countries\": 126,\n    \"total_us_states\": 17,\n    \"example_filters\": [...]\n}\n```\n\n## Use Cases\n\n- Discover available exit locations for job geo routing\n- Build UI dropdowns for country selection\n- Validate geo filters before job submission","operationId":"get_vpn_countries_api_v1_cluster_vpn_countries_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbvpn/api/v1/daemon/":{"get":{"tags":["cbvpn","daemon"],"summary":"Daemon status summary","description":"Return a summary of the daemon's current state.","operationId":"get_status_api_v1_daemon__get","responses":{"200":{"description":"Current daemon status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__DaemonStatusResponse"}}}},"503":{"description":"Daemon service not available"}}}},"/cbvpn/api/v1/daemon/events":{"get":{"tags":["cbvpn","daemon"],"summary":"Recent lifecycle events","description":"Return recent structured lifecycle events from the JSON-lines log.","operationId":"get_events_api_v1_daemon_events_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Last N lifecycle events","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbvpn__LifecycleEvent"},"title":"Response Get Events Api V1 Daemon Events Get"}}}},"503":{"description":"Daemon service not available"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/daemon/prime":{"post":{"tags":["cbvpn","daemon"],"summary":"Prime workers and start health loop","description":"Prime workers with random VPN geos and start the health loop.\n\n## What This Does\n\n1. Deploys VPN profiles to each target worker\n2. Validates download speed (re-rolls geo on slow connections)\n3. Starts a background health-check loop\n\n## Request Body\n\n- **workers**: List of worker numbers (default: all 16)\n- **geo_filter**: Force a specific geo for all workers (default: random)","operationId":"prime_api_v1_daemon_prime_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbvpn__PrimeRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Daemon started, workers primed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__DaemonStatusResponse"}}}},"503":{"description":"Daemon service not available"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/daemon/reconcile":{"post":{"tags":["cbvpn","daemon"],"summary":"Re-derive daemon worker state from the bank layer","description":"Rebuild the daemon's per-worker view from bank assignments.\n\n## Why This Exists\n\nThe daemon's per-worker record is written at prime and was never\nre-derived, so re-banking a worker left the daemon describing the previous\nallocation indefinitely — worker 1 reported `geo_filter: mu` while sitting\nin a `us` bank, making a correctly-banked worker look like it was exiting\nthe wrong country (cbvpn#47). Until now the only ways to clear it were\n`teardown` + `prime` or a daemon restart, both of which churn the whole\nfleet and re-stage profiles.\n\n## What It Touches\n\n**No tunnels.** It re-derives `geo_filter` from bank ownership and\nre-probes `exit_ip` by reading the workers, then persists. Nothing is\nprimed, restarted or torn down, so it is safe to run while jobs are\nrouting through the fleet.\n\nIt also works with the daemon **stopped** — the case that needed it most,\nbecause with no loop running nothing rewrites the state file at all and\n`GET /daemon/workers` serves a snapshot frozen at the last prime.\n\n## Response\n\n| Field | Description |\n|-------|-------------|\n| `geo_corrections` | Workers whose geo_filter disagreed with their bank |\n| `exit_ip_updates` | Workers whose recorded exit IP was stale |\n| `workers_reconciled` | Records examined |\n\nA worker that cannot be reached has its exit nulled rather than left\nstale — unknown beats wrong.\n\nThe running health loop now does the geo half of this every tick, so this\nendpoint is for the stopped-daemon case and for forcing an immediate\nrefresh.","operationId":"reconcile_api_v1_daemon_reconcile_post","responses":{"200":{"description":"Reconciled; reports what was corrected","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Reconcile Api V1 Daemon Reconcile Post"},"example":{"geo_corrections":[{"worker_number":2,"before":"cz","after":"us:oh"}],"exit_ip_updates":[{"worker_number":2,"before":"185.98.171.93","after":"149.22.88.160"}],"workers_reconciled":16,"daemon_status":"stopped"}}}}}}},"/cbvpn/api/v1/daemon/stop":{"post":{"tags":["cbvpn","daemon"],"summary":"Stop health loop only","description":"Stop the health-check loop without disconnecting workers.","operationId":"stop_loop_api_v1_daemon_stop_post","responses":{"200":{"description":"Health loop stopped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__DaemonStatusResponse"}}}},"503":{"description":"Daemon service not available"}}}},"/cbvpn/api/v1/daemon/teardown":{"post":{"tags":["cbvpn","daemon"],"summary":"Tear down VPN connections","description":"Stop VPN connections and halt the health loop.\n\nConnections are stopped fire-and-forget; the response returns immediately.","operationId":"teardown_api_v1_daemon_teardown_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbvpn__TeardownRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Teardown initiated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__DaemonStatusResponse"}}}},"503":{"description":"Daemon service not available"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/daemon/workers":{"get":{"tags":["cbvpn","daemon"],"summary":"Per-worker lifecycle details","description":"Return lifecycle phase, geo, speed, and failure counts per worker.","operationId":"get_workers_api_v1_daemon_workers_get","responses":{"200":{"description":"List of per-worker daemon state","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbvpn__WorkerDaemonInfo"},"type":"array","title":"Response Get Workers Api V1 Daemon Workers Get"}}}},"503":{"description":"Daemon service not available"}}}},"/cbvpn/api/v1/devices/":{"get":{"tags":["cbvpn","devices"],"summary":"List Devices","description":"List all devices in the registry.\n\nReturns the full device registry with all 16 devices and their current\ncached metadata. Use /refresh to update with live device data.","operationId":"list_devices_api_v1_devices__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbvpn__DeviceRegistry"},"type":"array","title":"Response List Devices Api V1 Devices  Get"}}}}}}},"/cbvpn/api/v1/devices/ping-all":{"post":{"tags":["cbvpn","devices"],"summary":"Ping All Devices","description":"Ping all devices in parallel.\n\nReturns ping results for all 16 devices. Useful for quick health check.","operationId":"ping_all_devices_api_v1_devices_ping_all_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbvpn__PingResult"},"type":"array","title":"Response Ping All Devices Api V1 Devices Ping All Post"}}}}}}},"/cbvpn/api/v1/devices/refresh":{"post":{"tags":["cbvpn","devices"],"summary":"Refresh All Devices","description":"Refresh status for all devices.\n\nQueries all 16 devices in parallel for current status.\nThis may take 10-30 seconds depending on device responsiveness.","operationId":"refresh_all_devices_api_v1_devices_refresh_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbvpn__DeviceRegistry"},"type":"array","title":"Response Refresh All Devices Api V1 Devices Refresh Post"}}}}}}},"/cbvpn/api/v1/devices/{device_id}":{"get":{"tags":["cbvpn","devices"],"summary":"Get Device","description":"Get specific device from registry.\n\nReturns cached device information. Use /{device_id}/refresh for live data.","operationId":"get_device_api_v1_devices__device_id__get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"integer","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__DeviceRegistry"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}},"put":{"tags":["cbvpn","devices"],"summary":"Update Device","description":"Update device metadata in registry.\n\nUpdates stored metadata like hostname, tags, notes, WireGuard config, etc.\nDoes not modify the actual device - use other endpoints for device operations.","operationId":"update_device_api_v1_devices__device_id__put","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"integer","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__DeviceRegistryUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__DeviceRegistry"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/devices/{device_id}/execute":{"post":{"tags":["cbvpn","devices"],"summary":"Execute Command","description":"Execute command on device.\n\nRuns arbitrary shell command on the device via LuCI RPC.\nUse with caution - commands run as root.","operationId":"execute_command_api_v1_devices__device_id__execute_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"integer","title":"Device Id"}},{"name":"command","in":"query","required":true,"schema":{"type":"string","description":"Shell command to execute","title":"Command"},"description":"Shell command to execute"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__CommandResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/devices/{device_id}/external-ip":{"get":{"tags":["cbvpn","devices"],"summary":"Get External Ip","description":"Get current external IP with geolocation.\n\nQueries ipinfo.io from the device to get current exit IP and location info.\nUseful for verifying VPN connections are working correctly.","operationId":"get_external_ip_api_v1_devices__device_id__external_ip_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"integer","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__ExternalIPInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/devices/{device_id}/ping":{"post":{"tags":["cbvpn","devices"],"summary":"Ping Device","description":"Ping device and return latency metrics.\n\nSends ICMP ping packets to the device and returns latency statistics.","operationId":"ping_device_api_v1_devices__device_id__ping_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"integer","title":"Device Id"}},{"name":"count","in":"query","required":false,"schema":{"type":"integer","maximum":10,"minimum":1,"description":"Number of ping packets","default":3,"title":"Count"},"description":"Number of ping packets"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__PingResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/devices/{device_id}/reboot":{"post":{"tags":["cbvpn","devices"],"summary":"Reboot Device","description":"Reboot device.\n\nSchedules device reboot with 1 second delay to allow HTTP response.\nDevice will be unavailable for 30-60 seconds during reboot.","operationId":"reboot_device_api_v1_devices__device_id__reboot_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"integer","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/devices/{device_id}/refresh":{"post":{"tags":["cbvpn","devices"],"summary":"Refresh Device","description":"Refresh device status from live device.\n\nQueries the actual device for current status, system info, and external IP.\nUpdates the registry with the latest information.","operationId":"refresh_device_api_v1_devices__device_id__refresh_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"integer","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__DeviceRegistry"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/devices/{device_id}/speedtest":{"post":{"tags":["cbvpn","devices"],"summary":"Run Speedtest","description":"Run bandwidth test on device.\n\nRuns speedtest-cli if available, otherwise falls back to curl-based test.\nMay take 30-60 seconds to complete.","operationId":"run_speedtest_api_v1_devices__device_id__speedtest_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"integer","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__SpeedTestResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/devices/{device_id}/status":{"get":{"tags":["cbvpn","devices"],"summary":"Get Device Status","description":"Get quick status check from device.\n\nReturns current system info including hostname, uptime, load, memory, and disk.","operationId":"get_device_status_api_v1_devices__device_id__status_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"integer","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__SystemInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/devices/{device_id}/sync-time":{"post":{"tags":["cbvpn","devices"],"summary":"Sync Time","description":"Sync device time via NTP.\n\nRestarts NTP daemon or runs ntpdate to synchronize device clock.","operationId":"sync_time_api_v1_devices__device_id__sync_time_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"integer","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/egress/nodes":{"get":{"tags":["cbvpn","egress"],"summary":"Residential egress nodes","description":"List nodes. `egress_class`, `proxy_url` and `is_selectable` are computed.","operationId":"list_nodes_api_v1_egress_nodes_get","parameters":[{"name":"selectable","in":"query","required":false,"schema":{"type":"boolean","description":"Only nodes safe to send traffic through right now: ACTIVE, with a measured exit address, at a site that is not burned. This is the flag a caller picking an exit should use — a node can be online and still be none of those things.","default":false,"title":"Selectable"},"description":"Only nodes safe to send traffic through right now: ACTIVE, with a measured exit address, at a site that is not burned. This is the flag a caller picking an exit should use — a node can be online and still be none of those things."},{"name":"site","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one location key. Remember a site can hold several nodes.","title":"Site"},"description":"Filter to one location key. Remember a site can hold several nodes."}],"responses":{"200":{"description":"Nodes, newest enrolment last","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbvpn__ResidentialNode"},"title":"Response List Nodes Api V1 Egress Nodes Get"}}}},"503":{"description":"Registry unreadable — failing closed"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/egress/nodes/{node_id}":{"get":{"tags":["cbvpn","egress"],"summary":"One residential node","operationId":"get_node_api_v1_egress_nodes__node_id__get","parameters":[{"name":"node_id","in":"path","required":true,"schema":{"type":"string","title":"Node Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__ResidentialNode"}}}},"404":{"description":"No such node"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/egress/sites":{"get":{"tags":["cbvpn","egress"],"summary":"Per-site burn state","description":"Burn state per site — including sites with no burn recorded yet.\n\nDerived from the nodes rather than returned straight from `sites`, because\na site only gets a stored reputation row once something happens to it. A\ncaller asking \"what are my locations and are any burned\" should not have to\ninfer the clean ones from an absence.","operationId":"list_sites_api_v1_egress_sites_get","responses":{"200":{"description":"Reputation for every site holding a node","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbvpn__SiteReputation"},"type":"array","title":"Response List Sites Api V1 Egress Sites Get"}}}}}}},"/cbvpn/api/v1/proton/countries":{"get":{"tags":["cbvpn","proton"],"summary":"Exit countries with server counts","description":"Per-country rollup — the natural shape for a country picker.","operationId":"list_countries_api_v1_proton_countries_get","responses":{"200":{"description":"Per-country rollup, busiest first","content":{"application/json":{"schema":{}}}}}}},"/cbvpn/api/v1/proton/features":{"get":{"tags":["cbvpn","proton"],"summary":"Feature bitmask reference","description":"What the `features` bitmask on a server means.","operationId":"list_features_api_v1_proton_features_get","responses":{"200":{"description":"Flag names and their bit values","content":{"application/json":{"schema":{}}}}}}},"/cbvpn/api/v1/proton/inventory":{"get":{"tags":["cbvpn","proton"],"summary":"Harvest metadata and totals","description":"Totals from the most recent harvest — cheap enough to poll for a UI header.\n\n`harvested_at` is the measurement time. Proton's fleet turns over\ncontinuously: at the 2026-07-31 harvest, 44.9% of the previous library's\nendpoints had ceased to exist, so treat a stale inventory with suspicion.","operationId":"get_inventory_summary_api_v1_proton_inventory_get","responses":{"200":{"description":"Summary of the last harvest","content":{"application/json":{"schema":{}}}}}}},"/cbvpn/api/v1/proton/servers":{"get":{"tags":["cbvpn","proton"],"summary":"Query available exit servers","description":"Filtered server query, ordered by Proton's own score (lower is better).\n\n## For LLMs\n\n- This lists what *could* be used, not what is working. Nothing here has\n  been verified to carry traffic — that is `GET /api/v1/workers/`.\n- `p2p=true` is Proton's TORRENT-PERMISSION flag, not an IP-reputation\n  property. It does NOT clear bot-walls: a p2p exit and a non-p2p exit\n  returned byte-identical Cloudflare 403s (#51). These are all commercial\n  hosting ASNs. The residential-egress ladder (#51) needs a consumer-ISP\n  ASN, which no Proton server provides — that rung is DC0/AS7018, not\n  anything in this list.\n- Secure Core servers enter one country and exit another: `entry_country`\n  and `exit_country` differ, so filter on the one you actually mean.\n- `slug` is the generated `.ovpn` filename, so it is the link from a row\n  here to something deployable on a worker.","operationId":"list_servers_api_v1_proton_servers_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exit country, ISO-3166 (case-insensitive)","title":"Country"},"description":"Exit country, ISO-3166 (case-insensitive)"},{"name":"city","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exit city (case-insensitive)","title":"City"},"description":"Exit city (case-insensitive)"},{"name":"p2p","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Proton's torrent-permission flag. NOT residential egress and NOT a bot-wall bypass — see #51 before using this to pick an exit.","title":"P2P"},"description":"Proton's torrent-permission flag. NOT residential egress and NOT a bot-wall bypass — see #51 before using this to pick an exit."},{"name":"secure_core","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter Secure Core in/out","title":"Secure Core"},"description":"Filter Secure Core in/out"},{"name":"max_load","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":0},{"type":"null"}],"description":"Cap reported load","title":"Max Load"},"description":"Cap reported load"},{"name":"tier","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Plan tier: 0 free, 2 plus","title":"Tier"},"description":"Plan tier: 0 free, 2 plus"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Max rows","default":200,"title":"Limit"},"description":"Max rows"}],"responses":{"200":{"description":"Matching servers, least loaded first","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbvpn__ProtonServer"},"title":"Response List Servers Api V1 Proton Servers Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/telemetry":{"get":{"tags":["cbvpn","telemetry"],"summary":"Latest fleet telemetry snapshot","description":"Return the most recent snapshot — the screen's primary read.","operationId":"latest_api_v1_telemetry_get","responses":{"200":{"description":"Latest snapshot (or null if none yet)","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbvpn__TelemetrySnapshot"},{"type":"null"}],"title":"Response Latest Api V1 Telemetry Get"}}}}}},"post":{"tags":["cbvpn","telemetry"],"summary":"Ingest a fleet telemetry snapshot","description":"Accept a snapshot pushed by the pool collector over the mesh.\n\nCumulative tun0 counters in the snapshot are diffed against the previous\nsnapshot to derive per-worker throughput before storage.","operationId":"ingest_api_v1_telemetry_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__TelemetrySnapshot"}}},"required":true},"responses":{"200":{"description":"Snapshot stored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__TelemetryIngestResponse"}}}},"503":{"description":"Telemetry store not initialized"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/telemetry/history":{"get":{"tags":["cbvpn","telemetry"],"summary":"Recent telemetry snapshots (trend history)","description":"Return recent snapshots from the ring buffer for trend lines.","operationId":"history_api_v1_telemetry_history_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":120,"title":"Limit"}}],"responses":{"200":{"description":"Up to `limit` recent snapshots, oldest first","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbvpn__TelemetrySnapshot"},"title":"Response History Api V1 Telemetry History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/telemetry/stream":{"get":{"tags":["cbvpn","telemetry"],"summary":"Live telemetry push stream (Server-Sent Events)","description":"Server-Sent Events stream for a live broadcast screen.\n\nEmits the current snapshot immediately, then one `data:` event per ingest.\nA `: keepalive` comment every 15s keeps proxies from idling the connection.","operationId":"stream_api_v1_telemetry_stream_get","responses":{"200":{"description":"text/event-stream of snapshots as they land","content":{"application/json":{"schema":{}}}}}}},"/cbvpn/api/v1/usage/":{"get":{"tags":["cbvpn","usage"],"summary":"List usage records","description":"Query API usage records with filtering and pagination.\n\n**Filters:**\n- `user_id`: Filter by specific user\n- `operation`: Filter by operation type\n- `start_date`/`end_date`: Filter by time range\n- `success`: Filter by success status\n- `has_vpn`: Filter by VPN usage\n\nRecords are returned newest-first.","operationId":"list_usage_api_v1_usage__get","parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by user ID","title":"User Id"},"description":"Filter by user ID"},{"name":"operation","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbvpn__OperationType"},{"type":"null"}],"description":"Filter by operation type","title":"Operation"},"description":"Filter by operation type"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of time range (ISO format)","title":"Start Date"},"description":"Start of time range (ISO format)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of time range (ISO format)","title":"End Date"},"description":"End of time range (ISO format)"},{"name":"success","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by success status","title":"Success"},"description":"Filter by success status"},{"name":"has_vpn","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by VPN usage","title":"Has Vpn"},"description":"Filter by VPN usage"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number","default":1,"title":"Page"},"description":"Page number"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Results per page","default":50,"title":"Page Size"},"description":"Results per page"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__UsageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/usage/summary":{"get":{"tags":["cbvpn","usage"],"summary":"Get usage summary","description":"Get aggregated usage statistics for a time period.\n\n**Summary includes:**\n- Request counts (total, successful, failed)\n- Latency statistics (avg, min, max, p95)\n- Breakdown by operation type\n- Unique users and request counts per user\n- VPN usage statistics (requests, countries)\n- Traffic totals (bytes sent/received)\n\n**Period types:**\n- `hour`: Last hour or specified hour\n- `day`: Last 24 hours (default)\n- `week`: Last 7 days\n- `month`: Last 30 days","operationId":"get_usage_summary_api_v1_usage_summary_get","parameters":[{"name":"period_type","in":"query","required":false,"schema":{"enum":["hour","day","week","month"],"type":"string","description":"Aggregation period","default":"day","title":"Period Type"},"description":"Aggregation period"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Custom start date (overrides period_type)","title":"Start Date"},"description":"Custom start date (overrides period_type)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Custom end date","title":"End Date"},"description":"Custom end date"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__UsageSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/usage/users/":{"get":{"tags":["cbvpn","usage"],"summary":"List tracked users","description":"Get all users who have made API requests.","operationId":"list_users_api_v1_usage_users__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbvpn__UserInfo"},"type":"array","title":"Response List Users Api V1 Usage Users  Get"}}}}}}},"/cbvpn/api/v1/usage/users/{user_id}":{"get":{"tags":["cbvpn","usage"],"summary":"Get user info","description":"Get detailed information about a specific API user.","operationId":"get_user_api_v1_usage_users__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__UserInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/usage/{request_id}":{"get":{"tags":["cbvpn","usage"],"summary":"Get usage record","description":"Get a specific usage record by its request ID.","operationId":"get_usage_record_api_v1_usage__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__UsageRecord"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}},"delete":{"tags":["cbvpn","usage"],"summary":"Delete usage record","description":"Delete a specific usage record. Returns 204 on success.","operationId":"delete_usage_record_api_v1_usage__request_id__delete","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Usage Record Api V1 Usage  Request Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/workers/":{"get":{"tags":["cbvpn","workers"],"summary":"List all workers","description":"List all VPN workers with current status and assignments.\n\n## What This Returns\n\nAll 15 workers with:\n- VPN tunnel status (up/down/error)\n- Current VPN profile and exit IP\n- Proxy status\n- Bank assignment (if any)\n\n## Use Cases\n\n- **Capacity Planning**: See available vs allocated workers\n- **Health Check**: Identify workers with VPN issues\n- **Troubleshooting**: Find workers failing in specific region\n\n## Where This Comes From\n\nThe pool collector measures every exit once a minute by fetching through\nits proxy, and this endpoint projects that measurement — it does **not**\nprobe the devices on a read. So `exit_ip` is an observation, not a config\nclaim, and a worker only reports healthy if traffic provably came back.\n\nSee `GET /api/v1/workers/status` for the snapshot's age and provenance, and\n`GET /api/v1/telemetry` for the full cross-DC view.\n\n## Response Fields\n\n| Field | Description |\n|-------|-------------|\n| `worker_number` | Worker ID (1-16) |\n| `is_healthy` | **Verified to carry traffic** — the field to select on |\n| `worker_ip` | Management IP, derived from `worker_number` |\n| `vpn_status` | \"up\" only when an exit was observed |\n| `exit_ip` | Last **observed** public exit IP (null if never seen) |\n| `exit_ip_checked_at` | When that exit was measured |\n| `proxy_status` | \"running\" only when the proxy provably served traffic |\n| `assigned_bank` | Assigned bank (null if unallocated) |\n\n## For LLMs\n\n- Use `is_healthy` for capacity — it means *verified to carry traffic*.\n  `vpn_status == \"up\"` alone is not a usability signal.\n- **Never use a worker whose `exit_ip` is null.** Such a worker has no\n  proven VPN egress; sending traffic to it can leak the host's real IP.\n- Workers with `assigned_bank == null` are available for allocation.","operationId":"list_workers_api_v1_workers__get","responses":{"200":{"description":"List of all workers with VPN and proxy status","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbvpn__Worker"},"type":"array","title":"Response List Workers Api V1 Workers  Get"}}}}}}},"/cbvpn/api/v1/workers/status":{"get":{"tags":["cbvpn","workers"],"summary":"Enriched node status list","description":"Enriched status list with node info, geography, and IP addresses.\n\nServed from the collector's measured snapshot — no inline probing, so this\nreturns in milliseconds rather than hanging on sixteen SSH round-trips\n(cbvpn#49). `source`, `collected_at`, `age_seconds` and `stale` describe\nthe measurement itself: if `stale` is true the collector has fallen behind\nand **no worker is reported healthy**, though last-observed exit IPs are\nstill returned so you can see what broke.","operationId":"get_worker_status_api_v1_workers_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__NodeStatusSummary"}}}}}}},"/cbvpn/api/v1/workers/{worker_number}":{"get":{"tags":["cbvpn","workers"],"summary":"Get worker status","description":"Get specific worker status and configuration.\n\n## What This Returns\n\nWorker state from the collector's measured snapshot — same truth as\n`GET /api/v1/workers/`, so a single read and a list read can never\ndisagree:\n- Whether the exit is verified to carry traffic\n- Last observed exit IP and when it was measured\n- Bank assignment details\n\n## Query Parameters\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| `probe` | `false` | SSH the device for live detail (uptime, load, VPN profile, interface). Slow — seconds, and it can hang on an unreachable worker. Use when diagnosing *why* an exit is down, not for routine reads. |\n\n## Path Parameters\n\n| Parameter | Range | Description |\n|-----------|-------|-------------|\n| `worker_number` | 1-16 | Worker identifier |","operationId":"get_worker_api_v1_workers__worker_number__get","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}},{"name":"probe","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Probe"}}],"responses":{"200":{"description":"Worker details with VPN and proxy status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__Worker"}}}},"400":{"description":"Invalid worker number","content":{"application/json":{"example":{"detail":"Worker number must be 1-16"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/workers/{worker_number}/cycle":{"post":{"tags":["cbvpn","workers"],"summary":"Cycle one worker to a fresh profile (same bank filter)","description":"Rotate a single worker to a fresh random profile.\n\nPicks a new random profile from a `country[:state][:type]` filter,\nredeploys to the worker, restarts OpenVPN, and (when `validate=true`)\nconfirms a usable exit IP. Retries with new profiles up to `max_attempts`\ntimes if the exit IP comes back empty.\n\n## Which filter is used\n\n`geo` if given, otherwise the filter of the bank that owns the worker.\nThere is no random fallback: rotating a worker to an arbitrary country\nbecause nobody said what they wanted is how a bank ends up mis-sited.\n\n**`geo` is required for a worker no bank owns** — which is most of them.\n14 of 16 workers belong to no bank; they are the pool `pool.py` and the\ntelemetry consumers draw on, and until now there was no way to refresh\nany of their profiles through the API. Worker 11 was found still running a\nhand-staged profile from Sep 2025 whose server had left Proton's inventory.\n\nPassing a `geo` that contradicts the owning bank's filter is **refused**,\nnot applied — mis-siting a bank should not be reachable from a query\nparameter. Change the bank's filter instead.\n\n## Query Parameters\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| `validate` | true | Re-check exit IP after deploy; retry on empty |\n| `max_attempts` | 3 | Max profile attempts before giving up |\n| `geo` | bank's filter | Explicit filter, e.g. `us`, `us:fl`, `us::p2p` |\n\n## When To Use\n\nSurgical rotation when one worker's exit endpoint is stale but the rest\nof its bank is fine, or to refresh an unassigned pool worker. Cheaper than\n`POST /api/v1/banks/{tag}/cycle` which rotates every worker in the bank.","operationId":"cycle_worker_api_v1_workers__worker_number__cycle_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}},{"name":"validate","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Validate"}},{"name":"max_attempts","in":"query","required":false,"schema":{"type":"integer","default":3,"title":"Max Attempts"}},{"name":"geo","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo"}}],"responses":{"200":{"description":"Worker rotated to a new profile","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cycle Worker Api V1 Workers  Worker Number  Cycle Post"},"example":{"worker_number":1,"bank_tag":"canadaprimary","filter":"country=ca","ok":true,"profile":"ca-mtl-117.protonvpn.udp.ovpn","exit_ip":"185.156.46.144","country":"CA"}}}},"400":{"description":"Invalid worker number"},"404":{"description":"Worker not assigned to any bank and no `geo` given"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/workers/{worker_number}/exit-ip":{"get":{"tags":["cbvpn","workers"],"summary":"Get exit IP and location","description":"Get current exit IP and geolocation for a worker.\n\n## What This Returns\n\nQueries ipinfo.io from the worker to get:\n- Public IP address (exit IP through VPN)\n- Country and region/state\n- City (approximate)\n- ISP/Organization (usually VPN provider)\n\n## Use Cases\n\n- **Verify VPN**: Confirm traffic exits in expected geography\n- **Debug Routing**: Check if VPN is working correctly\n- **Compliance**: Document exit location for auditing\n\n## Response Fields\n\n| Field | Description |\n|-------|-------------|\n| `ip` | Public IPv4 address |\n| `country` | ISO country code |\n| `region` | State/province name |\n| `city` | City name |\n| `org` | Organization/ISP |\n\n## For LLMs\n\n- Compare `country` with expected filter (e.g., \"US\" for \"us:ca\")\n- If `country` doesn't match, VPN may be misconfigured\n- `org` containing \"VPN\" confirms traffic is routing through tunnel","operationId":"get_exit_ip_api_v1_workers__worker_number__exit_ip_get","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"Exit IP with geolocation data","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Exit Ip Api V1 Workers  Worker Number  Exit Ip Get"},"example":{"ip":"203.0.113.42","country":"US","region":"California","city":"Los Angeles","org":"VPN Provider Inc"}}}},"400":{"description":"Invalid worker number","content":{"application/json":{"example":{"detail":"Worker number must be 1-16"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/workers/{worker_number}/proxy/restart":{"post":{"tags":["cbvpn","workers"],"summary":"Restart HTTP proxy","description":"Restart HTTP proxy to clear state and refresh connections.\n\n## What This Does\n\n1. Stops TinyProxy daemon\n2. Clears any cached state\n3. Restarts with fresh configuration\n\n## Use Cases\n\n- **Memory Issues**: Clear proxy memory after heavy usage\n- **Configuration Changes**: Apply new proxy settings\n- **Stuck Connections**: Clear hung connection state","operationId":"restart_proxy_api_v1_workers__worker_number__proxy_restart_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"Proxy restarted, returns updated worker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__Worker"}}}},"400":{"description":"Invalid worker number","content":{"application/json":{"example":{"detail":"Worker number must be 1-16"}}}},"500":{"description":"Failed to restart proxy","content":{"application/json":{"example":{"detail":"TinyProxy restart failed"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/workers/{worker_number}/proxy/start":{"post":{"tags":["cbvpn","workers"],"summary":"Start HTTP proxy","description":"Start HTTP proxy (TinyProxy) on a specific worker.\n\n## What This Does\n\n1. Starts TinyProxy daemon\n2. Configures proxy to route through VPN interface\n3. Makes proxy available on worker's port\n\n## Prerequisites\n\nVPN should be running for proxy traffic to route through VPN.\nIf VPN is down, proxy traffic will use direct routing.\n\n## Use Cases\n\n- **Enable Traffic Routing**: Prepare worker for HTTP traffic\n- **Recovery**: Restart proxy after crash","operationId":"start_proxy_api_v1_workers__worker_number__proxy_start_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"Proxy started, returns updated worker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__Worker"}}}},"400":{"description":"Invalid worker number","content":{"application/json":{"example":{"detail":"Worker number must be 1-16"}}}},"500":{"description":"Failed to start proxy","content":{"application/json":{"example":{"detail":"TinyProxy failed to start"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/workers/{worker_number}/proxy/stop":{"post":{"tags":["cbvpn","workers"],"summary":"Stop HTTP proxy","description":"Stop HTTP proxy (TinyProxy) on a specific worker.\n\n## What This Does\n\nGracefully stops the TinyProxy daemon. Any in-flight proxy\nrequests will be terminated.\n\n## Warning\n\nActive HTTP connections through this worker will be dropped.\nEnsure no jobs are actively using this worker.","operationId":"stop_proxy_api_v1_workers__worker_number__proxy_stop_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"Proxy stopped, returns updated worker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__Worker"}}}},"400":{"description":"Invalid worker number","content":{"application/json":{"example":{"detail":"Worker number must be 1-16"}}}},"500":{"description":"Failed to stop proxy","content":{"application/json":{"example":{"detail":"TinyProxy stop timeout"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/workers/{worker_number}/vpn/restart":{"post":{"tags":["cbvpn","workers"],"summary":"Restart VPN tunnel","description":"Restart VPN tunnel to refresh connection and exit IP.\n\n## What This Does\n\n1. Stops the current VPN tunnel\n2. Clears connection state\n3. Restarts with fresh connection\n4. May get a new exit IP from the VPN provider\n\n## Use Cases\n\n- **Refresh Exit IP**: Get a new public IP address\n- **Connection Issues**: Fix stuck or degraded tunnels\n- **Routine Maintenance**: Periodic tunnel refresh\n\n## When to Use\n\n- Worker `vpn_status == \"error\"`\n- Exit IP is stale or blacklisted\n- Tunnel performance is degraded","operationId":"restart_vpn_api_v1_workers__worker_number__vpn_restart_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"VPN restarted, returns updated worker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__Worker"}}}},"400":{"description":"Invalid worker number","content":{"application/json":{"example":{"detail":"Worker number must be 1-16"}}}},"500":{"description":"Failed to restart VPN","content":{"application/json":{"example":{"detail":"VPN restart failed: connection refused"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/workers/{worker_number}/vpn/start":{"post":{"tags":["cbvpn","workers"],"summary":"Start VPN tunnel","description":"Start VPN tunnel on a specific worker.\n\n## What This Does\n\n1. Loads the configured VPN profile for this worker\n2. Starts the WireGuard tunnel\n3. Waits for tunnel establishment\n4. Verifies exit IP matches expected geography\n\n## Use Cases\n\n- **Manual Recovery**: Start VPN after it was stopped\n- **Initial Setup**: Bring new worker online\n- **Post-Maintenance**: Restart after configuration changes\n\n## Response\n\nReturns updated Worker object. Check `vpn_status == \"up\"` and\n`exit_ip` is populated to confirm success.","operationId":"start_vpn_api_v1_workers__worker_number__vpn_start_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"VPN started, returns updated worker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__Worker"}}}},"400":{"description":"Invalid worker number","content":{"application/json":{"example":{"detail":"Worker number must be 1-16"}}}},"500":{"description":"Failed to start VPN","content":{"application/json":{"example":{"detail":"VPN service failed to start: timeout"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/api/v1/workers/{worker_number}/vpn/stop":{"post":{"tags":["cbvpn","workers"],"summary":"Stop VPN tunnel","description":"Stop VPN tunnel on a specific worker.\n\n## What This Does\n\n1. Gracefully stops the WireGuard tunnel\n2. Releases the VPN connection\n3. Worker traffic reverts to direct (non-VPN) routing\n\n## Warning\n\nStopping VPN will immediately affect any traffic routing through this worker.\nEnsure no active jobs are using this worker before stopping.\n\n## Use Cases\n\n- **Maintenance**: Prepare worker for configuration changes\n- **Troubleshooting**: Isolate worker for debugging\n- **Resource Management**: Stop unused tunnels to reduce load","operationId":"stop_vpn_api_v1_workers__worker_number__vpn_stop_post","parameters":[{"name":"worker_number","in":"path","required":true,"schema":{"type":"integer","title":"Worker Number"}}],"responses":{"200":{"description":"VPN stopped, returns updated worker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__Worker"}}}},"400":{"description":"Invalid worker number","content":{"application/json":{"example":{"detail":"Worker number must be 1-16"}}}},"500":{"description":"Failed to stop VPN","content":{"application/json":{"example":{"detail":"VPN service stop timeout"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HTTPValidationError"}}}}}}},"/cbvpn/health":{"get":{"tags":["cbvpn","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HealthResponse"}}}}}}},"/cbvpn/health/detailed":{"get":{"tags":["cbvpn","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__HealthResponse"}}}}}}},"/cbvpn/prime":{"get":{"tags":["cbvpn","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__PrimeResponse"}}}}}}},"/cbvpn/status":{"get":{"tags":["cbvpn","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbvpn__StatusResponse"}}}}}}},"/cbweave/api/v1/examples":{"get":{"tags":["cbweave","CBWEAVE","Examples"],"summary":"List bundled examples","description":"Return a sorted list of example slugs available under the bundled `examples/` directory. The listing recurses, so per-component examples appear under the `components/` prefix.\n\nUse the slug with `GET /api/v1/examples/{slug}` to render the example, or copy the markdown out of the repo at `examples/<slug>.md` to use as a starting point for your own document.","operationId":"list_examples_api_v1_examples_get","responses":{"200":{"description":"Available example slugs.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Examples Api V1 Examples Get"},"example":{"examples":["01-hello","02-kpi-tile","03-grid","05-dashboard","components/action-button","components/delta","components/elbow","components/kicker","components/live-indicator","components/meter","components/pill","components/sparkline","components/stubs","components/value"]}}}}}}},"/cbweave/api/v1/examples/{slug}":{"get":{"tags":["cbweave","CBWEAVE","Examples"],"summary":"Render a bundled example","description":"Compile and return the bundled example identified by `slug`. Always uses `fonts=reference` mode; for other modes, fetch the source markdown and POST to `/api/v1/render`.\n\nSlugs match filenames under `examples/` minus the `.md` extension. Subdirectory examples (like component demos) use their full path: `components/kicker`, `components/sparkline`, etc. See `GET /api/v1/examples` for the full list.","operationId":"get_example_api_v1_examples__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"SVG document","content":{"image/svg+xml":{"schema":{"type":"string"}}}},"404":{"description":"No example matches that slug."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__HTTPValidationError"}}}}}}},"/cbweave/api/v1/markdown-to-blocks":{"post":{"tags":["cbweave","CBWEAVE","Render"],"summary":"Normalize markdown to the v1.2 block JSON shape","description":"Parse a markdown document and return the equivalent typed block tree, as the chat-engine emitter and `POST /api/v1/render-blocks` will consume.\n\n**Pure**: no rendering, no font work, no surface resolution. Just frontmatter + directive normalization.\n\n**Returns** `{weave_version, frontmatter, blocks}` — same discriminated-union shape as the M11 ingest endpoint will accept, so the playground can round-trip authoring formats with a single POST.\n\nPer cbai E2 the request body is `extra=\"forbid\"`: unknown top-level fields return **400**.","operationId":"markdown_to_blocks_api_v1_markdown_to_blocks_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__MarkdownToBlocksRequest"}}},"required":true},"responses":{"200":{"description":"Normalized block tree.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Markdown To Blocks Api V1 Markdown To Blocks Post"},"example":{"weave_version":"1","frontmatter":{"surface":"desktop"},"blocks":[{"type":"headline","label":"ACTIVE SESSIONS","value":"12,478"}]}}}},"400":{"description":"Unknown field in request body."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__HTTPValidationError"}}}}}}},"/cbweave/api/v1/profiles":{"get":{"tags":["cbweave","CBWEAVE","Render"],"summary":"List the active surface profiles","description":"Returns every registered surface profile slug (9 at last count — surface.PROFILES is the truth) cbweave recognizes in `surface` fields. Useful for hosts building selector UIs.","operationId":"list_profiles_api_v1_profiles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Profiles Api V1 Profiles Get"}}}}}}},"/cbweave/api/v1/render":{"post":{"tags":["cbweave","CBWEAVE","Render"],"summary":"Compile markdown to SVG","description":"Compile a markdown document to a deterministic SVG.\n\nSame input → same bytes — no timestamps, no random IDs, no dict-iteration-order surprises. Safe to cache by hash of (markdown, fonts).\n\n**Pipeline** (see `docs/lcars-svg/lcars-svg/ARCHITECTURE.md`):\n1. Split YAML frontmatter.\n2. Parse body into a tree of `Leaf` / `Container` / `FencedBlock` nodes.\n3. Walk top-down: leaves → component renderers; containers → layout primitives (`vstack`, `hstack`, `grid`); data-consumer containers (`sparkline`) extract a fenced ` ```data ` JSON block.\n4. Emit canvas-wrapped `<svg>`; apply font-strategy post-pass.\n\n**Errors**: malformed YAML frontmatter renders an empty canvas rather than 4xx-ing. Unknown directives are silently skipped. The endpoint only 422s on missing `markdown` field.","operationId":"render_api_v1_render_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__RenderRequest"}}},"required":true},"responses":{"200":{"description":"SVG document","content":{"image/svg+xml":{"schema":{"type":"string"},"example":"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 280\" preserveAspectRatio=\"xMidYMid meet\"><rect width=\"400\" height=\"280\" fill=\"#050507\"/>… </svg>"}}},"422":{"description":"Invalid request body (missing `markdown` field)"}}}},"/cbweave/api/v1/render-blocks":{"post":{"tags":["cbweave","CBWEAVE","Render"],"summary":"Compile a block tree to SVG (v1.2 schema)","description":"JSON-native ingest for the data chat app. Discriminated union on `type`; closed enums; `extra=\"forbid\"` on every nested schema; per cbai E3, `delta.value` is validated against sign and mode (ambiguous-positive / sign-mode contradiction / direction-contradicts-sign all rejected at the boundary).\n\nResponse is always JSON `{weave_version, svgs, errors}` (per cbloom C1). Single-surface requests return a single-key map; multi-surface requests return one entry per profile. Per-profile failures land in `errors`, succeeding profiles still render (per cbloom C3 partial-success envelope).","operationId":"render_blocks_api_v1_render_blocks_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__RenderBlocksRequest"}}},"required":true},"responses":{"200":{"description":"At least one surface rendered successfully. `errors` may still be populated with per-profile failures. Per #10, `errors[surface]` is a list (capped at 50); `errors_truncated[surface]` flags when the cap bites.","content":{"application/json":{"schema":{},"example":{"weave_version":"1","svgs":{"desktop":"<svg xmlns=\"...\" viewBox=\"0 0 1280 720\">...</svg>"},"errors":{},"errors_truncated":{}}}}},"422":{"description":"All requested surfaces failed; envelope still returned with `svgs` populated as `null` per profile and `errors` carrying the per-block reasons."},"400":{"description":"Request body rejected by schema validation."}}}},"/cbweave/api/v1/render-blocks/examples":{"get":{"tags":["cbweave","CBWEAVE","Render"],"summary":"Canonical per-block-type examples (cbai prompt builder)","description":"Returns a `{block_type: [example_payload, …]}` map. cbai pulls this at startup and injects examples into its system-prompt builder rather than embedding them in the prompt template forever. Hash the response and refresh on change.","operationId":"render_blocks_examples_api_v1_render_blocks_examples_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Render Blocks Examples Api V1 Render Blocks Examples Get"}}}}}}},"/cbweave/api/v1/render-report":{"post":{"tags":["cbweave","CBWEAVE","Render"],"summary":"Compile a plain-markdown report to SVG","description":"Compile an ordinary GFM markdown report to a deterministic SVG.\n\nSame input → same bytes. Unlike `/render`, no `::directive` syntax is needed — the body is parsed as plain markdown and each construct is mapped to a Weave block. **GFM tables become Weave `table` blocks** (measured columns, GFM alignment, `<br>` two-line cells, zebra rows). The default `document` surface grows in height to fit the whole report.","operationId":"render_report_api_v1_render_report_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__RenderReportRequest"}}},"required":true},"responses":{"200":{"description":"SVG document","content":{"image/svg+xml":{}}},"422":{"description":"Invalid request body (missing `markdown` field)"}}}},"/cbweave/api/v1/render-template":{"post":{"tags":["cbweave","CBWEAVE","Render"],"summary":"Bind data into a template and compile to SVG","description":"Bind a JSON `data` payload into a directive-markdown template (`{{interpolation}}` + `:::each` iteration), then compile to a deterministic SVG via the standard pipeline.\n\nSame `(template, data, fonts, theme, surface)` → same bytes. Pass an inline `template`; named `template_ref` (tenant bucket) is 501 until that store lands.","operationId":"render_template_endpoint_api_v1_render_template_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__RenderTemplateRequest"}}},"required":true},"responses":{"200":{"description":"SVG document","content":{"image/svg+xml":{}}},"422":{"description":"Bad request body or unresolved strict binding"},"501":{"description":"Named template_ref not available yet"}}}},"/cbweave/api/v1/render/story":{"post":{"tags":["cbweave","CBWEAVE","Render"],"summary":"Compile markdown + beats to an animated scene bundle","description":"Compile directive markdown with **beat attrs** into a deterministic, content-hashed **scene bundle** the `storyjs` runtime plays on the cbcast Enhanced channel.\n\nSame input → same bytes — no timestamps, no random IDs. Cache on `bundle.hash`.\n\n**Pipeline:** parse → strip beat attrs → render each block through the existing component path → place (absolute x/y, canvas-only; a `:::board` is one node) → outline glyphs → precompute path arc-lengths → resolve the sequential timeline (`at`/`stagger`/`::hold`).\n\n**Errors:** malformed frontmatter renders an empty bundle rather than 4xx-ing; unknown directives are skipped; a bad beat attr (unknown `enter`/`ease`, non-numeric `dur`) 422s with the offending directive named. Only a missing `markdown` field is a plain 422.","operationId":"render_story_api_v1_render_story_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__RenderStoryRequest"}}},"required":true},"responses":{"200":{"description":"Scene bundle (JSON), optionally with `final_svg`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__RenderStoryResponse"}}}},"422":{"description":"Missing `markdown`, or a malformed beat attr."}}}},"/cbweave/api/v1/tool-schema":{"get":{"tags":["cbweave","CBWEAVE","Render"],"summary":"Claude tool descriptor for render_weave","description":"Returns the canonical Claude tool-input schema derived from the v1.2 pydantic models. cbai pulls this at startup and hands it directly to the agent loop; closed enums (per cbai E1) propagate automatically — the model's tool-use sampler treats invalid variants as low-probability.","operationId":"tool_schema_api_v1_tool_schema_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Tool Schema Api V1 Tool Schema Get"}}}}}}},"/cbweave/api/v1/zoo":{"get":{"tags":["cbweave","CBWEAVE"],"summary":"The component zoo — every animal, one catalog","description":"The full authoring vocabulary, assembled live from the registries of record: leaf components and containers (the `:::name{...}` directive DSL), data consumers, semantic directives, the compiler-special layouts (`board`), themes, and surface profiles. Use this instead of hand-maintaining component lists in primers and tool docs — it can never drift because it reads the same tables the compiler dispatches on.","operationId":"zoo_catalog_api_v1_zoo_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cbweave/health":{"get":{"tags":["cbweave","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__HealthResponse"}}}}}}},"/cbweave/health/detailed":{"get":{"tags":["cbweave","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__HealthResponse"}}}}}}},"/cbweave/prime":{"get":{"tags":["cbweave","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__PrimeResponse"}}}}}}},"/cbweave/status":{"get":{"tags":["cbweave","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbweave__StatusResponse"}}}}}}},"/cbwebhook/api/v1/admin/consumers":{"post":{"tags":["cbwebhook","Admin"],"summary":"Create a new API consumer","description":"Create a new API consumer with scoped access to specified form IDs. The raw API key is returned once in the response and cannot be retrieved later.","operationId":"create_consumer_api_v1_admin_consumers_post","parameters":[{"name":"x-admin-key","in":"header","required":true,"schema":{"type":"string","title":"X-Admin-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__ConsumerCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__ConsumerCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}},"get":{"tags":["cbwebhook","Admin"],"summary":"List all API consumers","description":"List all registered API consumers with their status, scoped form IDs, and creation timestamps. API key hashes are never exposed.","operationId":"list_consumers_api_v1_admin_consumers_get","parameters":[{"name":"x-admin-key","in":"header","required":true,"schema":{"type":"string","title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__ConsumerListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}}},"/cbwebhook/api/v1/admin/consumers/{consumer_id}":{"delete":{"tags":["cbwebhook","Admin"],"summary":"Deactivate an API consumer","description":"Soft-deactivate an API consumer by setting is_active to false. The consumer record is preserved but the API key will stop authenticating.","operationId":"delete_consumer_api_v1_admin_consumers__consumer_id__delete","parameters":[{"name":"consumer_id","in":"path","required":true,"schema":{"type":"integer","title":"Consumer Id"}},{"name":"x-admin-key","in":"header","required":true,"schema":{"type":"string","title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__ConsumerDeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}}},"/cbwebhook/api/v1/admin/endpoints":{"post":{"tags":["cbwebhook","Admin"],"summary":"Register an inbound webhook endpoint for a tenant","description":"Register a tenant-scoped inbound URL and issue its token. The raw token is returned once in this response and cannot be retrieved later.","operationId":"create_endpoint_api_v1_admin_endpoints_post","parameters":[{"name":"x-admin-key","in":"header","required":true,"schema":{"type":"string","title":"X-Admin-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__EndpointCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__EndpointCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}},"get":{"tags":["cbwebhook","Admin"],"summary":"List registered inbound endpoints","description":"List registered endpoints with their URL, delivery counts and most recent delivery. Pass `tenant` to filter to one tenant. Tokens are never returned.","operationId":"list_endpoints_api_v1_admin_endpoints_get","parameters":[{"name":"tenant","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one tenant slug.","title":"Tenant"},"description":"Filter to one tenant slug."},{"name":"x-admin-key","in":"header","required":true,"schema":{"type":"string","title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__EndpointListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}}},"/cbwebhook/api/v1/admin/endpoints/{endpoint_id}":{"delete":{"tags":["cbwebhook","Admin"],"summary":"Deactivate a registered endpoint","description":"Soft-deactivate an endpoint. Its URL starts answering 404 and its token stops authenticating; the record and its events are preserved.","operationId":"delete_endpoint_api_v1_admin_endpoints__endpoint_id__delete","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"integer","title":"Endpoint Id"}},{"name":"x-admin-key","in":"header","required":true,"schema":{"type":"string","title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__EndpointDeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}}},"/cbwebhook/api/v1/data/events":{"get":{"tags":["cbwebhook","Data Extraction"],"summary":"List events","description":"List webhook events scoped to the authenticated consumer's allowed form IDs. Supports filtering by form_id, event_type, and time range with cursor-based pagination.","operationId":"list_events_api_v1_data_events_get","parameters":[{"name":"form_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Form Id"}},{"name":"provider","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Source integration, e.g. `boldsign` or a registered endpoint's `t:<tenant>:<slug>`. Indexed — the only practical way to see one endpoint's deliveries in a log dominated by another provider.","title":"Provider"},"description":"Source integration, e.g. `boldsign` or a registered endpoint's `t:<tenant>:<slug>`. Indexed — the only practical way to see one endpoint's deliveries in a log dominated by another provider."},{"name":"event_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Type"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Since"}},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Until"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"x-api-key","in":"header","required":true,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__EventListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}}},"/cbwebhook/api/v1/data/events/{event_id}":{"get":{"tags":["cbwebhook","Data Extraction"],"summary":"Get a single event","description":"Retrieve a single webhook event by its event_id, including the full raw payload. Returns 404 if the event does not exist or is outside the consumer's scope.","operationId":"get_event_api_v1_data_events__event_id__get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}},{"name":"x-api-key","in":"header","required":true,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__EventDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}}},"/cbwebhook/api/v1/data/forms":{"get":{"tags":["cbwebhook","Data Extraction"],"summary":"List accessible forms","description":"List distinct Typeform forms that have received webhooks and are within the consumer's allowed scope. Includes event counts and the most recent submission timestamp.","operationId":"list_forms_api_v1_data_forms_get","parameters":[{"name":"x-api-key","in":"header","required":true,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__FormListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}}},"/cbwebhook/api/v1/data/forms/{form_id}/responses":{"get":{"tags":["cbwebhook","Data Extraction"],"summary":"Get flattened responses for a form","description":"Extract and flatten Typeform answers into a simplified structure. Each response includes field IDs, titles, types, and extracted values. Supports time-range filtering and pagination.","operationId":"list_form_responses_api_v1_data_forms__form_id__responses_get","parameters":[{"name":"form_id","in":"path","required":true,"schema":{"type":"string","title":"Form Id"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Since"}},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Until"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"x-api-key","in":"header","required":true,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__FormResponsesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}}},"/cbwebhook/api/v1/hooks/boldsign":{"post":{"tags":["cbwebhook","Webhooks"],"summary":"Receive BoldSign webhook","description":"Receives signing events from BoldSign (Sent, Viewed, Signed, Completed, Declined, Expired, Reassigned). Validates the webhook verification token and persists the event.","operationId":"receive_boldsign_webhook_api_v1_hooks_boldsign_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__BoldSignWebhookResponse"}}}}}}},"/cbwebhook/api/v1/hooks/gdrive":{"post":{"tags":["cbwebhook","Webhooks"],"summary":"Receive GDrive Pub/Sub push","description":"Receives Google Cloud Pub/Sub push messages for the GDrive sync topic. Verifies the inbound Google OIDC bearer token (audience must match `CBWEBHOOK_GDRIVE_AUDIENCE`), dedupes by `messageId`, and forwards the decoded payload to cbradio's internal sync trigger before returning 200.","operationId":"receive_gdrive_pubsub_api_v1_hooks_gdrive_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__GDriveWebhookResponse"}}}}}}},"/cbwebhook/api/v1/hooks/iterable/{project}":{"post":{"tags":["cbwebhook","Webhooks"],"summary":"Receive Iterable system webhook (click events)","description":"Receives system-webhook events from one Iterable project (the project slug rides in the path — Iterable's payload doesn't carry it). Click events (`emailClick`) are persisted and broadcast to the mesh topic `system.iterable.click`; all other event types are acknowledged and ignored.\n\nExample payload:\n\n```json\n{\"eventName\": \"emailClick\", \"email\": \"supporter@example.com\",\n \"dataFields\": {\"campaignId\": 1234567, \"messageId\": \"9c11…\",\n  \"url\": \"https://example.com/donate\"}}\n```","operationId":"receive_iterable_webhook_api_v1_hooks_iterable__project__post","parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string","description":"Iterable project slug (e.g. `cc-prod`) — must be listed in CBWEBHOOK_ITERABLE_PROJECTS.","examples":["cc-prod"],"title":"Project"},"description":"Iterable project slug (e.g. `cc-prod`) — must be listed in CBWEBHOOK_ITERABLE_PROJECTS."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__IterableWebhookResponse"}}}},"403":{"description":"Missing or invalid X-Iterable-Token header.","content":{"application/json":{"example":{"error":"Forbidden","detail":"Invalid Iterable webhook token","code":"TOKEN_INVALID"}}}},"404":{"description":"Unknown project slug (not in CBWEBHOOK_ITERABLE_PROJECTS).","content":{"application/json":{"example":{"error":"Not Found","detail":"Unknown Iterable project: cc-nope","code":"UNKNOWN_PROJECT"}}}},"422":{"description":"Body is not a JSON object/array of Iterable events.","content":{"application/json":{"example":{"error":"Validation error","detail":"eventName: Field required","code":"VALIDATION_ERROR"}}}}}}},"/cbwebhook/api/v1/hooks/t/{tenant}/{slug}":{"post":{"tags":["cbwebhook","Webhooks"],"summary":"Receive a delivery on a tenant-registered endpoint","description":"Generic inbound receiver for an endpoint registered under `/api/v1/admin/endpoints`. Authenticates the `X-Webhook-Token` issued at registration, then persists the JSON body to the event log with `provider` = `t:<tenant>:<slug>`. Send `X-Webhook-Event-Id` to make retries idempotent, and `X-Webhook-Event-Type` to label the event.","operationId":"receive_registered_webhook_api_v1_hooks_t__tenant___slug__post","parameters":[{"name":"tenant","in":"path","required":true,"schema":{"type":"string","title":"Tenant"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__EndpointReceiveResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HTTPValidationError"}}}}}}},"/cbwebhook/api/v1/hooks/typeform":{"post":{"tags":["cbwebhook","Webhooks"],"summary":"Receive Typeform webhook","description":"Receives and validates Typeform form_response webhooks.","operationId":"receive_typeform_webhook_api_v1_hooks_typeform_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__TypeformWebhookResponse"}}}}}}},"/cbwebhook/health":{"get":{"tags":["cbwebhook","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HealthResponse"}}}}}}},"/cbwebhook/health/detailed":{"get":{"tags":["cbwebhook","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__HealthResponse"}}}}}}},"/cbwebhook/prime":{"get":{"tags":["cbwebhook","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__PrimeResponse"}}}}}}},"/cbwebhook/status":{"get":{"tags":["cbwebhook","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbwebhook__StatusResponse"}}}}}}},"/cbworkflow/":{"get":{"tags":["cbworkflow","Health"],"summary":"Service info","description":"Get basic service information.\n\nReturns the service name, version, and documentation URL.","operationId":"root__get","responses":{"200":{"description":"Service information","content":{"application/json":{"schema":{}}}}}}},"/cbworkflow/api/connectors":{"get":{"tags":["cbworkflow","connectors"],"summary":"List all connectors","description":"List all registered connectors.","operationId":"list_connectors_api_connectors_get","parameters":[{"name":"connector_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__ConnectorType"},{"type":"null"}],"description":"Filter by type","title":"Connector Type"},"description":"Filter by type"},{"name":"healthy_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only return healthy connectors","default":false,"title":"Healthy Only"},"description":"Only return healthy connectors"}],"responses":{"200":{"description":"List of registered connectors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ConnectorListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/connectors/execute":{"post":{"tags":["cbworkflow","connectors"],"summary":"Execute connector action","description":"Execute an action via a connector.","operationId":"execute_action_api_connectors_execute_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ActionExecuteRequest"}}},"required":true},"responses":{"200":{"description":"Action executed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ActionExecuteResponse"}}}},"404":{"description":"Connector not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Connector unhealthy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/connectors/health":{"get":{"tags":["cbworkflow","connectors"],"summary":"Get all connector health","description":"Get health status of all connectors.","operationId":"get_all_health_api_connectors_health_get","responses":{"200":{"description":"Health status of all connectors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HealthCheckResponse"}}}}}}},"/cbworkflow/api/connectors/specs":{"get":{"tags":["cbworkflow","connectors"],"summary":"List all connector specifications","description":"List specifications for all registered connectors.\n\nReturns full specs including credential schemas, action definitions,\nand configuration options. Used by the UI to render dynamic forms.","operationId":"list_connector_specs_api_connectors_specs_get","responses":{"200":{"description":"Full specs for all connectors","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbworkflow__ConnectorSpec"},"type":"array","title":"Response List Connector Specs Api Connectors Specs Get"}}}}}}},"/cbworkflow/api/connectors/tasks/{task_id}":{"get":{"tags":["cbworkflow","connectors"],"summary":"Get task status","description":"Get status of a queued task.","operationId":"get_task_status_api_connectors_tasks__task_id__get","parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Task status and result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TaskStatusResponse"}}}},"404":{"description":"Task not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"delete":{"tags":["cbworkflow","connectors"],"summary":"Cancel pending task","description":"Cancel a pending task.","operationId":"cancel_task_api_connectors_tasks__task_id__delete","parameters":[{"name":"task_id","in":"path","required":true,"schema":{"type":"string","title":"Task Id"}}],"responses":{"200":{"description":"Task cancelled successfully","content":{"application/json":{"schema":{}}}},"400":{"description":"Task cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/connectors/webhook/{connector_type}":{"post":{"tags":["cbworkflow","connectors"],"summary":"Receive external webhook","description":"Receive webhook from external service.","operationId":"receive_webhook_api_connectors_webhook__connector_type__post","parameters":[{"name":"connector_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/cbworkflow__ConnectorType"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WebhookPayload"}}}},"responses":{"200":{"description":"Webhook processed or acknowledged","content":{"application/json":{"schema":{}}}},"404":{"description":"Connector not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/connectors/{connector_type}":{"get":{"tags":["cbworkflow","connectors"],"summary":"Get connector by type","description":"Get information about a specific connector.","operationId":"get_connector_api_connectors__connector_type__get","parameters":[{"name":"connector_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/cbworkflow__ConnectorType"}}],"responses":{"200":{"description":"Connector information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ConnectorInfo"}}}},"404":{"description":"Connector not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/connectors/{connector_type}/health":{"get":{"tags":["cbworkflow","connectors"],"summary":"Get connector health","description":"Get health status of a specific connector.","operationId":"get_connector_health_api_connectors__connector_type__health_get","parameters":[{"name":"connector_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/cbworkflow__ConnectorType"}}],"responses":{"200":{"description":"Connector health status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ConnectorHealth"}}}},"404":{"description":"Connector not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/connectors/{connector_type}/spec":{"get":{"tags":["cbworkflow","connectors"],"summary":"Get connector specification","description":"Get the full specification for a specific connector.\n\nReturns credential schema, action definitions, and configuration\noptions. Used by the UI to render dynamic forms.","operationId":"get_connector_spec_api_connectors__connector_type__spec_get","parameters":[{"name":"connector_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/cbworkflow__ConnectorType"}}],"responses":{"200":{"description":"Full connector specification","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ConnectorSpec"}}}},"404":{"description":"Connector not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/contacts":{"get":{"tags":["cbworkflow","contacts"],"summary":"List all contacts","description":"List all contacts with optional filters.\n\nReturns contacts filtered by county, tag, and/or organization.\nAll filters are optional and combined with AND logic.","operationId":"list_contacts_api_contacts_get","parameters":[{"name":"county","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by county","title":"County"},"description":"Filter by county"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tag","title":"Tag"},"description":"Filter by tag"},{"name":"organization","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by organization","title":"Organization"},"description":"Filter by organization"}],"responses":{"200":{"description":"List of contacts matching filters","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__Contact"},"title":"Response List Contacts Api Contacts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"post":{"tags":["cbworkflow","contacts"],"summary":"Create new contact","description":"Create a new contact in the CRM.\n\nRequires at minimum a `name`. Returns the created contact with\nauto-generated ID and timestamps.","operationId":"create_contact_api_contacts_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ContactCreate"}}}},"responses":{"201":{"description":"Contact created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Contact"}}}},"422":{"description":"Validation error in request body"}}}},"/cbworkflow/api/contacts/search":{"get":{"tags":["cbworkflow","contacts"],"summary":"Search contacts by name/email/phone","description":"Search contacts by name, email, or phone using fuzzy matching.\n\nUse this endpoint when you have partial information about a contact\nand need to find matches across multiple fields.","operationId":"search_contacts_api_contacts_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","description":"Search query (name, email, or phone)","title":"Q"},"description":"Search query (name, email, or phone)"}],"responses":{"200":{"description":"Contacts matching search query","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__Contact"},"title":"Response Search Contacts Api Contacts Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/contacts/{contact_id}":{"get":{"tags":["cbworkflow","contacts"],"summary":"Get contact by ID","description":"Get a single contact by ID.\n\nReturns full contact details including workflow instance IDs,\ntags, and metadata.","operationId":"get_contact_api_contacts__contact_id__get","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"responses":{"200":{"description":"Contact details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Contact"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"patch":{"tags":["cbworkflow","contacts"],"summary":"Update existing contact","description":"Update an existing contact's fields.\n\nOnly provided fields are updated. To clear a field, set it to null.","operationId":"update_contact_api_contacts__contact_id__patch","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ContactUpdate"}}}},"responses":{"200":{"description":"Contact updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Contact"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation error in request body"}}},"delete":{"tags":["cbworkflow","contacts"],"summary":"Delete contact","description":"Delete a contact permanently.\n\nThis also removes the contact from any linked workflow instances.","operationId":"delete_contact_api_contacts__contact_id__delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"responses":{"204":{"description":"Contact deleted successfully"},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/contacts/{contact_id}/tags/{tag}":{"post":{"tags":["cbworkflow","contacts"],"summary":"Add tag to contact","description":"Add a tag to a contact for segmentation.\n\nTags are used for filtering contacts (e.g., 'volunteer', 'donor', 'priority').\nAdding a tag that already exists is a no-op.","operationId":"add_tag_api_contacts__contact_id__tags__tag__post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"tag","in":"path","required":true,"schema":{"type":"string","title":"Tag"}}],"responses":{"200":{"description":"Tag added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Contact"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"delete":{"tags":["cbworkflow","contacts"],"summary":"Remove tag from contact","description":"Remove a tag from a contact.\n\nRemoving a tag that doesn't exist is a no-op.","operationId":"remove_tag_api_contacts__contact_id__tags__tag__delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"tag","in":"path","required":true,"schema":{"type":"string","title":"Tag"}}],"responses":{"200":{"description":"Tag removed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Contact"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/contacts/{contact_id}/timeline":{"get":{"tags":["cbworkflow","contacts"],"summary":"Get contact activity timeline","description":"Get unified activity timeline for a contact across all workflow instances.\n\nReturns all events from all workflow instances attached to this contact,\nsorted by timestamp (newest first). Each entry includes workflow context\n(template name, instance status, current node).","operationId":"get_contact_timeline_api_contacts__contact_id__timeline_get","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"responses":{"200":{"description":"Unified timeline of all workflow events","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__TimelineEntry"},"title":"Response Get Contact Timeline Api Contacts  Contact Id  Timeline Get"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/email/dead-letters":{"get":{"tags":["cbworkflow","email-dead-letters"],"summary":"List email dead letters","description":"List email dead letter messages with optional filtering.\n\nDead letters are incoming email messages that could not be routed\nto a tenant. They require manual review and resolution.\n\nArgs:\n    status: Filter by status (pending, resolved, discarded)\n    reason: Filter by reason (no_routing_match, tenant_disabled, etc.)\n    limit: Maximum number of results (default 100)\n    offset: Number of results to skip\n\nReturns:\n    List of email dead letter messages","operationId":"list_email_dead_letters_api_email_dead_letters_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterStatus"},{"type":"null"}],"title":"Status"}},{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterReason"},{"type":"null"}],"title":"Reason"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"List of email dead letter messages","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterMessage"},"title":"Response List Email Dead Letters Api Email Dead Letters Get"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/email/dead-letters/cleanup":{"post":{"tags":["cbworkflow","email-dead-letters"],"summary":"Clean up old email dead letters","description":"Clean up old resolved/discarded email dead letters.\n\nRemoves resolved and discarded email dead letters older than\nthe specified number of days.\n\nArgs:\n    days: Remove entries older than this many days (default 30)\n\nReturns:\n    Cleanup results","operationId":"cleanup_old_email_dead_letters_api_email_dead_letters_cleanup_post","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Cleanup completed with count of removed entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailCleanupResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/email/dead-letters/pending":{"get":{"tags":["cbworkflow","email-dead-letters"],"summary":"List pending email dead letters","description":"List pending email dead letters awaiting review.\n\nConvenience endpoint for getting only pending messages\nthat need attention.\n\nArgs:\n    limit: Maximum number of results (default 100)\n\nReturns:\n    List of pending email dead letters","operationId":"list_pending_email_dead_letters_api_email_dead_letters_pending_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"List of pending email dead letters","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterMessage"},"title":"Response List Pending Email Dead Letters Api Email Dead Letters Pending Get"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/email/dead-letters/summary":{"get":{"tags":["cbworkflow","email-dead-letters"],"summary":"Get email dead letter summary","description":"Get summary statistics for email dead letters.\n\nReturns counts by status and reason for monitoring\nand dashboards.\n\nReturns:\n    Summary statistics","operationId":"get_email_dead_letter_summary_api_email_dead_letters_summary_get","responses":{"200":{"description":"Summary statistics by status and reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterSummary"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}}}}},"/cbworkflow/api/email/dead-letters/{dead_letter_id}":{"get":{"tags":["cbworkflow","email-dead-letters"],"summary":"Get email dead letter by ID","description":"Get a specific email dead letter by ID.\n\nArgs:\n    dead_letter_id: The dead letter ID\n\nReturns:\n    The email dead letter message","operationId":"get_email_dead_letter_api_email_dead_letters__dead_letter_id__get","parameters":[{"name":"dead_letter_id","in":"path","required":true,"schema":{"type":"string","title":"Dead Letter Id"}}],"responses":{"200":{"description":"Email dead letter message details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterMessage"}}}},"404":{"description":"Email dead letter not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"delete":{"tags":["cbworkflow","email-dead-letters"],"summary":"Delete email dead letter","description":"Permanently delete an email dead letter.\n\nThis removes the email dead letter from the system entirely.\nUse with caution - prefer resolve or discard for audit trail.\n\nArgs:\n    dead_letter_id: The dead letter ID\n\nReturns:\n    Deletion confirmation","operationId":"delete_email_dead_letter_api_email_dead_letters__dead_letter_id__delete","parameters":[{"name":"dead_letter_id","in":"path","required":true,"schema":{"type":"string","title":"Dead Letter Id"}}],"responses":{"200":{"description":"Email dead letter deleted permanently","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterDeleteResponse"}}}},"404":{"description":"Email dead letter not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/email/dead-letters/{dead_letter_id}/discard":{"post":{"tags":["cbworkflow","email-dead-letters"],"summary":"Discard email dead letter","description":"Discard an email dead letter (mark as spam/irrelevant).\n\nUse this for messages that are spam, irrelevant, or don't\nneed to be routed to any tenant.\n\nArgs:\n    dead_letter_id: The dead letter ID\n    request: Optional discard details (notes)\n\nReturns:\n    Action confirmation with updated dead letter","operationId":"discard_email_dead_letter_api_email_dead_letters__dead_letter_id__discard_post","parameters":[{"name":"dead_letter_id","in":"path","required":true,"schema":{"type":"string","title":"Dead Letter Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterDiscardRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Email dead letter discarded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterActionResponse"}}}},"404":{"description":"Email dead letter not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/email/dead-letters/{dead_letter_id}/resolve":{"post":{"tags":["cbworkflow","email-dead-letters"],"summary":"Resolve email dead letter","description":"Resolve an email dead letter by assigning it to a tenant.\n\nUse this to manually route a message that couldn't be\nautomatically routed. The message will be marked as resolved\nand can be processed by the assigned tenant.\n\nArgs:\n    dead_letter_id: The dead letter ID\n    request: Resolution details (tenant_id, optional notes)\n\nReturns:\n    Action confirmation with updated dead letter","operationId":"resolve_email_dead_letter_api_email_dead_letters__dead_letter_id__resolve_post","parameters":[{"name":"dead_letter_id","in":"path","required":true,"schema":{"type":"string","title":"Dead Letter Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterResolveRequest"}}}},"responses":{"200":{"description":"Email dead letter resolved to tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterActionResponse"}}}},"404":{"description":"Email dead letter not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/forge/journeys":{"post":{"tags":["cbworkflow","forge-journeys"],"summary":"Start a journey on the cbforge engine","description":"Start a durable journey for a contact from a ported template.\n\nRuns on the cbforge engine: the journey advances to its first wait (or\ncompletion) and persists; timer waits are woken by the conductor's\nscheduler, event waits by /deliver or inbound SMS.","operationId":"start_journey_api_forge_journeys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__JourneyCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Start Journey Api Forge Journeys Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/forge/journeys/deliver":{"post":{"tags":["cbworkflow","forge-journeys"],"summary":"Deliver an external event (payload → wait routing)","description":"Deliver an external event to waiting journeys (durable: enqueued\nbefore processing). The optional payload's `outcome` selects the wait's\ntransition, e.g. {\"outcome\": \"responded\"}; `instance_id` narrows delivery\nto a single journey.","operationId":"deliver_event_api_forge_journeys_deliver_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EventDeliver"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Deliver Event Api Forge Journeys Deliver Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/forge/journeys/{journey_id}":{"get":{"tags":["cbworkflow","forge-journeys"],"summary":"Journey status","description":"Full journey record: status, pending wait, history, result data.","operationId":"journey_status_api_forge_journeys__journey_id__get","parameters":[{"name":"journey_id","in":"path","required":true,"schema":{"type":"string","title":"Journey Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Journey Status Api Forge Journeys  Journey Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/guides":{"get":{"tags":["cbworkflow","guides"],"summary":"List all user guides","description":"List all available user guides with blurbs.\n\nReturns a list of guides found in docs/user-guides/ with\ntheir titles and preview blurbs extracted from the content.","operationId":"list_guides_api_guides_get","responses":{"200":{"description":"List of available guides with previews","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/cbworkflow__GuideListItem"},"type":"array","title":"Response List Guides Api Guides Get"}}}}}}},"/cbworkflow/api/guides/{slug}":{"get":{"tags":["cbworkflow","guides"],"summary":"Get guide by slug","description":"Get full content of a specific guide.\n\nThe slug is the filename without extension, with underscores\nconverted to hyphens. For example:\n- CLIENT-INTEGRATION.md -> client-integration\n- CONNECTOR-DEVELOPMENT.md -> connector-development","operationId":"get_guide_api_guides__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Full guide content in markdown","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__GuideDetail"}}}},"404":{"description":"Guide not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"500":{"description":"Error reading guide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/instances":{"get":{"tags":["cbworkflow","instances"],"summary":"List all workflow instances","description":"List all workflow instances with optional filters.\n\nAll filters are combined with AND logic. Common patterns:\n- Get all instances for a contact: `?contact_id=xxx`\n- Get active instances: `?status=active`\n- Get instances from a template: `?template_id=xxx`","operationId":"list_instances_api_instances_get","parameters":[{"name":"contact_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by contact ID","title":"Contact Id"},"description":"Filter by contact ID"},{"name":"template_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by template ID","title":"Template Id"},"description":"Filter by template ID"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__InstanceStatus"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tag","title":"Tag"},"description":"Filter by tag"}],"responses":{"200":{"description":"List of workflow instances matching filters","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"},"title":"Response List Instances Api Instances Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"post":{"tags":["cbworkflow","instances"],"summary":"Create workflow instance","description":"Create a new workflow instance from a template.\n\nCreates the instance in PENDING status. Call POST /{id}/start to\nbegin execution. The template's nodes are copied (frozen) at creation.","operationId":"create_instance_api_instances_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstanceCreate"}}}},"responses":{"201":{"description":"Instance created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"400":{"description":"Invalid contact or template ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation error in request body"}}}},"/cbworkflow/api/instances/{instance_id}":{"get":{"tags":["cbworkflow","instances"],"summary":"Get workflow instance by ID","description":"Get a workflow instance by ID.\n\nReturns full instance details including current node, state,\ntimeline events, and the frozen node graph.","operationId":"get_instance_api_instances__instance_id__get","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}}],"responses":{"200":{"description":"Instance details including state and timeline","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"404":{"description":"Instance not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"delete":{"tags":["cbworkflow","instances"],"summary":"Delete workflow instance","description":"Delete a workflow instance.\n\nRemoves the instance and unlinks it from the associated contact.","operationId":"delete_instance_api_instances__instance_id__delete","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}}],"responses":{"204":{"description":"Instance deleted successfully"},"404":{"description":"Instance not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/instances/{instance_id}/notes":{"post":{"tags":["cbworkflow","instances"],"summary":"Add note to instance timeline","description":"Add a user note to the instance timeline.\n\nCreates a 'user_note' event in the timeline with the provided text.\nUseful for recording observations or context about the workflow progress.","operationId":"add_note_api_instances__instance_id__notes_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__NoteRequest"}}}},"responses":{"200":{"description":"Note added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"404":{"description":"Instance not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/instances/{instance_id}/pause":{"post":{"tags":["cbworkflow","instances"],"summary":"Pause workflow instance","description":"Pause a running workflow instance.\n\nTransitions from ACTIVE to PAUSED status. The instance can be\nresumed later with POST /{id}/resume.","operationId":"pause_instance_api_instances__instance_id__pause_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Actor initiating the pause","title":"Actor"},"description":"Actor initiating the pause"}],"responses":{"200":{"description":"Instance paused successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"400":{"description":"Cannot pause (wrong status)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/instances/{instance_id}/resume":{"post":{"tags":["cbworkflow","instances"],"summary":"Resume paused workflow instance","description":"Resume a paused workflow instance.\n\nTransitions from PAUSED back to ACTIVE status and continues\nprocessing from where it was paused.","operationId":"resume_instance_api_instances__instance_id__resume_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Actor initiating the resume","title":"Actor"},"description":"Actor initiating the resume"}],"responses":{"200":{"description":"Instance resumed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"400":{"description":"Cannot resume (wrong status)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/instances/{instance_id}/start":{"post":{"tags":["cbworkflow","instances"],"summary":"Start workflow instance","description":"Start a workflow instance execution.\n\nTransitions from PENDING to ACTIVE status and begins processing\nat the start node. Records an 'instance_started' event.","operationId":"start_instance_api_instances__instance_id__start_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Actor initiating the start","title":"Actor"},"description":"Actor initiating the start"}],"responses":{"200":{"description":"Instance started successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"400":{"description":"Cannot start (wrong status)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/instances/{instance_id}/state":{"post":{"tags":["cbworkflow","instances"],"summary":"Update instance state","description":"Update the mutable state of an instance.\n\nMerges the provided updates into the existing state dict.\nState values are used in decision rule evaluation via `state.xxx` syntax.","operationId":"update_instance_state_api_instances__instance_id__state_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__StateUpdateRequest"}}}},"responses":{"200":{"description":"State updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"404":{"description":"Instance not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/instances/{instance_id}/timeline":{"get":{"tags":["cbworkflow","instances"],"summary":"Get instance event timeline","description":"Get the timeline of events for an instance.\n\nReturns all events in the instance's timeline in chronological order.\nEvents include lifecycle changes, node transitions, actions, and notes.","operationId":"get_timeline_api_instances__instance_id__timeline_get","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}}],"responses":{"200":{"description":"List of events in chronological order","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__Event"},"title":"Response Get Timeline Api Instances  Instance Id  Timeline Get"}}}},"404":{"description":"Instance not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/instances/{instance_id}/transition":{"post":{"tags":["cbworkflow","instances"],"summary":"Manually transition workflow instance","description":"Manually transition an instance to the next node.\n\nUse this for manual decision nodes or to override automatic transitions.\nThe `outcome` must match a key in the current node's transitions dict.","operationId":"transition_instance_api_instances__instance_id__transition_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TransitionRequest"}}}},"responses":{"200":{"description":"Transition completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"400":{"description":"Invalid transition or outcome","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/sms/dead-letters":{"get":{"tags":["cbworkflow","dead-letters"],"summary":"List SMS dead letters","description":"List dead letter messages with optional filtering.\n\nDead letters are incoming SMS messages that could not be routed\nto a tenant. They require manual review and resolution.\n\nArgs:\n    status: Filter by status (pending, resolved, discarded)\n    reason: Filter by reason (no_routing_match, tenant_disabled, etc.)\n    limit: Maximum number of results (default 100)\n    offset: Number of results to skip\n\nReturns:\n    List of dead letter messages","operationId":"list_dead_letters_api_sms_dead_letters_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__DeadLetterStatus"},{"type":"null"}],"title":"Status"}},{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__DeadLetterReason"},{"type":"null"}],"title":"Reason"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"List of dead letter messages","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__DeadLetterMessage"},"title":"Response List Dead Letters Api Sms Dead Letters Get"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/sms/dead-letters/cleanup":{"post":{"tags":["cbworkflow","dead-letters"],"summary":"Clean up old dead letters","description":"Clean up old resolved/discarded dead letters.\n\nRemoves resolved and discarded dead letters older than\nthe specified number of days.\n\nArgs:\n    days: Remove entries older than this many days (default 30)\n\nReturns:\n    Cleanup results","operationId":"cleanup_old_dead_letters_api_sms_dead_letters_cleanup_post","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Cleanup completed with count of removed entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__CleanupResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/sms/dead-letters/pending":{"get":{"tags":["cbworkflow","dead-letters"],"summary":"List pending dead letters","description":"List pending dead letters awaiting review.\n\nConvenience endpoint for getting only pending messages\nthat need attention.\n\nArgs:\n    limit: Maximum number of results (default 100)\n\nReturns:\n    List of pending dead letters","operationId":"list_pending_dead_letters_api_sms_dead_letters_pending_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"List of pending dead letters","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__DeadLetterMessage"},"title":"Response List Pending Dead Letters Api Sms Dead Letters Pending Get"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/sms/dead-letters/summary":{"get":{"tags":["cbworkflow","dead-letters"],"summary":"Get dead letter summary","description":"Get summary statistics for dead letters.\n\nReturns counts by status and reason for monitoring\nand dashboards.\n\nReturns:\n    Summary statistics","operationId":"get_dead_letter_summary_api_sms_dead_letters_summary_get","responses":{"200":{"description":"Summary statistics by status and reason","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__DeadLetterSummary"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}}}}},"/cbworkflow/api/sms/dead-letters/{dead_letter_id}":{"get":{"tags":["cbworkflow","dead-letters"],"summary":"Get dead letter by ID","description":"Get a specific dead letter by ID.\n\nArgs:\n    dead_letter_id: The dead letter ID\n\nReturns:\n    The dead letter message","operationId":"get_dead_letter_api_sms_dead_letters__dead_letter_id__get","parameters":[{"name":"dead_letter_id","in":"path","required":true,"schema":{"type":"string","title":"Dead Letter Id"}}],"responses":{"200":{"description":"Dead letter message details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__DeadLetterMessage"}}}},"404":{"description":"Dead letter not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"delete":{"tags":["cbworkflow","dead-letters"],"summary":"Delete dead letter","description":"Permanently delete a dead letter.\n\nThis removes the dead letter from the system entirely.\nUse with caution - prefer resolve or discard for audit trail.\n\nArgs:\n    dead_letter_id: The dead letter ID\n\nReturns:\n    Deletion confirmation","operationId":"delete_dead_letter_api_sms_dead_letters__dead_letter_id__delete","parameters":[{"name":"dead_letter_id","in":"path","required":true,"schema":{"type":"string","title":"Dead Letter Id"}}],"responses":{"200":{"description":"Dead letter deleted permanently","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__DeadLetterDeleteResponse"}}}},"404":{"description":"Dead letter not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/sms/dead-letters/{dead_letter_id}/discard":{"post":{"tags":["cbworkflow","dead-letters"],"summary":"Discard dead letter","description":"Discard a dead letter (mark as spam/irrelevant).\n\nUse this for messages that are spam, irrelevant, or don't\nneed to be routed to any tenant.\n\nArgs:\n    dead_letter_id: The dead letter ID\n    request: Optional discard details (notes)\n\nReturns:\n    Action confirmation with updated dead letter","operationId":"discard_dead_letter_api_sms_dead_letters__dead_letter_id__discard_post","parameters":[{"name":"dead_letter_id","in":"path","required":true,"schema":{"type":"string","title":"Dead Letter Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__DeadLetterDiscardRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Dead letter discarded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__DeadLetterActionResponse"}}}},"404":{"description":"Dead letter not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/sms/dead-letters/{dead_letter_id}/resolve":{"post":{"tags":["cbworkflow","dead-letters"],"summary":"Resolve dead letter","description":"Resolve a dead letter by assigning it to a tenant.\n\nUse this to manually route a message that couldn't be\nautomatically routed. The message will be marked as resolved\nand can be processed by the assigned tenant.\n\nArgs:\n    dead_letter_id: The dead letter ID\n    request: Resolution details (tenant_id, optional notes)\n\nReturns:\n    Action confirmation with updated dead letter","operationId":"resolve_dead_letter_api_sms_dead_letters__dead_letter_id__resolve_post","parameters":[{"name":"dead_letter_id","in":"path","required":true,"schema":{"type":"string","title":"Dead Letter Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__DeadLetterResolveRequest"}}}},"responses":{"200":{"description":"Dead letter resolved to tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__DeadLetterActionResponse"}}}},"404":{"description":"Dead letter not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/":{"post":{"tags":["cbworkflow","tenants"],"summary":"Create new tenant","description":"Create a new tenant.\n\nCreates a new tenant organization that can have its own:\n- Contacts\n- Workflow templates\n- Workflow instances\n- Connector credentials","operationId":"create_tenant_api_tenants__post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TenantCreate"}}}},"responses":{"201":{"description":"Tenant created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Tenant"}}}},"422":{"description":"Validation error in request body"},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}}}},"get":{"tags":["cbworkflow","tenants"],"summary":"List all tenants","description":"List all tenants.\n\nArgs:\n    include_inactive: Include soft-deleted tenants.","operationId":"list_tenants_api_tenants__get","parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}}],"responses":{"200":{"description":"List of tenants","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__Tenant"},"title":"Response List Tenants Api Tenants  Get"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}":{"get":{"tags":["cbworkflow","tenants"],"summary":"Get tenant by ID","description":"Get a tenant by ID.","operationId":"get_tenant_api_tenants__tenant_id__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Tenant details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Tenant"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"patch":{"tags":["cbworkflow","tenants"],"summary":"Update tenant","description":"Update a tenant.","operationId":"update_tenant_api_tenants__tenant_id__patch","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TenantUpdate"}}}},"responses":{"200":{"description":"Tenant updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Tenant"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation error in request body"},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}}}},"delete":{"tags":["cbworkflow","tenants"],"summary":"Delete tenant","description":"Delete a tenant.\n\nArgs:\n    hard: If True, permanently delete. If False, soft delete (default).","operationId":"delete_tenant_api_tenants__tenant_id__delete","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"hard","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Hard"}}],"responses":{"204":{"description":"Tenant deleted successfully"},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/contacts":{"get":{"tags":["cbworkflow","contacts"],"summary":"List tenant contacts","description":"List contacts for this tenant with optional filters.","operationId":"list_contacts_api_tenants__tenant_id__contacts_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"county","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by county","title":"County"},"description":"Filter by county"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tag","title":"Tag"},"description":"Filter by tag"},{"name":"organization","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by organization","title":"Organization"},"description":"Filter by organization"}],"responses":{"200":{"description":"List of contacts matching filters","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__Contact"},"title":"Response List Contacts Api Tenants  Tenant Id  Contacts Get"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"post":{"tags":["cbworkflow","contacts"],"summary":"Create contact in tenant","description":"Create a new contact in this tenant.","operationId":"create_contact_api_tenants__tenant_id__contacts_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ContactCreate"}}}},"responses":{"201":{"description":"Contact created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Contact"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation error in request body"}}}},"/cbworkflow/api/tenants/{tenant_id}/contacts/search":{"get":{"tags":["cbworkflow","contacts"],"summary":"Search tenant contacts","description":"Search contacts by name, email, or phone within this tenant.","operationId":"search_contacts_api_tenants__tenant_id__contacts_search_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"q","in":"query","required":true,"schema":{"type":"string","description":"Search query (name, email, or phone)","title":"Q"},"description":"Search query (name, email, or phone)"}],"responses":{"200":{"description":"Contacts matching search query","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__Contact"},"title":"Response Search Contacts Api Tenants  Tenant Id  Contacts Search Get"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/contacts/{contact_id}":{"get":{"tags":["cbworkflow","contacts"],"summary":"Get contact by ID","description":"Get a contact by ID (validates tenant ownership).","operationId":"get_contact_api_tenants__tenant_id__contacts__contact_id__get","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Contact details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Contact"}}}},"404":{"description":"Contact or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"patch":{"tags":["cbworkflow","contacts"],"summary":"Update tenant contact","description":"Update an existing contact (validates tenant ownership).","operationId":"update_contact_api_tenants__tenant_id__contacts__contact_id__patch","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ContactUpdate"}}}},"responses":{"200":{"description":"Contact updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Contact"}}}},"404":{"description":"Contact or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation error in request body"}}},"delete":{"tags":["cbworkflow","contacts"],"summary":"Delete tenant contact","description":"Delete a contact (validates tenant ownership).","operationId":"delete_contact_api_tenants__tenant_id__contacts__contact_id__delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"204":{"description":"Contact deleted successfully"},"404":{"description":"Contact or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/contacts/{contact_id}/tags/{tag}":{"post":{"tags":["cbworkflow","contacts"],"summary":"Add tag to contact","description":"Add a tag to a contact (validates tenant ownership).","operationId":"add_tag_api_tenants__tenant_id__contacts__contact_id__tags__tag__post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"tag","in":"path","required":true,"schema":{"type":"string","title":"Tag"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Tag added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Contact"}}}},"404":{"description":"Contact or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"delete":{"tags":["cbworkflow","contacts"],"summary":"Remove tag from contact","description":"Remove a tag from a contact (validates tenant ownership).","operationId":"remove_tag_api_tenants__tenant_id__contacts__contact_id__tags__tag__delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"tag","in":"path","required":true,"schema":{"type":"string","title":"Tag"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Tag removed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__Contact"}}}},"404":{"description":"Contact or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/contacts/{contact_id}/timeline":{"get":{"tags":["cbworkflow","contacts"],"summary":"Get contact activity timeline","description":"Get unified activity timeline for a contact (validates tenant ownership).","operationId":"get_contact_timeline_api_tenants__tenant_id__contacts__contact_id__timeline_get","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Unified timeline of workflow events","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__TimelineEntry"},"title":"Response Get Contact Timeline Api Tenants  Tenant Id  Contacts  Contact Id  Timeline Get"}}}},"404":{"description":"Contact or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/credentials/":{"post":{"tags":["cbworkflow","credentials"],"summary":"Create or update credential","description":"Create or update credentials for a connector.\n\nIf credentials already exist for this tenant+connector, they are replaced.\n\nThe credential data is encrypted at rest. The raw credential values\n(API keys, tokens, etc.) are never returned in API responses.","operationId":"create_credential_api_tenants__tenant_id__credentials__post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__CredentialCreateRequest"}}}},"responses":{"201":{"description":"Credential created or updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__CredentialSummary"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"get":{"tags":["cbworkflow","credentials"],"summary":"List tenant credentials","description":"List all credentials for a tenant.\n\nReturns metadata only - secrets are never exposed.","operationId":"list_credentials_api_tenants__tenant_id__credentials__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"List of credential summaries (secrets not exposed)","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__CredentialSummary"},"title":"Response List Credentials Api Tenants  Tenant Id  Credentials  Get"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/credentials/api-key":{"post":{"tags":["cbworkflow","credentials"],"summary":"Create API key credential","description":"Create an API key credential.\n\nConvenience endpoint for the common case of API key authentication.","operationId":"create_api_key_credential_api_tenants__tenant_id__credentials_api_key_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"connector_type","in":"query","required":true,"schema":{"$ref":"#/components/schemas/cbworkflow__ConnectorType"}},{"name":"api_key","in":"query","required":true,"schema":{"type":"string","title":"Api Key"}},{"name":"label","in":"query","required":false,"schema":{"type":"string","default":"Default","title":"Label"}}],"responses":{"201":{"description":"API key credential created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__CredentialSummary"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/credentials/{connector_type}/check":{"get":{"tags":["cbworkflow","credentials"],"summary":"Check credential exists","description":"Check if credentials are configured for a connector.\n\nReturns whether credentials exist and basic metadata (no secrets).","operationId":"check_credential_exists_api_tenants__tenant_id__credentials__connector_type__check_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"connector_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/cbworkflow__ConnectorType"}}],"responses":{"200":{"description":"Credential status for the connector","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Check Credential Exists Api Tenants  Tenant Id  Credentials  Connector Type  Check Get"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/credentials/{credential_id}":{"get":{"tags":["cbworkflow","credentials"],"summary":"Get credential by ID","description":"Get credential metadata by ID.\n\nReturns metadata only - secrets are never exposed.","operationId":"get_credential_api_tenants__tenant_id__credentials__credential_id__get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}}],"responses":{"200":{"description":"Credential metadata (secrets not exposed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__CredentialSummary"}}}},"404":{"description":"Credential or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"delete":{"tags":["cbworkflow","credentials"],"summary":"Delete credential","description":"Delete a credential.","operationId":"delete_credential_api_tenants__tenant_id__credentials__credential_id__delete","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"credential_id","in":"path","required":true,"schema":{"type":"string","title":"Credential Id"}}],"responses":{"204":{"description":"Credential deleted successfully"},"404":{"description":"Credential or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/email":{"get":{"tags":["cbworkflow","tenant-email"],"summary":"Get email configuration","description":"Get email configuration for a tenant.\n\nReturns the tenant's email config including:\n- Email address\n- Provider\n- Verification status\n- Message counts\n\nArgs:\n    tenant_id: The tenant ID\n\nReturns:\n    The tenant's email configuration","operationId":"get_tenant_email_api_tenants__tenant_id__email_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Tenant email configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TenantEmailConfigResponse"}}}},"404":{"description":"Email config not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"delete":{"tags":["cbworkflow","tenant-email"],"summary":"Delete email configuration","description":"Remove email configuration for a tenant.\n\nThis will:\n- Close the POBox session\n- Delete the tenant's email config\n- Prevent further email sending/receiving\n- NOT affect routing entries (they will remain until replaced)\n\nArgs:\n    tenant_id: The tenant ID\n\nReturns:\n    Deletion confirmation","operationId":"delete_tenant_email_api_tenants__tenant_id__email_delete","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Email configuration deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailDeleteResponse"}}}},"404":{"description":"Email config not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/email/disable":{"post":{"tags":["cbworkflow","tenant-email"],"summary":"Disable tenant email","description":"Disable email for a tenant.\n\nSets the email status to DISABLED, preventing sending/receiving\nwithout deleting the configuration or closing the session.\n\nArgs:\n    tenant_id: The tenant ID\n\nReturns:\n    Updated email configuration","operationId":"disable_tenant_email_api_tenants__tenant_id__email_disable_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Email disabled successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TenantEmailConfigResponse"}}}},"404":{"description":"Email config not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/email/resend":{"post":{"tags":["cbworkflow","tenant-email"],"summary":"Resend verification email","description":"Resend the verification code for a tenant.\n\nGenerates a new verification code and sends it via email.\nUse this if the original code expired or wasn't received.\n\nArgs:\n    tenant_id: The tenant ID\n\nReturns:\n    Setup confirmation with updated config","operationId":"resend_verification_api_tenants__tenant_id__email_resend_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"New verification email sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailSetupResponse"}}}},"400":{"description":"Resend failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/email/setup":{"post":{"tags":["cbworkflow","tenant-email"],"summary":"Set up tenant email","description":"Set up email for a tenant by registering their email account.\n\nThis initiates the opt-in verification flow:\n1. Creates a POBox session with the provided credentials\n2. Sends a verification email with a 6-digit code\n3. Sets status to PENDING until verification is complete\n\nThe tenant can then verify by:\n- Calling POST /tenants/{id}/email/verify with the code\n- Or replying to the email with the verification code\n\nArgs:\n    tenant_id: The tenant ID\n    request: Email credentials and provider info\n\nReturns:\n    Setup confirmation with config (verification code excluded)","operationId":"setup_tenant_email_api_tenants__tenant_id__email_setup_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TenantEmailSetupRequest"}}}},"responses":{"201":{"description":"Email setup initiated, verification email sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailSetupResponse"}}}},"400":{"description":"Invalid credentials or setup error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/email/verify":{"post":{"tags":["cbworkflow","tenant-email"],"summary":"Verify tenant email","description":"Verify a tenant's email opt-in with the verification code.\n\nCall this endpoint with the 6-digit code received via email\nto complete the verification process.\n\nArgs:\n    tenant_id: The tenant ID\n    request: Verification code\n\nReturns:\n    Verification result with updated config","operationId":"verify_tenant_email_api_tenants__tenant_id__email_verify_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TenantEmailVerifyRequest"}}}},"responses":{"200":{"description":"Email verified successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__EmailVerifyResponse"}}}},"400":{"description":"Invalid or expired code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/instances":{"get":{"tags":["cbworkflow","instances"],"summary":"List tenant instances","description":"List workflow instances for this tenant with optional filters.","operationId":"list_instances_api_tenants__tenant_id__instances_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"contact_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by contact ID","title":"Contact Id"},"description":"Filter by contact ID"},{"name":"template_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by template ID","title":"Template Id"},"description":"Filter by template ID"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__InstanceStatus"},{"type":"null"}],"description":"Filter by status","title":"Status"},"description":"Filter by status"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tag","title":"Tag"},"description":"Filter by tag"}],"responses":{"200":{"description":"List of workflow instances matching filters","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"},"title":"Response List Instances Api Tenants  Tenant Id  Instances Get"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"post":{"tags":["cbworkflow","instances"],"summary":"Create workflow instance","description":"Create a new workflow instance in this tenant.","operationId":"create_instance_api_tenants__tenant_id__instances_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstanceCreate"}}}},"responses":{"201":{"description":"Instance created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"400":{"description":"Invalid contact or template ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation error in request body"}}}},"/cbworkflow/api/tenants/{tenant_id}/instances/{instance_id}":{"get":{"tags":["cbworkflow","instances"],"summary":"Get instance by ID","description":"Get a workflow instance by ID (validates tenant ownership).","operationId":"get_instance_api_tenants__tenant_id__instances__instance_id__get","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Instance details including state and timeline","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"404":{"description":"Instance or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"delete":{"tags":["cbworkflow","instances"],"summary":"Delete workflow instance","description":"Delete a workflow instance (validates tenant ownership).","operationId":"delete_instance_api_tenants__tenant_id__instances__instance_id__delete","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"204":{"description":"Instance deleted successfully"},"404":{"description":"Instance or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/instances/{instance_id}/notes":{"post":{"tags":["cbworkflow","instances"],"summary":"Add note to instance","description":"Add a note to the instance timeline (validates tenant ownership).","operationId":"add_note_api_tenants__tenant_id__instances__instance_id__notes_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__NoteRequest"}}}},"responses":{"200":{"description":"Note added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"404":{"description":"Instance or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/instances/{instance_id}/pause":{"post":{"tags":["cbworkflow","instances"],"summary":"Pause workflow instance","description":"Pause a workflow instance (validates tenant ownership).","operationId":"pause_instance_api_tenants__tenant_id__instances__instance_id__pause_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Actor initiating the pause","title":"Actor"},"description":"Actor initiating the pause"}],"responses":{"200":{"description":"Instance paused successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"400":{"description":"Cannot pause (wrong status)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"404":{"description":"Instance or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/instances/{instance_id}/resume":{"post":{"tags":["cbworkflow","instances"],"summary":"Resume paused instance","description":"Resume a paused workflow instance (validates tenant ownership).","operationId":"resume_instance_api_tenants__tenant_id__instances__instance_id__resume_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Actor initiating the resume","title":"Actor"},"description":"Actor initiating the resume"}],"responses":{"200":{"description":"Instance resumed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"400":{"description":"Cannot resume (wrong status)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"404":{"description":"Instance or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/instances/{instance_id}/start":{"post":{"tags":["cbworkflow","instances"],"summary":"Start workflow instance","description":"Start a workflow instance (validates tenant ownership).","operationId":"start_instance_api_tenants__tenant_id__instances__instance_id__start_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Actor initiating the start","title":"Actor"},"description":"Actor initiating the start"}],"responses":{"200":{"description":"Instance started successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"400":{"description":"Cannot start (wrong status)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"404":{"description":"Instance or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/instances/{instance_id}/state":{"post":{"tags":["cbworkflow","instances"],"summary":"Update instance state","description":"Update the state of an instance (validates tenant ownership).","operationId":"update_instance_state_api_tenants__tenant_id__instances__instance_id__state_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__StateUpdateRequest"}}}},"responses":{"200":{"description":"State updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"404":{"description":"Instance or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/instances/{instance_id}/timeline":{"get":{"tags":["cbworkflow","instances"],"summary":"Get instance timeline","description":"Get the timeline of events for an instance (validates tenant ownership).","operationId":"get_timeline_api_tenants__tenant_id__instances__instance_id__timeline_get","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"List of events in chronological order","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__Event"},"title":"Response Get Timeline Api Tenants  Tenant Id  Instances  Instance Id  Timeline Get"}}}},"404":{"description":"Instance or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/instances/{instance_id}/transition":{"post":{"tags":["cbworkflow","instances"],"summary":"Manually transition instance","description":"Manually transition an instance based on an outcome (validates tenant ownership).","operationId":"transition_instance_api_tenants__tenant_id__instances__instance_id__transition_post","parameters":[{"name":"instance_id","in":"path","required":true,"schema":{"type":"string","title":"Instance Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TransitionRequest"}}}},"responses":{"200":{"description":"Transition completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowInstance"}}}},"400":{"description":"Invalid transition or outcome","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"404":{"description":"Instance or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/sms":{"get":{"tags":["cbworkflow","tenant-sms"],"summary":"Get SMS configuration","description":"Get SMS configuration for a tenant.\n\nReturns the tenant's SMS config including:\n- Phone number\n- Verification status\n- Message counts\n\nArgs:\n    tenant_id: The tenant ID\n\nReturns:\n    The tenant's SMS configuration","operationId":"get_tenant_sms_api_tenants__tenant_id__sms_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Tenant SMS configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TenantSMSConfigResponse"}}}},"404":{"description":"SMS config not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"delete":{"tags":["cbworkflow","tenant-sms"],"summary":"Delete SMS configuration","description":"Remove SMS configuration for a tenant.\n\nThis will:\n- Delete the tenant's SMS config\n- Prevent further SMS sending/receiving\n- NOT affect routing entries (they will remain until replaced)\n\nArgs:\n    tenant_id: The tenant ID\n\nReturns:\n    Deletion confirmation","operationId":"delete_tenant_sms_api_tenants__tenant_id__sms_delete","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"SMS configuration deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__SMSDeleteResponse"}}}},"404":{"description":"SMS config not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/sms/disable":{"post":{"tags":["cbworkflow","tenant-sms"],"summary":"Disable tenant SMS","description":"Disable SMS for a tenant.\n\nSets the SMS status to DISABLED, preventing sending/receiving\nwithout deleting the configuration.\n\nArgs:\n    tenant_id: The tenant ID\n\nReturns:\n    Updated SMS configuration","operationId":"disable_tenant_sms_api_tenants__tenant_id__sms_disable_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"SMS disabled successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TenantSMSConfigResponse"}}}},"404":{"description":"SMS config not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/sms/resend":{"post":{"tags":["cbworkflow","tenant-sms"],"summary":"Resend verification code","description":"Resend the verification code for a tenant.\n\nGenerates a new verification code and sends it via SMS.\nUse this if the original code expired or wasn't received.\n\nArgs:\n    tenant_id: The tenant ID\n\nReturns:\n    Setup confirmation with updated config","operationId":"resend_verification_api_tenants__tenant_id__sms_resend_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"New verification code sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__SMSSetupResponse"}}}},"400":{"description":"Resend failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/sms/setup":{"post":{"tags":["cbworkflow","tenant-sms"],"summary":"Set up tenant SMS","description":"Set up SMS for a tenant by registering their phone number.\n\nThis initiates the opt-in verification flow:\n1. Registers the phone number for the tenant\n2. Sends a verification code via SMS\n3. Sets status to PENDING until verification is complete\n\nThe tenant can then verify by:\n- Calling POST /tenants/{id}/sms/verify with the code\n- Or replying to the SMS with the verification code\n\nArgs:\n    tenant_id: The tenant ID\n    request: Phone number to register\n\nReturns:\n    Setup confirmation with config (verification code excluded)","operationId":"setup_tenant_sms_api_tenants__tenant_id__sms_setup_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TenantSMSSetupRequest"}}}},"responses":{"201":{"description":"SMS setup initiated, verification code sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__SMSSetupResponse"}}}},"400":{"description":"Invalid phone number or setup error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/sms/verify":{"post":{"tags":["cbworkflow","tenant-sms"],"summary":"Verify tenant SMS","description":"Verify a tenant's SMS opt-in with the verification code.\n\nCall this endpoint with the 6-digit code received via SMS\nto complete the verification process.\n\nArgs:\n    tenant_id: The tenant ID\n    request: Verification code\n\nReturns:\n    Verification result with updated config","operationId":"verify_tenant_sms_api_tenants__tenant_id__sms_verify_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__TenantSMSVerifyRequest"}}}},"responses":{"200":{"description":"SMS verified successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__SMSVerifyResponse"}}}},"400":{"description":"Invalid or expired code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"503":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/workflows":{"get":{"tags":["cbworkflow","workflows"],"summary":"List tenant workflows","description":"List workflow templates for this tenant.","operationId":"list_workflows_api_tenants__tenant_id__workflows_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tag","title":"Tag"},"description":"Filter by tag"}],"responses":{"200":{"description":"List of workflow templates","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplate"},"title":"Response List Workflows Api Tenants  Tenant Id  Workflows Get"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"post":{"tags":["cbworkflow","workflows"],"summary":"Create workflow template","description":"Create a new workflow template in this tenant.","operationId":"create_workflow_api_tenants__tenant_id__workflows_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplateCreate"}}}},"responses":{"201":{"description":"Workflow template created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplate"}}}},"400":{"description":"Invalid template structure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation error in request body"}}}},"/cbworkflow/api/tenants/{tenant_id}/workflows/{template_id}":{"get":{"tags":["cbworkflow","workflows"],"summary":"Get workflow by ID","description":"Get a workflow template by ID (validates tenant ownership).","operationId":"get_workflow_api_tenants__tenant_id__workflows__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Workflow template details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplate"}}}},"404":{"description":"Template or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"patch":{"tags":["cbworkflow","workflows"],"summary":"Update workflow template","description":"Update an existing workflow template (validates tenant ownership).","operationId":"update_workflow_api_tenants__tenant_id__workflows__template_id__patch","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplateUpdate"}}}},"responses":{"200":{"description":"Workflow template updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplate"}}}},"404":{"description":"Template or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation error in request body"}}},"delete":{"tags":["cbworkflow","workflows"],"summary":"Delete workflow template","description":"Delete a workflow template (validates tenant ownership).","operationId":"delete_workflow_api_tenants__tenant_id__workflows__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"204":{"description":"Workflow template deleted successfully"},"404":{"description":"Template or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/workflows/{template_id}/clone":{"post":{"tags":["cbworkflow","workflows"],"summary":"Clone workflow template","description":"Clone a workflow template within this tenant.","operationId":"clone_workflow_api_tenants__tenant_id__workflows__template_id__clone_post","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"},{"name":"new_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Name for the cloned template","title":"New Name"},"description":"Name for the cloned template"},{"name":"new_version","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Version for the cloned template","title":"New Version"},"description":"Version for the cloned template"}],"responses":{"201":{"description":"Workflow template cloned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplate"}}}},"404":{"description":"Source template or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/tenants/{tenant_id}/workflows/{template_id}/validate":{"get":{"tags":["cbworkflow","workflows"],"summary":"Validate workflow graph","description":"Validate a workflow template's graph structure (validates tenant ownership).","operationId":"validate_workflow_api_tenants__tenant_id__workflows__template_id__validate_get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","description":"Tenant ID","title":"Tenant Id"},"description":"Tenant ID"}],"responses":{"200":{"description":"Validation results","content":{"application/json":{"schema":{}}}},"404":{"description":"Template or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/workflows":{"get":{"tags":["cbworkflow","workflows"],"summary":"List all workflow templates","description":"List all workflow templates with optional tag filter.\n\nReturns workflow blueprints that can be instantiated for contacts.","operationId":"list_workflows_api_workflows_get","parameters":[{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by tag","title":"Tag"},"description":"Filter by tag"}],"responses":{"200":{"description":"List of workflow templates","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplate"},"title":"Response List Workflows Api Workflows Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"post":{"tags":["cbworkflow","workflows"],"summary":"Create workflow template","description":"Create a new workflow template.\n\nThe template must include:\n- A `nodes` dict with at least one END node\n- A `start_node` that exists in nodes\n- All transitions pointing to valid node IDs","operationId":"create_workflow_api_workflows_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplateCreate"}}}},"responses":{"201":{"description":"Template created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplate"}}}},"400":{"description":"Invalid template structure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation error in request body"}}}},"/cbworkflow/api/workflows/{template_id}":{"get":{"tags":["cbworkflow","workflows"],"summary":"Get workflow template by ID","description":"Get a workflow template by ID.\n\nReturns the full template including node graph structure,\ntransitions, and metadata.","operationId":"get_workflow_api_workflows__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}}],"responses":{"200":{"description":"Workflow template details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplate"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}},"patch":{"tags":["cbworkflow","workflows"],"summary":"Update workflow template","description":"Update an existing workflow template.\n\nOnly provided fields are updated. If nodes or start_node are changed,\nthe new structure is validated for consistency.\n\nNote: Updating a template does not affect existing instances (they\nuse a frozen copy of the nodes at creation time).","operationId":"update_workflow_api_workflows__template_id__patch","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplateUpdate"}}}},"responses":{"200":{"description":"Template updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplate"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation error in request body"}}},"delete":{"tags":["cbworkflow","workflows"],"summary":"Delete workflow template","description":"Delete a workflow template.\n\nExisting instances created from this template are not affected\n(they have a frozen copy of the nodes).","operationId":"delete_workflow_api_workflows__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}}],"responses":{"204":{"description":"Template deleted successfully"},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/workflows/{template_id}/clone":{"post":{"tags":["cbworkflow","workflows"],"summary":"Clone workflow template","description":"Clone a workflow template to create a new version.\n\nCreates a copy of the template with a new ID. Optionally specify\na new name and/or version for the clone.","operationId":"clone_workflow_api_workflows__template_id__clone_post","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"new_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Name for the cloned template","title":"New Name"},"description":"Name for the cloned template"},{"name":"new_version","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Version for the cloned template","title":"New Version"},"description":"Version for the cloned template"}],"responses":{"201":{"description":"Template cloned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__WorkflowTemplate"}}}},"404":{"description":"Source template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/api/workflows/{template_id}/validate":{"get":{"tags":["cbworkflow","workflows"],"summary":"Validate workflow template graph","description":"Validate a workflow template's graph structure.\n\nChecks for:\n- Unreachable nodes\n- Missing transitions\n- Dead ends (non-END nodes with no outgoing transitions)\n- Cycles and infinite loops\n\nReturns validation status and any issues found.","operationId":"validate_workflow_api_workflows__template_id__validate_get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}}],"responses":{"200":{"description":"Validation results","content":{"application/json":{"schema":{}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HTTPValidationError"}}}}}}},"/cbworkflow/health":{"get":{"tags":["cbworkflow","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HealthResponse"}}}}}}},"/cbworkflow/health/detailed":{"get":{"tags":["cbworkflow","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__HealthResponse"}}}}}}},"/cbworkflow/prime":{"get":{"tags":["cbworkflow","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__PrimeResponse"}}}}}}},"/cbworkflow/status":{"get":{"tags":["cbworkflow","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkflow__StatusResponse"}}}}}}},"/cbworkspaces/api/v1/hello":{"get":{"tags":["cbworkspaces","CBWORKSPACES"],"summary":"Connectivity / greeting probe","description":"Return a static greeting payload. Use this as a lightweight application-level connectivity probe: a `200` with the expected `message` confirms the `/api/v1` router is mounted and the service is reachable through the mesh, beyond what the framework `/health` liveness check covers.\n\nRequires no authentication and takes no parameters. The response shape is a single-key object: `{\"message\": \"Hello from cbworkspaces\"}`.","operationId":"hello_api_v1_hello_get","responses":{"200":{"description":"Greeting returned successfully.","content":{"application/json":{"schema":{},"example":{"message":"Hello from cbworkspaces"}}}}}}},"/cbworkspaces/health":{"get":{"tags":["cbworkspaces","Health"],"summary":"Basic health check","description":"Quick liveness check. Returns status without running component checks.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkspaces__HealthResponse"}}}}}}},"/cbworkspaces/health/detailed":{"get":{"tags":["cbworkspaces","Health"],"summary":"Detailed health check","description":"Full health check including all registered component checks.","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkspaces__HealthResponse"}}}}}}},"/cbworkspaces/prime":{"get":{"tags":["cbworkspaces","Health"],"summary":"Prime application caches","description":"Force initialization of all lazy-loaded components:\n\n- **OpenAPI schema**: Validates all route definitions and Pydantic models\n- **Dependencies**: Resolves the dependency injection graph\n- **Model schemas**: Generates JSON schemas for all request/response models\n\nUse this endpoint:\n- In readiness probes (after liveness passes)\n- Before load balancer adds instance to pool\n- In CI to catch schema errors early\n\nReturns errors array if any component fails to initialize.","operationId":"prime_prime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkspaces__PrimeResponse"}}}}}}},"/cbworkspaces/status":{"get":{"tags":["cbworkspaces","Health"],"summary":"Service status overview","description":"Operational overview of the service including identity, uptime, enabled features, mesh connectivity, and registered health components. Not a health check — use /health for liveness.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/cbworkspaces__StatusResponse"}}}}}}}},"components":{"schemas":{"cbai__AIProvider":{"type":"string","enum":["ollama","claude","mistral","unstructured"],"title":"AIProvider","description":"Available AI providers for routing requests.\n\nEach provider has different capabilities and cost profiles:\n- **ollama**: Local LLM, free, fast, good for most tasks\n- **claude**: Anthropic API, paid, highest quality for complex reasoning\n- **mistral**: Mistral API, paid, specialized for OCR/vision\n- **unstructured**: Document parsing API, specialized for document extraction"},"cbai__AgentChatMessage":{"properties":{"role":{"type":"string","enum":["user","assistant"],"title":"Role","description":"Message author role.","examples":["user","assistant"]},"content":{"anyOf":[{"type":"string"},{"items":{"anyOf":[{"$ref":"#/components/schemas/cbai__TextBlock"},{"$ref":"#/components/schemas/cbai__ToolUseBlock"},{"$ref":"#/components/schemas/cbai__ToolResultBlock"}]},"type":"array"}],"title":"Content","description":"Message content. A plain string for simple user messages, or a list of TextBlock/ToolUseBlock/ToolResultBlock for tool-aware turns."}},"type":"object","required":["role","content"],"title":"AgentChatMessage","description":"A message in an agent-loop chat conversation.\n\nSame shape as ToolChatMessage -- callers can round-trip the messages\nthey receive in the agent endpoint's `done` event without rewriting.","example":{"content":"How many R voters in MI between 18 and 35?","role":"user"}},"cbai__AgentChatRequest":{"properties":{"messages":{"items":{"$ref":"#/components/schemas/cbai__AgentChatMessage"},"type":"array","minItems":1,"title":"Messages","description":"Conversation history. Must contain at least one message."},"tool_catalog":{"type":"string","title":"Tool Catalog","description":"Name of the tool catalog to load. Must match a key in app/data/catalogs.py::CATALOGS.","examples":["cbmodels"]},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Claude model override. Defaults to settings.CLAUDE_MODEL.","examples":["claude-sonnet-4-5-20250929"]},"system":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"System","description":"Optional system prompt to steer the agent.","examples":["You are a voter data assistant."]},"max_tool_calls":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Max Tool Calls","description":"Maximum number of Claude round-trips (iterations) the loop is allowed. The loop terminates with stop_reason='max_iterations_reached' if hit.","default":10,"examples":[5,10,25]},"temperature":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Temperature","description":"Sampling temperature for Claude.","default":1.0,"examples":[0.0,1.0]},"max_tokens":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Max Tokens","description":"Per-turn max output tokens. Defaults to settings.CLAUDE_MAX_TOKENS.","examples":[4096,8192]}},"additionalProperties":false,"type":"object","required":["messages","tool_catalog"],"title":"AgentChatRequest","description":"Request body for POST /api/v1/chat/agent.","example":{"max_tool_calls":10,"messages":[{"content":"How many R voters in MI between 18 and 35?","role":"user"}],"system":"You are a voter data assistant.","tool_catalog":"cbmodels"}},"cbai__BBox":{"properties":{"bbox":{"prefixItems":[{"type":"integer"},{"type":"integer"},{"type":"integer"},{"type":"integer"}],"type":"array","maxItems":4,"minItems":4,"title":"Bbox","description":"Pixel offsets of the region within the input image as (x0, y0, x1, y1) -- top-left corner followed by bottom-right corner.","examples":[[120,40,360,120]]},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence","description":"Claude's confidence that this region is the requested target, in [0.0, 1.0]. Higher is more confident; boxes are returned ranked by this value.","examples":[0.93,0.5]},"kind":{"type":"string","title":"Kind","description":"Hint for the role of the located region: 'primary' (the main logo on the page), 'icon' (favicon/square mark), 'mobile' (stripped/small variant), or 'other'.","default":"other","examples":["primary","icon","mobile","other"]}},"type":"object","required":["bbox","confidence"],"title":"BBox","description":"A single bounding box returned by /api/v1/imagelocate.\n\nCoordinates are pixel offsets within the input image. ``bbox`` is\n``(x0, y0, x1, y1)`` -- top-left + bottom-right. ``kind`` is a hint\nClaude assigns based on visual context (size, placement).","example":{"bbox":[120,40,360,120],"confidence":0.93,"kind":"primary"}},"cbai__CacheStats":{"properties":{"creation_tokens":{"type":"integer","title":"Creation Tokens","description":"Tokens written to the cache","default":0},"read_tokens":{"type":"integer","title":"Read Tokens","description":"Tokens served from the cache","default":0},"hit_rate":{"type":"number","title":"Hit Rate","description":"Fraction of prompt tokens served from cache: cache_read / (input + cache_creation + cache_read). 0.0 when there were no prompt tokens at all.","default":0.0}},"type":"object","title":"CacheStats","description":"Prompt-cache statistics for a period."},"cbai__ChatMessage":{"properties":{"role":{"type":"string","enum":["user","assistant","system"],"title":"Role","description":"The role of the message author. 'user' for human input, 'assistant' for AI responses, 'system' for instructions/context that guide the AI's behavior.","examples":["user","assistant","system"]},"content":{"type":"string","title":"Content","description":"The text content of the message.","examples":["What is the capital of France?"]},"images":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Images","description":"Base64-encoded images attached to this message (vision). Ollama provider only -- pick a vision-capable model (gemma4:26b, qwen3.5:9b, minicpm-v, gemma3:4b). Raw base64, no data: URI prefix. Requests to the Claude provider with images are rejected (route Claude vision via /imagelocate)."},"documents":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Documents","description":"Base64-encoded PDF documents attached to this message. Claude provider only -- sent as native document content blocks so the model sees the rendered pages, not just extracted text. Raw base64, no data: URI prefix. Requests to other providers with documents are rejected."}},"type":"object","required":["role","content"],"title":"ChatMessage","description":"A single message in a chat conversation.\n\nMessages form the conversation history sent to the AI model.\nThe AI uses the full message history to generate contextual responses.","examples":[{"content":"What is the capital of France?","role":"user"},{"content":"The capital of France is Paris.","role":"assistant"},{"content":"You are a helpful geography assistant.","role":"system"}]},"cbai__ChatRequest":{"properties":{"messages":{"items":{"$ref":"#/components/schemas/cbai__ChatMessage"},"type":"array","minItems":1,"title":"Messages","description":"The conversation messages to send. Must include at least one message. Messages are processed in order - the AI sees the full conversation history. Include system messages first to set context/instructions.","examples":[[{"content":"Summarize this article in 3 bullet points.","role":"user"}]]},"system":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"System","description":"System prompt providing context and instructions to the AI. For Claude, this is sent as the top-level system parameter. For Ollama, this is prepended as a system message.","examples":["You are a helpful coding assistant. Be concise."]},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Override the default model for this request. If not specified, uses the provider's configured default. For Ollama: 'mistral-small3.2:latest', 'qwen3:8b', etc. For Claude: 'claude-sonnet-4-5-20250929', 'claude-opus-4', etc.","examples":["mistral-small3.2:latest","claude-sonnet-4-5-20250929"]},"max_tokens":{"anyOf":[{"type":"integer","maximum":100000.0,"minimum":1.0},{"type":"null"}],"title":"Max Tokens","description":"Maximum tokens in the response. If not set, uses provider default. 1 token ≈ 4 characters. Use lower values for concise responses, higher for detailed analysis. Claude default: 4096.","examples":[100,500,2000,4096]},"temperature":{"type":"number","maximum":2.0,"minimum":0.0,"title":"Temperature","description":"Sampling temperature controlling randomness (0.0-2.0). Lower values (0.0-0.3): More focused, deterministic, factual. Medium values (0.5-0.8): Balanced creativity and coherence. Higher values (1.0-2.0): More creative, varied, potentially less coherent.","default":0.7,"examples":[0.0,0.3,0.7,1.0]},"stream":{"type":"boolean","title":"Stream","description":"Whether to stream the response as chunks. When true, response is returned as Server-Sent Events (SSE). When false, waits for complete response before returning.","default":false},"format":{"anyOf":[{"type":"string","const":"json"},{"type":"null"}],"title":"Format","description":"Constrain the output format. 'json' forces valid-JSON output (Ollama structured mode). Ollama provider only, non-streaming."},"timeout":{"anyOf":[{"type":"number","maximum":1800.0,"minimum":1.0},{"type":"null"}],"title":"Timeout","description":"Ollama read-timeout override in seconds for this call. Ollama provider only. When unset, slow vision calls (messages carrying images) use the vision default and other calls use the base timeout. Raise this for long local vision/reasoning inference.","examples":[300,600,900]}},"additionalProperties":false,"type":"object","required":["messages"],"title":"ChatRequest","description":"Request to chat with an AI provider.\n\nSend a conversation history to an AI model and receive a response.\nSupports both streaming and non-streaming responses.\n\n### Provider Selection\n- **ollama** (default): Fast, free, local inference. Best for most tasks.\n- **claude**: Higher quality, better reasoning. Use for complex analysis.\n\n### When to Use Streaming\nEnable `stream=true` for long responses or real-time display.\nStreaming returns chunks as they're generated rather than waiting for completion.","examples":[{"max_tokens":500,"messages":[{"content":"What is machine learning?","role":"user"}],"temperature":0.7},{"messages":[{"content":"You are a Python expert. Be concise.","role":"system"},{"content":"How do I read a JSON file?","role":"user"}],"temperature":0.3}]},"cbai__ChatResponse":{"properties":{"content":{"type":"string","title":"Content","description":"The generated response text from the AI model.","examples":["The capital of France is Paris."]},"model":{"type":"string","title":"Model","description":"The model identifier that generated this response.","examples":["mistral-small3.2:latest","claude-sonnet-4-5-20250929"]},"usage":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Usage","description":"Token usage statistics (Claude only). Contains: 'input_tokens' (prompt cost), 'output_tokens' (response cost). Useful for cost tracking: total_cost = (input_tokens * $3 + output_tokens * $15) / 1M.","examples":[{"input_tokens":50,"output_tokens":150}]},"finish_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finish Reason","description":"Why generation stopped. Values: 'stop' = natural completion, 'length' = hit max_tokens limit, 'content_filter' = blocked by safety filter.","examples":["stop","length"]}},"type":"object","required":["content","model"],"title":"ChatResponse","description":"Response from a chat request.\n\nContains the AI-generated response and metadata about the generation.\n\n### Interpreting the Response\n- **content**: The main AI response text\n- **model**: Which model generated this (useful for debugging)\n- **usage**: Token counts (only available for Claude, helps track costs)\n- **finish_reason**: Why generation stopped ('stop' = natural end, 'length' = hit max_tokens)","example":{"content":"Machine learning is a subset of artificial intelligence...","finish_reason":"stop","model":"mistral-small3.2:latest","usage":{"input_tokens":25,"output_tokens":180}}},"cbai__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbai__CostStats":{"properties":{"total_usd":{"type":"number","title":"Total Usd","description":"Total billable USD across the period","default":0.0},"saved_by_cache_usd":{"type":"number","title":"Saved By Cache Usd","description":"USD savings from cached reads — what cache_read tokens would have cost at standard input rates minus what they actually cost","default":0.0}},"type":"object","title":"CostStats","description":"USD cost statistics for a period (cbai#81).\n\nAll values rounded to 6 decimal places. Zero for local providers\n(ollama) and for unknown (provider, model) pairs not present in\nthe price table."},"cbai__EmbeddingRequest":{"properties":{"text":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Text","description":"Text or list of texts to embed. Each text is converted to a vector. For batch processing, pass a list. Maximum recommended: 100 texts per request. Long texts (>8000 tokens) may be truncated by the model.","examples":["What is the meaning of life?",["First document to embed","Second document","Third document"]]},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Override the default embedding model. Default: 'nomic-embed-text' (768 dims, English-optimized). Alternative: 'nomic-embed-text-v2-moe' (multilingual).","examples":["nomic-embed-text","nomic-embed-text-v2-moe"]}},"additionalProperties":false,"type":"object","required":["text"],"title":"EmbeddingRequest","description":"Request to generate vector embeddings for text.\n\nEmbeddings convert text into numerical vectors that capture semantic meaning.\nSimilar texts have similar embeddings (high cosine similarity).\n\n### Use Cases\n- **Semantic Search**: Find documents similar to a query\n- **Clustering**: Group similar documents together\n- **RAG Pipelines**: Retrieve relevant context for AI responses\n- **Duplicate Detection**: Find near-duplicate content\n\n### Vector Dimensions\nDefault model (nomic-embed-text) produces 768-dimensional vectors,\ncompatible with pgvector and most vector databases.","examples":[{"text":"What is machine learning?"},{"model":"nomic-embed-text","text":["Document 1 content","Document 2 content"]}]},"cbai__EmbeddingResponse":{"properties":{"embeddings":{"items":{"items":{"type":"number"},"type":"array"},"type":"array","title":"Embeddings","description":"List of embedding vectors. One vector per input text. Each vector is a list of floats with length equal to 'dimensions'. Vectors are L2-normalized (unit length) for cosine similarity."},"model":{"type":"string","title":"Model","description":"The embedding model used.","examples":["nomic-embed-text"]},"dimensions":{"type":"integer","title":"Dimensions","description":"Dimensionality of the embedding vectors. nomic-embed-text: 768 dims. Higher dimensions = more semantic detail.","examples":[768,384,1536]}},"type":"object","required":["embeddings","model","dimensions"],"title":"EmbeddingResponse","description":"Response containing embedding vectors.\n\n### Using Embeddings\n1. Store vectors in a vector database (pgvector, Pinecone, etc.)\n2. For search: embed the query, find nearest neighbors\n3. Cosine similarity > 0.8 typically indicates high relevance\n\n### Vector Format\nEach embedding is a list of floats. The list length equals `dimensions`.","example":{"dimensions":768,"embeddings":[[0.123,-0.456,0.789,0.234,-0.567]],"model":"nomic-embed-text"}},"cbai__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbai__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbai__HealthDetailedResponse":{"properties":{"ollama":{"$ref":"#/components/schemas/cbai__ProviderHealthDetail"},"claude":{"$ref":"#/components/schemas/cbai__ProviderHealthDetail"},"mistral":{"$ref":"#/components/schemas/cbai__ProviderHealthDetail"},"unstructured":{"$ref":"#/components/schemas/cbai__ProviderHealthDetail"},"embed":{"$ref":"#/components/schemas/cbai__ProviderHealthDetail"}},"type":"object","required":["ollama","claude","mistral","unstructured","embed"],"title":"HealthDetailedResponse","description":"Per-provider detailed health snapshot (cbai#93). The plain\n`/api/v1/health` endpoint stays bool-only for backwards compat;\noperators + alerting probe this endpoint for the rich state.","example":{"claude":{"active_key_slot":"primary","available":false,"consecutive_failures":2316,"has_alt":true,"next_check_at":"2026-05-08T21:53:36+00:00","reason":"credit_low","since":"2026-05-08T19:53:36+00:00"},"embed":{"available":true},"mistral":{"available":true},"ollama":{"available":true},"unstructured":{"available":true}}},"cbai__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbai__ImageLocateRequest":{"properties":{"image_b64":{"type":"string","title":"Image B64","description":"Base64-encoded PNG/JPEG/WebP. No data URI prefix."},"media_type":{"type":"string","title":"Media Type","description":"MIME type of the supplied image bytes.","default":"image/png"},"target":{"type":"string","title":"Target","description":"What to locate. V2.0 supports 'logo'. Forward-compatible -- future targets (e.g., 'hero_image', 'cta_button') ride the same endpoint shape.","default":"logo"}},"additionalProperties":false,"type":"object","required":["image_b64"],"title":"ImageLocateRequest","description":"Request for /api/v1/imagelocate (cbloom#102 phase B).\n\nLocate a target (currently always 'logo') in a PNG screenshot via\nClaude Vision. Returns zero or more bboxes with confidence + role\nhint so cbloom's brand pipeline can hand them to cbintel /crop."},"cbai__ImageLocateResponse":{"properties":{"bboxes":{"items":{"$ref":"#/components/schemas/cbai__BBox"},"type":"array","title":"Bboxes","description":"Zero or more located regions, ranked by confidence. Empty list means Claude could not identify the target with any confidence -- caller should surface 'no logo detected' to UX."},"image_width":{"type":"integer","title":"Image Width","description":"Width of the input image in pixels.","examples":[1280]},"image_height":{"type":"integer","title":"Image Height","description":"Height of the input image in pixels.","examples":[720]},"model":{"type":"string","title":"Model","description":"The Claude Vision model that produced these bboxes.","examples":["claude-sonnet-4-5-20250929"]},"usage":{"additionalProperties":{"type":"integer"},"type":"object","title":"Usage","description":"Claude token usage for the vision call (input_tokens / output_tokens). Empty if not reported.","examples":[{"input_tokens":1450,"output_tokens":80}]}},"type":"object","required":["image_width","image_height","model"],"title":"ImageLocateResponse","description":"Response from /api/v1/imagelocate.\n\nCarries the ranked bounding boxes plus the input image dimensions\n(so callers can convert to relative coordinates) and Claude usage\nmetadata for cost tracking.","example":{"bboxes":[{"bbox":[120,40,360,120],"confidence":0.93,"kind":"primary"}],"image_height":720,"image_width":1280,"model":"claude-sonnet-4-5-20250929","usage":{"input_tokens":1450,"output_tokens":80}}},"cbai__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbai__ModelCard":{"properties":{"id":{"type":"string","title":"Id","description":"Ollama model tag for pulling/running. Use this exact string with `ollama run <id>` or in API calls.","examples":["deepseek-r1:8b","qwen2.5-coder:7b","minicpm-v:latest"]},"installed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Installed","description":"Whether this model is actually installed on the fleet ollama, read live from its /api/tags. **null means unknown** (ollama could not be reached) — not absent. Check this before selecting a model: the catalogue is a menu, not an inventory (cbai#115)."},"size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size Bytes","description":"On-disk size reported by ollama, when installed. Compare against available VRAM before choosing: a model larger than a single card will fall back to CPU and run one to two orders of magnitude slower."},"name":{"type":"string","title":"Name","description":"Human-readable model name.","examples":["DeepSeek-R1 8B","Qwen2.5-Coder 7B"]},"category":{"$ref":"#/components/schemas/cbai__ModelCategory","description":"Primary capability category. Models may have secondary capabilities not reflected here."},"size":{"type":"string","title":"Size","description":"Total parameter count. Format: '<number>B' for billions, '<number>M' for millions. Larger models generally perform better but require more resources.","examples":["8B","32B","137M","1.5B"]},"active_params":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Active Params","description":"Active parameters for MoE (Mixture of Experts) models. MoE models only activate a subset of parameters per token, making them faster than their total size suggests. None for dense (non-MoE) models.","examples":["3B","22B",null]},"context_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Context Length","description":"Maximum context window in tokens. Larger context = more conversation history or document content. 1K tokens ≈ 750 words.","examples":[32768,128000,256000]},"capabilities":{"items":{"type":"string"},"type":"array","title":"Capabilities","description":"List of specific capabilities and strengths. Use these to match model to task requirements.","examples":[["chain-of-thought","math","logic","complex-reasoning"],["code-generation","code-repair","40+-languages"]]},"value_rating":{"type":"integer","maximum":5.0,"minimum":1.0,"title":"Value Rating","description":"Cost-effectiveness rating from 1-5. 5 = exceptional value (small but capable), 1 = expensive/large (only for max performance needs). Prefer higher ratings when capabilities meet requirements."},"size_tier":{"type":"string","title":"Size Tier","description":"Resource tier for hardware planning. 'tiny' (<3B): edge/mobile, 4-8GB VRAM. 'small' (3-8B): consumer GPU, 8-16GB VRAM. 'medium' (14-32B): enthusiast, 24-32GB VRAM. 'large' (>32B): datacenter, 48GB+ VRAM or multi-GPU.","examples":["tiny","small","medium","large"]},"recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommendation","description":"When to use this model. Helps AI systems decide if this model fits their use case.","examples":["Best small reasoning model for limited hardware","Exceptional value for vision tasks, beats GPT-4o mini"]},"benchmarks":{"anyOf":[{"additionalProperties":{"anyOf":[{"type":"number"},{"type":"string"}]},"type":"object"},{"type":"null"}],"title":"Benchmarks","description":"Benchmark scores where available. Keys are benchmark names, values are scores or percentages.","examples":[{"BFCL":"leader","SWE-bench":20.8},{"LiveCodeBench":60.6,"MATH":96.2}]}},"type":"object","required":["id","name","category","size","value_rating","size_tier"],"title":"ModelCard","description":"Information about an available Ollama model.\n\nProvides normalized metadata to help AI systems select the optimal model\nfor a given task based on capabilities, size, and value rating.\n\n### Selection Criteria\n- **value_rating**: Higher is better cost/performance ratio (1-5)\n- **size_tier**: Indicates resource requirements\n- **capabilities**: What the model excels at","example":{"capabilities":["chain-of-thought","math","logic","reasoning"],"category":"thinking","id":"deepseek-r1:8b","name":"DeepSeek-R1 8B","recommendation":"Best small reasoning model, distilled from 671B","size":"8B","size_tier":"small","value_rating":5}},"cbai__ModelCardsResponse":{"properties":{"availability":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Availability","description":"Summary of the live availability check against the fleet ollama (cbai#115). `checked=false` means ollama was unreachable and every card's `installed` is null — unknown, not absent. `catalogued_but_missing` lists models this catalogue advertises that are NOT installed; asking for one returns 'model not found'."},"version":{"type":"string","title":"Version","description":"Schema version for compatibility checking.","examples":["1.0.0"]},"last_updated":{"type":"string","title":"Last Updated","description":"ISO 8601 date when model catalog was last updated.","examples":["2025-12-01"]},"categories":{"additionalProperties":{"type":"string"},"type":"object","title":"Categories","description":"Category descriptions for reference."},"size_tiers":{"additionalProperties":{"type":"string"},"type":"object","title":"Size Tiers","description":"Size tier descriptions with VRAM requirements."},"models":{"items":{"$ref":"#/components/schemas/cbai__ModelCard"},"type":"array","title":"Models","description":"All available model cards, sorted by category then value_rating."},"recommendations":{"additionalProperties":{"type":"string"},"type":"object","title":"Recommendations","description":"Quick recommendations for common scenarios. Keys are use cases, values are recommended model IDs."}},"type":"object","required":["version","last_updated","categories","size_tiers","models","recommendations"],"title":"ModelCardsResponse","description":"Response containing all available model cards.\n\nProvides a comprehensive catalog of Ollama models organized by category\nto help AI systems select the optimal model for any task.\n\n### Usage\n1. Filter by `category` for your task type\n2. Sort by `value_rating` (descending) for best cost/performance\n3. Filter by `size_tier` based on available hardware\n4. Check `capabilities` for specific requirements\n\n### Decision Flow\n```\nTask → Category → Filter by size_tier → Sort by value_rating → Check capabilities\n```","example":{"categories":{"thinking":"Chain-of-thought, math, logic, complex problem solving"},"last_updated":"2025-12-01","models":[{"category":"thinking","id":"deepseek-r1:8b","name":"DeepSeek-R1 8B","size":"8B","size_tier":"small","value_rating":5}],"recommendations":{"8gb_vram":"gemma3:4b","reasoning":"deepseek-r1:8b"},"size_tiers":{"tiny":"<3B active params, 4-8GB VRAM"},"version":"1.0.0"}},"cbai__ModelCategory":{"type":"string","enum":["thinking","coding","vision","embedding","tools","general"],"title":"ModelCategory","description":"Categories of AI model capabilities.\n\nUsed to classify Ollama models by their primary function."},"cbai__OCRRequest":{"properties":{"document_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Url","description":"Public URL to the document. Must be directly accessible (no auth). Supported: PDF, PNG, JPEG, WebP. Max file size: 20MB.","examples":["https://example.com/document.pdf"]},"document_base64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Base64","description":"Base64-encoded document content. Use when URL not available. Include the full base64 string without data URI prefix."},"include_images":{"type":"boolean","title":"Include Images","description":"Whether to extract images from the document as base64. Set to false to reduce response size if images aren't needed.","default":true},"provider":{"anyOf":[{"type":"string","enum":["mistral","ollama"]},{"type":"null"}],"title":"Provider","description":"Force an OCR provider for this request. Omit to use the server default (OCR_PROVIDER, normally 'mistral'). 'ollama' runs the local DC-0 vision models: no per-page charge, but materially different output quality, and slower (~50s/page). Responses not produced by the server's default provider carry `metadata.degraded=true`. Note that a local result is memoised under its own cache key, so it is never replayed as vendor output.","examples":["mistral","ollama"]},"accept_seeded":{"type":"boolean","title":"Accept Seeded","description":"Whether a SEEDED memo entry may answer this request (cbai#130). Seeded entries were loaded from stored text rather than read by the provider, and a response served from one carries `metadata.seeded`. Not all of them are provider OCR: some are an older pipeline's chat-model transcriptions of the image. Set false to refuse them: the request goes to the provider, and a successful read replaces the seeded entry for every later caller. An entry the provider actually produced is still served. Only applies to `document_base64` requests; a URL request never uses the memo.","default":true,"examples":[false]},"fresh_read":{"type":"boolean","title":"Fresh Read","description":"Skip the memo and read from the provider even when an entry exists (cbai#130). A successful provider read replaces the stored entry, so later requests get it too. Check `metadata.cache_replaced` on the response: false means the entry was NOT replaced (for example the local fallback served instead of the vendor) and later requests still receive the old entry. Costs a provider call every time: use it to repair a known-bad entry, not on every request. Only applies to `document_base64` requests.","default":false,"examples":[true]},"allow_fallback":{"type":"boolean","title":"Allow Fallback","description":"Whether THIS request may be served by the local vision-model fallback when Mistral definitively refuses (a 402, or its cooldown). Default true: the server's fallback policy applies, and a fallback answer carries `metadata.degraded=true`. Set false when only provider OCR is acceptable, for example rate cards: the vendor's refusal then comes back as a 402 naming `allow_fallback=false`, never a degraded 200. Note that `provider: \"mistral\"` alone does NOT prevent the fallback. Has no effect when `provider` is 'ollama', and does not change the fleet-wide default.","default":true,"examples":[false]}},"additionalProperties":false,"type":"object","title":"OCRRequest","description":"Request for OCR processing using Mistral's vision model.\n\nExtracts text and images from PDFs and image files.\nProvide either a URL or base64-encoded document (not both).\n\n### Supported Formats\n- PDF documents (best results)\n- Images: PNG, JPEG, WebP, GIF\n- Scanned documents with text\n\n### When to Use Mistral vs Unstructured\n- **Mistral OCR**: Fast, good for PDFs, extracts images\n- **Unstructured**: Better for office documents and element-level\n  structure (titles, lists, captions). NOT for tabular/grid source --\n  measured twice to flatten grids into one run and damage cells; use\n  Mistral for anything that looks like a table. See the warning on\n  `/api/v1/ocr/unstructured`.","examples":[{"document_url":"https://example.com/invoice.pdf","include_images":true},{"document_base64":"JVBERi0xLjQK...","include_images":false}]},"cbai__OCRResponse":{"properties":{"text":{"type":"string","title":"Text","description":"Extracted text content from the document. Text is extracted in reading order (top-to-bottom, left-to-right). May include OCR artifacts for scanned documents."},"images":{"items":{"type":"string"},"type":"array","title":"Images","description":"Base64-encoded images extracted from the document. Only populated if include_images=true. Images are in their original format (PNG, JPEG, etc.)."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Processing metadata including: 'model' (OCR model used), 'pages' (page count for PDFs). When the answer came from the OCR memo rather than the provider: 'cache_hit' (true) and 'cached_at' (when the entry was stored, UTC ISO-8601), plus 'seeded' if the entry was loaded from stored text rather than read by the provider. When the request refused a memo entry (accept_seeded=false or fresh_read=true): 'cache_bypassed' ('seeded' or 'fresh_read') and 'cache_replaced' (true when later requests for these bytes now receive this read; false when the refused entry is still what they receive).","examples":[{"model":"mistral-ocr-latest","pages":5}]}},"type":"object","required":["text"],"title":"OCRResponse","description":"Response from OCR processing.\n\n### Interpreting Results\n- **text**: Full extracted text, preserving reading order\n- **images**: Base64-encoded images found in document (if requested)\n- **metadata**: Processing info including page count, model used","example":{"images":[],"metadata":{"model":"mistral-ocr-latest","pages":1},"text":"Invoice #12345\nDate: 2024-01-15\nTotal: $500.00"}},"cbai__OllamaLoadedModel":{"properties":{"name":{"type":"string","title":"Name","description":"Model tag, e.g. gemma3:4b."},"size_bytes":{"type":"integer","title":"Size Bytes","description":"Total resident size of the loaded model."},"vram_bytes":{"type":"integer","title":"Vram Bytes","description":"Bytes of it that landed in GPU memory."},"gpu_fraction":{"type":"number","title":"Gpu Fraction","description":"vram_bytes / size_bytes. Below 0.5 the model is effectively on CPU."},"processor":{"type":"string","title":"Processor","description":"Same column `ollama ps` prints: '100% GPU', '97%/3% CPU/GPU', ..."}},"type":"object","required":["name","size_bytes","vram_bytes","gpu_fraction","processor"],"title":"OllamaLoadedModel","description":"One row of `ollama ps` as seen from cbai (cbai#119)."},"cbai__OperationStats":{"properties":{"requests":{"type":"integer","title":"Requests","description":"Number of requests","default":0},"tokens":{"$ref":"#/components/schemas/cbai__TokenStats","description":"Token usage"},"cache":{"$ref":"#/components/schemas/cbai__CacheStats","description":"Prompt-cache usage and hit rate (Anthropic only)"},"cost":{"$ref":"#/components/schemas/cbai__CostStats","description":"USD cost and cache savings (cbai#81)"},"latency_avg_ms":{"type":"number","title":"Latency Avg Ms","description":"Average latency","default":0.0}},"type":"object","title":"OperationStats","description":"Stats for a single operation type."},"cbai__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbai__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbai__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbai__ProviderHealthDetail":{"properties":{"available":{"type":"boolean","title":"Available","description":"True iff the provider is currently usable."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Failure-class identifier when `available=false`. Stable strings: credit_low, invalid_api_key, permission_denied, rate_limit, bad_request, transient_5xx, network, http_<code>, api_error, unknown."},"since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since","description":"ISO-8601 timestamp of the first failure in the current streak."},"consecutive_failures":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Consecutive Failures","description":"Number of consecutive failures of `reason`."},"active_key_slot":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Active Key Slot","description":"Which key slot is in use: 'primary' or 'alt' (Claude only)."},"has_alt":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Alt","description":"True iff an ALT key is configured (Claude only)."},"next_check_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Check At","description":"ISO-8601 timestamp at which the health-check loop will resume live probes (Claude only; only set during backoff)."},"degraded":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Degraded","description":"Ollama only. True when the daemon answers but a normal caller budget will not survive it: a loaded model is mostly on CPU (cpu_offload) or the chat in-flight cap is full (saturated). `reason` says which. `available` alone stayed true through the 2026-08-31 outage."},"inflight":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Inflight","description":"Ollama only. Chat requests this process currently has queued on ollama."},"max_inflight":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Inflight","description":"Ollama only. OLLAMA_CHAT_MAX_INFLIGHT -- the (N+1)th chat is refused with 503 + Retry-After instead of queued. 0 = no cap."},"loaded_models":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbai__OllamaLoadedModel"},"type":"array"},{"type":"null"}],"title":"Loaded Models","description":"Ollama only. `ollama ps` snapshot (cached 10s)."}},"type":"object","required":["available"],"title":"ProviderHealthDetail","description":"Rich health state for a single provider (cbai#93).\n\nAll fields beyond `available` are optional -- only Claude reports the\nfull resilience state today; other providers populate just `available`."},"cbai__ProviderStats":{"properties":{"requests":{"type":"integer","title":"Requests","description":"Number of requests","default":0},"tokens":{"$ref":"#/components/schemas/cbai__TokenStats","description":"Token usage"},"cache":{"$ref":"#/components/schemas/cbai__CacheStats","description":"Prompt-cache usage and hit rate (Anthropic only)"},"cost":{"$ref":"#/components/schemas/cbai__CostStats","description":"USD cost and cache savings (cbai#81)"},"latency_avg_ms":{"type":"number","title":"Latency Avg Ms","description":"Average latency","default":0.0},"success_rate":{"type":"number","title":"Success Rate","description":"Success rate","default":1.0}},"type":"object","title":"ProviderStats","description":"Stats for a single provider."},"cbai__SeriesPoint":{"properties":{"bucket":{"type":"string","title":"Bucket","description":"Bucket label: 'YYYY-MM-DD' for day interval, 'YYYY-MM-DD HH:00' for hour interval (UTC)."},"requests":{"type":"integer","title":"Requests","description":"Requests in this bucket","default":0},"tokens_total":{"type":"integer","title":"Tokens Total","description":"Total tokens in this bucket","default":0},"cost_usd":{"type":"number","title":"Cost Usd","description":"Provider USD cost in this bucket","default":0.0},"cost_saved_usd":{"type":"number","title":"Cost Saved Usd","description":"USD saved by prompt cache in this bucket","default":0.0},"errors":{"type":"integer","title":"Errors","description":"Failed requests in this bucket","default":0},"success_rate":{"type":"number","title":"Success Rate","description":"Success rate (0.0-1.0)","default":1.0}},"type":"object","required":["bucket"],"title":"SeriesPoint","description":"One time-bucket of usage for trend charts."},"cbai__SpanRecord":{"properties":{"trace_id":{"type":"string","title":"Trace Id","description":"32-char hex trace ID this span belongs to.","examples":["4bf92f3577b34da6a3ce929d0e0e4736"]},"span_id":{"type":"string","title":"Span Id","description":"16-char hex span ID.","examples":["00f067aa0ba902b7"]},"parent_span_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Span Id","description":"Parent span ID, or None for the root span.","examples":["00f067aa0ba902b7",null]},"name":{"type":"string","title":"Name","description":"Span name (e.g. 'invoke_agent', 'chat', 'execute_tool get_voter').","examples":["invoke_agent","chat"]},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind","description":"OTel SpanKind name (INTERNAL, SERVER, CLIENT, ...).","examples":["INTERNAL"]},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"Span start time.","examples":["2026-06-20T12:00:00Z"]},"ended_at":{"type":"string","format":"date-time","title":"Ended At","description":"Span end time.","examples":["2026-06-20T12:00:01.842Z"]},"duration_ms":{"type":"number","title":"Duration Ms","description":"Span duration in milliseconds.","examples":[1842.7]},"status_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Code","description":"OTel StatusCode name (OK / ERROR / UNSET).","examples":["OK","ERROR"]},"status_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Message","description":"OTel status description, typically set on errors."},"attributes":{"additionalProperties":true,"type":"object","title":"Attributes","description":"Captured span attributes (e.g. gen_ai.* and cbai.* keys). Keys vary by span name."},"events":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Events","description":"Captured span events (timestamped sub-records)."}},"type":"object","required":["trace_id","span_id","name","started_at","ended_at","duration_ms"],"title":"SpanRecord","description":"One row from the spans table, normalized for API consumption.\n\nMirrors an OpenTelemetry span: identity (`trace_id` / `span_id` /\n`parent_span_id`), timing, OTel status, and the captured attribute /\nevent payloads.","example":{"attributes":{"gen_ai.system":"anthropic"},"duration_ms":1842.7,"ended_at":"2026-06-20T12:00:01.842Z","events":[],"kind":"INTERNAL","name":"invoke_agent","span_id":"00f067aa0ba902b7","started_at":"2026-06-20T12:00:00Z","status_code":"OK","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}},"cbai__SpanTree":{"properties":{"span":{"$ref":"#/components/schemas/cbai__SpanRecord","description":"This node's span."},"children":{"items":{"$ref":"#/components/schemas/cbai__SpanTree"},"type":"array","title":"Children","description":"Child spans nested under this span, in start-time order."}},"type":"object","required":["span"],"title":"SpanTree","description":"A span and its (recursive) children — the shape returned by\n/api/v1/traces/{trace_id}.\n\nDefining `children` as a list of `SpanTree` requires the model to\nrefer to itself; pydantic v2 handles forward refs after model\nrebuild below.","example":{"children":[],"span":{"duration_ms":1842.7,"ended_at":"2026-06-20T12:00:01.842Z","name":"invoke_agent","span_id":"00f067aa0ba902b7","started_at":"2026-06-20T12:00:00Z","status_code":"OK","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}},"cbai__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbai__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbai__SummarizeRequest":{"properties":{"content":{"type":"string","minLength":10,"title":"Content","description":"The content to summarize. Can be any text: articles, documents, transcripts, etc. No maximum length, but very long content may be truncated by the model.","examples":["The quick brown fox jumped over the lazy dog..."]},"max_length":{"type":"integer","maximum":1000.0,"minimum":10.0,"title":"Max Length","description":"Target maximum summary length in words. Actual length may vary slightly. Use 50-100 for brief summaries, 200-500 for detailed summaries.","default":200,"examples":[50,100,200,500]},"use_claude":{"type":"boolean","title":"Use Claude","description":"Use Claude for higher-quality summarization. Claude produces more coherent, nuanced summaries but costs money. Falls back to Ollama if Claude API key not configured.","default":false},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Override the underlying model for the Ollama path (e.g. 'gemma3:1b', 'mistral-small3.2:latest'). Ignored when use_claude=true.","examples":["gemma3:1b","phi4:14b"]}},"additionalProperties":false,"type":"object","required":["content"],"title":"SummarizeRequest","description":"Request to summarize text content.\n\nGenerates a concise summary of the provided content.\nChoose between fast local summarization (Ollama) or\nhigher-quality cloud summarization (Claude).\n\n### Provider Selection\n- **use_claude=false** (default): Fast, free, local via Ollama\n- **use_claude=true**: Higher quality, better coherence, costs money","examples":[{"content":"Long article text here...","max_length":100,"use_claude":false},{"content":"Complex document...","max_length":200,"use_claude":true},{"content":"Article...","max_length":50,"model":"gemma3:1b"}]},"cbai__SummarizeResponse":{"properties":{"summary":{"type":"string","title":"Summary","description":"The generated summary text.","examples":["This article discusses the impact of AI on healthcare..."]},"provider":{"type":"string","title":"Provider","description":"Which provider generated the summary ('ollama' or 'claude').","examples":["ollama","claude"]}},"type":"object","required":["summary","provider"],"title":"SummarizeResponse","description":"Response containing the generated summary.","example":{"provider":"ollama","summary":"The article examines three key trends in renewable energy..."}},"cbai__TextBlock":{"properties":{"type":{"type":"string","const":"text","title":"Type","description":"Block type identifier.","default":"text"},"text":{"type":"string","title":"Text","description":"The text content.","examples":["I'll check the weather for you."]}},"type":"object","required":["text"],"title":"TextBlock","description":"Text content block in a message.\n\nRepresents plain text content from Claude or the user.\nUsed in content arrays alongside tool_use and tool_result blocks."},"cbai__TokenStats":{"properties":{"input":{"type":"integer","title":"Input","description":"Total input tokens (uncached, full-price)","default":0},"output":{"type":"integer","title":"Output","description":"Total output tokens","default":0},"total":{"type":"integer","title":"Total","description":"Total tokens (input + output)","default":0},"cache_creation":{"type":"integer","title":"Cache Creation","description":"Total tokens written to the prompt cache (Anthropic)","default":0},"cache_read":{"type":"integer","title":"Cache Read","description":"Total tokens served from the prompt cache (Anthropic)","default":0}},"type":"object","title":"TokenStats","description":"Token usage statistics."},"cbai__ToolChatMessage":{"properties":{"role":{"type":"string","enum":["user","assistant"],"title":"Role","description":"Message role. 'user' for human/tool results, 'assistant' for Claude.","examples":["user","assistant"]},"content":{"anyOf":[{"type":"string"},{"items":{"anyOf":[{"$ref":"#/components/schemas/cbai__TextBlock"},{"$ref":"#/components/schemas/cbai__ToolUseBlock"},{"$ref":"#/components/schemas/cbai__ToolResultBlock"}]},"type":"array"}],"title":"Content","description":"Message content. String for simple messages, or array of content blocks for tool interactions."}},"type":"object","required":["role","content"],"title":"ToolChatMessage","description":"A message in a tool-enabled chat conversation.\n\nExtends the standard chat message to support content blocks for\nmulti-turn tool conversations.\n\n### Content Types by Role\n- **user**: text content OR tool_result blocks\n- **assistant**: text content AND/OR tool_use blocks\n\n### Simple Usage\nFor simple messages without tool interactions, use string content:\n`{\"role\": \"user\", \"content\": \"What's the weather?\"}`\n\n### Tool Result Usage\nAfter Claude returns tool_use, respond with tool_result:\n```json\n{\n    \"role\": \"user\",\n    \"content\": [\n        {\"type\": \"tool_result\", \"tool_use_id\": \"...\", \"content\": \"72°F\"}\n    ]\n}\n```","examples":[{"content":"What's the weather in Paris?","role":"user"},{"content":[{"text":"I'll check the weather for you.","type":"text"},{"id":"toolu_01","input":{"location":"Paris, France"},"name":"get_weather","type":"tool_use"}],"role":"assistant"},{"content":[{"content":"18°C, partly cloudy","tool_use_id":"toolu_01","type":"tool_result"}],"role":"user"}]},"cbai__ToolChatRequest":{"properties":{"messages":{"items":{"$ref":"#/components/schemas/cbai__ToolChatMessage"},"type":"array","minItems":1,"title":"Messages","description":"Conversation messages. Include previous turns for context. When continuing after tool_use, include the assistant's response and your tool_result blocks."},"tools":{"items":{"$ref":"#/components/schemas/cbai__ToolDefinition"},"type":"array","minItems":1,"title":"Tools","description":"Tools available to Claude. Each tool must have a unique name. Include clear descriptions for better tool selection."},"tool_choice":{"$ref":"#/components/schemas/cbai__ToolChoice","description":"Control tool usage behavior. Default 'auto' lets Claude decide."},"system":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"System","description":"System prompt providing context and instructions. Placed before the conversation, guides Claude's behavior.","examples":["You are a helpful weather assistant. Always include both temperature and conditions."]},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Override the default Claude model. Defaults to configured CLAUDE_MODEL (claude-sonnet-4-5-20250929).","examples":["claude-sonnet-4-5-20250929","claude-opus-4-20250514"]},"max_tokens":{"anyOf":[{"type":"integer","maximum":100000.0,"minimum":1.0},{"type":"null"}],"title":"Max Tokens","description":"Maximum tokens in response. Defaults to CLAUDE_MAX_TOKENS (4096).","examples":[1024,4096,8192]},"temperature":{"type":"number","maximum":2.0,"minimum":0.0,"title":"Temperature","description":"Sampling temperature. Lower = more focused, higher = more creative. For tool use, lower values (0.0-0.3) often work better.","default":0.7,"examples":[0.0,0.3,0.7]},"stream":{"type":"boolean","title":"Stream","description":"Enable streaming. When true, returns NDJSON events. Each line is a JSON object with 'type' field.","default":false}},"additionalProperties":false,"type":"object","required":["messages","tools"],"title":"ToolChatRequest","description":"Request for chat with tool use support.\n\nSend a conversation with available tools to Claude. Claude will\ndecide whether to use tools based on the conversation and tool_choice.\n\n### Tool Use Flow\n1. Send messages with tools array\n2. If Claude returns tool_use blocks (stop_reason=\"tool_use\"):\n   - Execute the requested tools\n   - Send tool_result blocks in next message\n   - Repeat until stop_reason=\"end_turn\"\n3. Process final response\n\n### Provider\nTool use is Claude-only. Other providers do not support this endpoint.\n\n### Streaming\nWhen stream=true, response is NDJSON (newline-delimited JSON).\nEach line is a complete JSON event:\n- `{\"type\": \"text\", \"text\": \"...\"}`\n- `{\"type\": \"tool_use\", \"id\": \"...\", \"name\": \"...\", \"input\": {...}}`\n- `{\"type\": \"message_complete\", \"stop_reason\": \"...\", \"usage\": {...}}`","examples":[{"messages":[{"content":"What's the weather in Tokyo?","role":"user"}],"tool_choice":{"type":"auto"},"tools":[{"description":"Get current weather for a location","input_schema":{"properties":{"location":{"type":"string"}},"required":["location"],"type":"object"},"name":"get_weather"}]},{"messages":[{"content":"What's the weather in Tokyo?","role":"user"},{"content":[{"text":"I'll check that for you.","type":"text"},{"id":"toolu_01","input":{"location":"Tokyo, Japan"},"name":"get_weather","type":"tool_use"}],"role":"assistant"},{"content":[{"content":"15°C, clear skies","tool_use_id":"toolu_01","type":"tool_result"}],"role":"user"}],"tools":[{"description":"Get current weather for a location","input_schema":{"properties":{"location":{"type":"string"}},"required":["location"],"type":"object"},"name":"get_weather"}]}]},"cbai__ToolChatResponse":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Claude message ID. Useful for logging and debugging tool-use loops.","examples":["msg_01XFDUDYJgAACzvnptvVoYEL"]},"content":{"items":{"anyOf":[{"$ref":"#/components/schemas/cbai__TextBlock"},{"$ref":"#/components/schemas/cbai__ToolUseBlock"},{"$ref":"#/components/schemas/cbai__ToolResultBlock"}]},"type":"array","title":"Content","description":"Response content blocks. May contain text and/or tool_use blocks. Process all blocks in order."},"model":{"type":"string","title":"Model","description":"Model that generated this response.","examples":["claude-sonnet-4-5-20250929"]},"stop_reason":{"$ref":"#/components/schemas/cbai__ToolStopReason","description":"Why generation stopped. 'tool_use' means tools were requested. 'end_turn' means conversation is complete."},"usage":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Usage","description":"Token usage: input_tokens, output_tokens. Use for cost tracking.","examples":[{"input_tokens":150,"output_tokens":50}]}},"type":"object","required":["content","model","stop_reason"],"title":"ToolChatResponse","description":"Response from a tool-enabled chat request.\n\n### Interpreting the Response\n\n**Check stop_reason first:**\n- `end_turn`: Conversation complete, process content normally\n- `tool_use`: Claude wants tools executed, find tool_use blocks in content\n- `max_tokens`: Response truncated, may need to retry with higher limit\n\n**Processing content blocks:**\n- Text blocks: Display to user\n- Tool use blocks: Execute tools, send results back\n\n### Multi-turn Flow\nWhen stop_reason=\"tool_use\":\n1. Execute each tool from tool_use blocks\n2. Create new ToolChatRequest with original messages + assistant response + tool_result blocks\n3. Send to get Claude's continuation\n\n### Cost Tracking\nUse usage field for cost estimation:\n`cost = (input_tokens * $3 + output_tokens * $15) / 1,000,000`","examples":[{"content":[{"text":"I'll check the weather for you.","type":"text"},{"id":"toolu_01A09q90qw90lq917835lq9","input":{"location":"San Francisco, CA"},"name":"get_weather","type":"tool_use"}],"id":"msg_01XFDUDYJgAACzvnptvVoYEL","model":"claude-sonnet-4-5-20250929","stop_reason":"tool_use","usage":{"input_tokens":150,"output_tokens":50}},{"content":[{"text":"The weather in San Francisco is 72°F and sunny with light clouds.","type":"text"}],"id":"msg_02BKEwMzLpAACnqrtuWxZFM","model":"claude-sonnet-4-5-20250929","stop_reason":"end_turn","usage":{"input_tokens":200,"output_tokens":25}}]},"cbai__ToolChoice":{"properties":{"type":{"$ref":"#/components/schemas/cbai__ToolChoiceType","description":"Tool choice mode. 'auto' = Claude decides, 'any' = must use a tool, 'tool' = must use the named tool, 'none' = no tools allowed.","default":"auto"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Required when type='tool'. The exact name of the tool Claude must use. Must match a tool name in the tools array.","examples":["get_weather","search_documents"]},"disable_parallel_tool_use":{"type":"boolean","title":"Disable Parallel Tool Use","description":"When true, Claude will use at most one tool per response. Default false allows parallel tool calls for efficiency.","default":false}},"type":"object","title":"ToolChoice","description":"Control how Claude uses tools.\n\n### Modes\n- **auto**: Claude decides whether to call tools (default)\n- **any**: Force Claude to use at least one tool\n- **none**: Prevent any tool usage\n- **tool**: Force a specific tool by name\n\n### When to Use Each Mode\n- Use `auto` for natural conversations where tools are optional\n- Use `any` when you need structured output via tools\n- Use `tool` when you know exactly which tool should be called\n- Use `none` to temporarily disable tools without removing them","examples":[{"type":"auto"},{"type":"any"},{"name":"get_weather","type":"tool"},{"disable_parallel_tool_use":true,"type":"auto"}]},"cbai__ToolChoiceType":{"type":"string","enum":["auto","any","none","tool"],"title":"ToolChoiceType","description":"Tool choice modes controlling Claude's tool usage behavior.\n\n- **auto**: Claude decides whether to use tools (default, recommended)\n- **any**: Force Claude to use at least one tool\n- **none**: Prevent any tool usage\n- **tool**: Force a specific tool by name"},"cbai__ToolDefinition":{"properties":{"name":{"type":"string","pattern":"^[a-zA-Z0-9_-]{1,64}$","title":"Name","description":"Unique tool name. Must match pattern: ^[a-zA-Z0-9_-]{1,64}$. Use descriptive, action-oriented names like 'get_weather' or 'search_documents'.","examples":["get_weather","search_database","calculate_price"]},"description":{"type":"string","minLength":10,"title":"Description","description":"Detailed description of what the tool does, when to use it, and what it returns. More detail = better tool selection by Claude. Aim for 3-4 sentences minimum.","examples":["Get the current weather in a given location. Returns temperature, conditions, and humidity. Use when the user asks about weather in a specific city or region."]},"input_schema":{"additionalProperties":true,"type":"object","title":"Input Schema","description":"JSON Schema object defining the tool's input parameters. Must be a valid JSON Schema with 'type': 'object' and 'properties'. Include 'required' array for mandatory parameters.","examples":[{"properties":{"location":{"description":"City and state, e.g. 'San Francisco, CA'","type":"string"},"unit":{"description":"Temperature unit","enum":["celsius","fahrenheit"],"type":"string"}},"required":["location"],"type":"object"}]}},"type":"object","required":["name","description","input_schema"],"title":"ToolDefinition","description":"Definition of a tool available to Claude.\n\nTools enable Claude to interact with external systems, APIs, or\nperform structured operations. Each tool must have a unique name,\nclear description, and a JSON Schema defining its inputs.\n\n### Best Practices\n- Provide detailed descriptions (3-4 sentences minimum)\n- Include examples in parameter descriptions\n- Use specific, unambiguous parameter names\n- Define required vs optional parameters clearly\n\n### JSON Schema Format\nThe input_schema must be a valid JSON Schema with:\n- `type: \"object\"` at the root\n- `properties` defining each parameter\n- `required` array listing mandatory parameters","example":{"description":"Get the current weather in a given location. Returns temperature, conditions, and humidity.","input_schema":{"properties":{"location":{"description":"City and state","type":"string"},"unit":{"enum":["celsius","fahrenheit"],"type":"string"}},"required":["location"],"type":"object"},"name":"get_weather"}},"cbai__ToolResultBlock":{"properties":{"type":{"type":"string","const":"tool_result","title":"Type","description":"Block type identifier.","default":"tool_result"},"tool_use_id":{"type":"string","title":"Tool Use Id","description":"The id from the tool_use block this result corresponds to. Must match exactly.","examples":["toolu_01A09q90qw90lq917835lq9"]},"content":{"type":"string","title":"Content","description":"The result from executing the tool. For structured data, serialize to JSON string.","examples":["72°F, sunny with light clouds","{\"temperature\": 72, \"unit\": \"fahrenheit\", \"conditions\": \"sunny\"}"]},"is_error":{"type":"boolean","title":"Is Error","description":"Set to true if the tool execution failed. Include error details in content.","default":false}},"type":"object","required":["tool_use_id","content"],"title":"ToolResultBlock","description":"Result from executing a tool.\n\nAfter executing a tool, send this block back to continue the conversation.\nThe tool_use_id must match the id from the corresponding ToolUseBlock.\n\n### Content Format\n- String: Simple text result\n- Can also include structured data as JSON string\n\n### Error Handling\nSet is_error=true when the tool fails. Claude will acknowledge\nthe error and may retry or ask the user for clarification.\n\n### Multiple Results\nWhen Claude requests multiple tools, include ALL tool_result blocks\nin a single user message, in any order.","example":{"content":"72°F, sunny with light clouds","is_error":false,"tool_use_id":"toolu_01A09q90qw90lq917835lq9","type":"tool_result"}},"cbai__ToolStopReason":{"type":"string","enum":["end_turn","tool_use","max_tokens","stop_sequence","refusal","pause_turn","model_context_window_exceeded","unknown"],"title":"ToolStopReason","description":"Why Claude stopped generating.\n\n- **end_turn**: Normal completion, no more content\n- **tool_use**: Claude wants to use tools, execute and continue\n- **max_tokens**: Hit token limit, response may be incomplete\n- **stop_sequence**: Hit a custom stop sequence\n- **refusal**: The model declined to continue. A NORMAL outcome, not an\n  error — the caller should skip the item, not retry the request.\n- **pause_turn**: A long-running turn was paused; resume by sending the\n  response back unmodified.\n- **model_context_window_exceeded**: The request exceeded the model's\n  context window.\n- **unknown**: A value this service does not recognise yet. See ``_missing_``.\n\nThis enum mirrors the upstream Anthropic ``stop_reason`` set and must stay a\nsuperset of ``agent.ALLOWED_NON_TOOL_STOP_REASONS`` — the two drifted once\n(cbai#114) and the drift was invisible until production traffic hit it."},"cbai__ToolUseBlock":{"properties":{"type":{"type":"string","const":"tool_use","title":"Type","description":"Block type identifier.","default":"tool_use"},"id":{"type":"string","title":"Id","description":"Unique identifier for this tool use. Must be included in the corresponding tool_result.","examples":["toolu_01A09q90qw90lq917835lq9"]},"name":{"type":"string","title":"Name","description":"Name of the tool being called.","examples":["get_weather","search_database"]},"input":{"additionalProperties":true,"type":"object","title":"Input","description":"Input parameters for the tool, matching the tool's input_schema. Pass this directly to your tool implementation.","examples":[{"location":"San Francisco, CA","unit":"celsius"}]}},"type":"object","required":["id","name","input"],"title":"ToolUseBlock","description":"Tool use request from Claude.\n\nWhen Claude decides to use a tool, it returns this block with:\n- A unique ID to match with the tool result\n- The tool name\n- The input parameters to pass to the tool\n\n### Handling Tool Use\n1. Extract the tool name and input\n2. Execute your tool with the provided input\n3. Return a ToolResultBlock with matching tool_use_id\n\n### Parallel Tool Calls\nClaude may return multiple tool_use blocks in one response.\nExecute all tools, then send all results in a single user message.","example":{"id":"toolu_01A09q90qw90lq917835lq9","input":{"location":"San Francisco, CA","unit":"celsius"},"name":"get_weather","type":"tool_use"}},"cbai__TopicsRequest":{"properties":{"content":{"type":"string","minLength":10,"title":"Content","description":"The content to analyze for topic extraction. Works best with 100+ words of content.","examples":["Article about climate change and renewable energy..."]},"max_topics":{"anyOf":[{"type":"integer","maximum":20.0,"minimum":1.0},{"type":"null"}],"title":"Max Topics","description":"Maximum number of topics to return. Default behavior returns 3-5 topics. Set this for a hard cap (e.g. 10 for finer categorization, 3 for headline-style).","examples":[3,5,10]}},"additionalProperties":false,"type":"object","required":["content"],"title":"TopicsRequest","description":"Request to extract main topics from content.\n\nAnalyzes text and extracts 3-5 key topics or themes.\nUseful for categorization, tagging, and content organization.","example":{"content":"The global economy is experiencing significant shifts...","max_topics":5}},"cbai__TopicsResponse":{"properties":{"topics":{"items":{"type":"string"},"type":"array","title":"Topics","description":"Extracted topics (3-5). Topics are short phrases or keywords representing main themes in the content. Ordered by prominence.","examples":[["climate change","renewable energy","carbon emissions","policy"]]},"provider":{"type":"string","title":"Provider","description":"Which provider extracted the topics ('ollama' or 'claude').","examples":["claude","ollama"]}},"type":"object","required":["topics","provider"],"title":"TopicsResponse","description":"Response containing extracted topics.\n\nReturns 3-5 main topics identified in the content.\nTopics are returned in order of relevance/prominence.","example":{"provider":"claude","topics":["artificial intelligence","machine learning","neural networks"]}},"cbai__TraceListResponse":{"properties":{"records":{"items":{"$ref":"#/components/schemas/cbai__TraceSummary"},"type":"array","title":"Records","description":"Trace summary rows for this page, newest first."},"total":{"type":"integer","title":"Total","description":"Total number of traces matching the query (across all pages).","examples":[128]},"limit":{"type":"integer","title":"Limit","description":"Maximum records requested for this page.","examples":[100]},"offset":{"type":"integer","title":"Offset","description":"Number of records skipped before this page.","examples":[0]}},"type":"object","required":["records","total","limit","offset"],"title":"TraceListResponse","description":"Paginated list of trace summaries returned by GET /api/v1/traces.","example":{"limit":100,"offset":0,"records":[{"duration_ms":1842.7,"root_span_id":"00f067aa0ba902b7","root_span_name":"invoke_agent","span_count":7,"started_at":"2026-06-20T12:00:00Z","status_code":"OK","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}],"total":128}},"cbai__TraceSummary":{"properties":{"trace_id":{"type":"string","title":"Trace Id","description":"32-char hex trace ID","examples":["4bf92f3577b34da6a3ce929d0e0e4736"]},"root_span_id":{"type":"string","title":"Root Span Id","description":"16-char hex root span ID","examples":["00f067aa0ba902b7"]},"root_span_name":{"type":"string","title":"Root Span Name","description":"Name of the root span (e.g. 'invoke_agent')","examples":["invoke_agent"]},"root_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Root Kind","description":"OTel SpanKind name (INTERNAL, SERVER, CLIENT, ...)","examples":["INTERNAL","SERVER"]},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"Root span start time","examples":["2026-06-20T12:00:00Z"]},"duration_ms":{"type":"number","title":"Duration Ms","description":"Root span duration in ms","examples":[1842.7]},"status_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Code","description":"OTel StatusCode name (OK / ERROR / UNSET)","examples":["OK","ERROR"]},"span_count":{"type":"integer","title":"Span Count","description":"Total number of spans in this trace tree","examples":[7]}},"type":"object","required":["trace_id","root_span_id","root_span_name","started_at","duration_ms","span_count"],"title":"TraceSummary","description":"Summary row for /api/v1/traces — one entry per trace_id.\n\nA compact, list-view projection of a trace: just the root span's\nidentity, timing, and status plus a total span count. Drill into the\nfull tree via GET /api/v1/traces/{trace_id}.","example":{"duration_ms":1842.7,"root_kind":"INTERNAL","root_span_id":"00f067aa0ba902b7","root_span_name":"invoke_agent","span_count":7,"started_at":"2026-06-20T12:00:00Z","status_code":"OK","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}},"cbai__UnstructuredElement":{"properties":{"type":{"type":"string","title":"Type","description":"Element type classification.","examples":["Title","NarrativeText","Table","Image","ListItem"]},"element_id":{"type":"string","title":"Element Id","description":"Unique identifier for this element.","examples":["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]},"text":{"type":"string","title":"Text","description":"Text content of the element."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Element metadata including: page_number, coordinates (if requested), parent_id (for nested elements), category_depth (heading level)."}},"type":"object","required":["type","element_id","text"],"title":"UnstructuredElement","description":"Single element from Unstructured response.\n\nDocuments are parsed into discrete elements with types like:\n- **Title**: Headings and section titles\n- **NarrativeText**: Body paragraphs\n- **Table**: Tabular data\n- **Image**: Embedded images\n- **ListItem**: Bulleted or numbered list items","example":{"element_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","metadata":{"category_depth":0,"page_number":1},"text":"This paper presents a novel approach to...","type":"NarrativeText"}},"cbai__UnstructuredOCRRequest":{"properties":{"document_base64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Base64","description":"Base64-encoded document content. Decoded server-side into `file_bytes`. Alias for `file_bytes` taking a base64 string."},"include_images":{"type":"boolean","title":"Include Images","description":"Convenience flag: when True and `extract_image_block_types` is empty, defaults the extraction list to `['Image']`.","default":false},"strategy":{"$ref":"#/components/schemas/cbai__UnstructuredStrategy","description":"Processing strategy. 'auto' recommended for most cases. Use 'hi_res' for complex layouts, 'fast' for simple documents.","default":"auto"},"languages":{"items":{"type":"string"},"type":"array","title":"Languages","description":"Document languages as ISO 639-3 codes. Default: English.","examples":[["eng"],["eng","spa"],["deu"]]},"ocr_languages":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Ocr Languages","description":"Separate OCR language config if different from document language."},"coordinates":{"type":"boolean","title":"Coordinates","description":"Include bounding box coordinates for each element (for visual mapping).","default":false},"include_page_breaks":{"type":"boolean","title":"Include Page Breaks","description":"Include page break markers between pages.","default":false},"unique_element_ids":{"type":"boolean","title":"Unique Element Ids","description":"Generate UUIDs for each element (recommended for tracking).","default":true},"xml_keep_tags":{"type":"boolean","title":"Xml Keep Tags","description":"Retain XML tags in output when processing XML documents.","default":false},"hi_res_model_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hi Res Model Name","description":"Inference model name for hi_res strategy (e.g., 'yolox')."},"pdf_infer_table_structure":{"type":"boolean","title":"Pdf Infer Table Structure","description":"Extract table structure from PDFs (set False to skip tables).","default":true},"skip_infer_table_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Skip Infer Table Types","description":"Document types to skip table extraction for.","examples":[["pdf","jpg"]]},"starting_page_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Starting Page Number","description":"Starting page number for split PDF processing."},"chunking_strategy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chunking Strategy","description":"Chunking strategy: 'by_title' groups content under headings.","examples":["by_title"]},"combine_under_n_chars":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Combine Under N Chars","description":"Combine small elements under N characters into larger chunks."},"max_characters":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Characters","description":"Maximum characters per chunk (for RAG pipelines)."},"new_after_n_chars":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"New After N Chars","description":"Soft character limit - start new chunk after this many characters."},"overlap":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Overlap","description":"Character overlap between chunks (preserves context at boundaries)."},"overlap_all":{"type":"boolean","title":"Overlap All","description":"Apply overlap between all chunks, not just same-section chunks.","default":false},"multipage_sections":{"type":"boolean","title":"Multipage Sections","description":"Allow sections to span multiple pages when chunking.","default":true},"extract_image_block_types":{"items":{"type":"string"},"type":"array","title":"Extract Image Block Types","description":"Element types to extract as base64 images. E.g., ['Image', 'Figure'].","examples":[["Image"],["Image","Figure","Table"]]},"file_bytes":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"File Bytes","description":"Binary file data."},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type","description":"MIME type hint (e.g., 'application/pdf')."},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename","description":"Original filename."},"encoding":{"type":"string","title":"Encoding","description":"Text encoding.","default":"utf-8"},"include_slide_notes":{"type":"boolean","title":"Include Slide Notes","description":"Include PowerPoint slide notes (for PPTX files).","default":true}},"additionalProperties":false,"type":"object","title":"UnstructuredOCRRequest","description":"Full-featured request for Unstructured document processing.\n\nUnstructured provides structured document parsing with element-level\nextraction (titles, paragraphs, tables, images as separate elements).\n\n### When to Use\n- Complex document layouts with multiple columns\n- Extracting structured elements (tables, lists, headings)\n- Need element coordinates for visual mapping\n- Processing PowerPoint or other structured formats\n\n### Input Options\nSend the document itself as `document_base64` (the simple-API surface\nmatching `OCRRequest`) or `file_bytes`. There is no URL or path input: cbai\ndoes not fetch caller-supplied URLs or open server-side files (cbai#129)."},"cbai__UnstructuredOCRResponse":{"properties":{"elements":{"items":{"$ref":"#/components/schemas/cbai__UnstructuredElement"},"type":"array","title":"Elements","description":"Parsed document elements in reading order."},"text":{"type":"string","title":"Text","description":"Combined text from all elements (newline-separated)."},"element_count":{"type":"integer","title":"Element Count","description":"Total number of elements extracted."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Processing metadata: strategy used, element types found, languages."},"images":{"items":{"type":"string"},"type":"array","title":"Images","description":"Base64-encoded images (if extract_image_block_types specified)."}},"type":"object","required":["elements","text","element_count"],"title":"UnstructuredOCRResponse","description":"Response from Unstructured document processing.\n\n### Response Structure\n- **elements**: Individual document components (most detailed)\n- **text**: Combined plain text (for simple use cases)\n- **metadata**: Processing statistics and options used","example":{"element_count":1,"elements":[{"element_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","metadata":{"page_number":1},"text":"Quarterly Report","type":"Title"}],"images":[],"metadata":{"languages":["eng"],"strategy":"auto"},"text":"Quarterly Report\nRevenue grew 12% year over year..."}},"cbai__UnstructuredStrategy":{"type":"string","enum":["fast","hi_res","auto","ocr_only"],"title":"UnstructuredStrategy","description":"Processing strategies for Unstructured API.\n\nChoose based on speed vs accuracy tradeoff:\n- **fast**: Quick processing, lower accuracy\n- **hi_res**: High resolution, best for complex layouts\n- **auto**: Automatic selection based on document type\n- **ocr_only**: Pure OCR, no layout detection"},"cbai__UsagePeriod":{"properties":{"start":{"type":"string","format":"date-time","title":"Start","description":"Period start time"},"end":{"type":"string","format":"date-time","title":"End","description":"Period end time"}},"type":"object","required":["start","end"],"title":"UsagePeriod","description":"Time period for aggregation."},"cbai__UsageRecord":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Unique identifier for this request (UUID)"},"api_key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Id","description":"API key identifier (for future per-key tracking)"},"operation":{"type":"string","title":"Operation","description":"Operation type: chat, embed, summarize, topics, ocr"},"provider":{"type":"string","title":"Provider","description":"AI provider: ollama, claude, mistral, unstructured"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Model name used for this request"},"input_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Input Tokens","description":"Input tokens consumed (Claude only)"},"output_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Output Tokens","description":"Output tokens generated (Claude only)"},"total_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Tokens","description":"Total tokens (input + output)"},"cache_creation_input_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cache Creation Input Tokens","description":"Tokens written to the prompt cache on this request (Anthropic only). Billed at a premium over standard input."},"cache_read_input_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cache Read Input Tokens","description":"Tokens served from the prompt cache on this request (Anthropic only). Billed at ~10% of standard input."},"cost_usd_total":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost Usd Total","description":"Billable USD cost for this record, computed from the price table at write time. Zero for local providers (ollama) and for unknown (provider, model) pairs."},"cost_usd_saved_by_cache":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost Usd Saved By Cache","description":"USD savings vs. the no-cache baseline: what the cache_read tokens would have cost at the standard input rate, minus what we actually paid at the cache_read rate."},"trace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trace Id","description":"OTel trace ID of the root span this request was part of. Hex-encoded, 32 chars. Use to drill into /api/v1/traces/{id}."},"span_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Span Id","description":"OTel span ID this usage record corresponds to. Hex-encoded, 16 chars."},"tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id","description":"Tenant the request was made on behalf of (X-CB-Tenant-ID)."},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"User the request was made on behalf of (X-CB-User-ID)."},"origin_request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Origin Request Id","description":"Originating request ID — propagated unchanged through the call chain so a single user action is followable across N service hops (X-CB-Request-ID). Distinct from this record's own request_id, which is per-record."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Service that made *this* call to cbai (X-CB-Source)."},"parent_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Source","description":"Service that called the immediate caller (X-CB-Parent-Source). Will be derivable from the inbound traceparent once cbai#80 lands."},"request_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Request Size Bytes","description":"Size of the request payload in bytes"},"response_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Response Size Bytes","description":"Size of the response payload in bytes"},"latency_ms":{"type":"number","title":"Latency Ms","description":"Request latency in milliseconds"},"stream":{"type":"boolean","title":"Stream","description":"Whether streaming was enabled","default":false},"temperature":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Temperature","description":"Temperature parameter used"},"max_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Tokens","description":"Max tokens parameter used"},"success":{"type":"boolean","title":"Success","description":"Whether the request succeeded","default":true},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Error message if request failed"},"finish_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finish Reason","description":"Why generation stopped: stop, length, content_filter, tool_use"},"tool_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tool Count","description":"Number of tools provided in request (chat_tools only)"},"tool_calls":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tool Calls","description":"Number of tool_use blocks in response (chat_tools only)"},"pages":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pages","description":"Document pages processed — for per-page-billed operations (Mistral OCR). Drives per-page cost in compute_cost (cbai#104)."},"id":{"type":"integer","title":"Id","description":"Database record ID"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When this request was made"}},"type":"object","required":["request_id","operation","provider","latency_ms","id","created_at"],"title":"UsageRecord","description":"Complete usage record with database metadata.\n\nThis is returned when querying usage data.","example":{"created_at":"2025-01-03T12:00:00Z","id":1,"input_tokens":150,"latency_ms":1234.5,"model":"claude-sonnet-4-5-20250929","operation":"chat","output_tokens":250,"provider":"claude","request_id":"550e8400-e29b-41d4-a716-446655440000","success":true,"total_tokens":400}},"cbai__UsageResponse":{"properties":{"records":{"items":{"$ref":"#/components/schemas/cbai__UsageRecord"},"type":"array","title":"Records","description":"List of usage records"},"total":{"type":"integer","title":"Total","description":"Total number of matching records"},"limit":{"type":"integer","title":"Limit","description":"Limit used for this query"},"offset":{"type":"integer","title":"Offset","description":"Offset used for this query"}},"type":"object","required":["records","total","limit","offset"],"title":"UsageResponse","description":"Paginated response containing usage records.","example":{"limit":50,"offset":0,"records":[{"created_at":"2025-01-03T12:00:00Z","id":1,"latency_ms":1234.5,"operation":"chat","provider":"claude","request_id":"550e8400-e29b-41d4-a716-446655440000","success":true}],"total":100}},"cbai__UsageSeries":{"properties":{"interval":{"type":"string","title":"Interval","description":"Bucket size: 'day' or 'hour'"},"points":{"items":{"$ref":"#/components/schemas/cbai__SeriesPoint"},"type":"array","title":"Points","description":"Buckets, oldest first"}},"type":"object","required":["interval"],"title":"UsageSeries","description":"Time-bucketed usage series for trend charts (cbai#111).\n\nBuckets are contiguous over [start, end] — empty buckets are filled\nwith zeros so a chart has no gaps.","example":{"interval":"day","points":[{"bucket":"2026-06-30","cost_saved_usd":0.88,"cost_usd":4.21,"errors":6,"requests":1200,"success_rate":0.995,"tokens_total":340000}]}},"cbai__UsageSummary":{"properties":{"period":{"$ref":"#/components/schemas/cbai__UsagePeriod","description":"Time period covered by this summary"},"totals":{"$ref":"#/components/schemas/cbai__UsageTotals","description":"Overall totals for the period"},"by_operation":{"additionalProperties":{"$ref":"#/components/schemas/cbai__OperationStats"},"type":"object","title":"By Operation","description":"Stats broken down by operation type"},"by_provider":{"additionalProperties":{"$ref":"#/components/schemas/cbai__ProviderStats"},"type":"object","title":"By Provider","description":"Stats broken down by provider"},"grouped_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Grouped By","description":"The dimension used to build `by_group`, when a `group_by` was requested (one of: model, tenant, user, source). Null otherwise."},"by_group":{"additionalProperties":{"$ref":"#/components/schemas/cbai__ProviderStats"},"type":"object","title":"By Group","description":"Stats broken down by the requested `group_by` dimension (model / tenant / user / source). Empty unless group_by was set. Rows with a null dimension value bucket under '(none)'. Reuses the ProviderStats shape (requests / tokens / cache / cost / latency / success_rate) so a per-model or per-tenant leaderboard renders the same as by_provider."}},"type":"object","required":["period","totals"],"title":"UsageSummary","description":"Aggregated usage summary.\n\nProvides high-level statistics for dashboards and monitoring.","example":{"by_operation":{"chat":{"latency_avg_ms":300.0,"requests":3000},"embed":{"latency_avg_ms":50.0,"requests":2000}},"by_provider":{"claude":{"requests":2000,"success_rate":0.99,"tokens":{"input":100000,"output":50000,"total":150000}},"ollama":{"requests":3000,"success_rate":0.97}},"period":{"end":"2025-01-02T00:00:00Z","start":"2025-01-01T00:00:00Z"},"totals":{"errors":100,"latency_avg_ms":234.5,"requests":5000,"success_rate":0.98,"tokens":{"input":100000,"output":50000,"total":150000}}}},"cbai__UsageTotals":{"properties":{"requests":{"type":"integer","title":"Requests","description":"Total number of requests"},"tokens":{"$ref":"#/components/schemas/cbai__TokenStats","description":"Token usage totals (Claude only)"},"cache":{"$ref":"#/components/schemas/cbai__CacheStats","description":"Prompt-cache usage and hit rate (Anthropic only)"},"cost":{"$ref":"#/components/schemas/cbai__CostStats","description":"USD cost and cache savings (cbai#81)"},"latency_avg_ms":{"type":"number","title":"Latency Avg Ms","description":"Average latency in milliseconds"},"latency_p50_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latency P50 Ms","description":"Median latency (50th percentile)"},"latency_p95_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latency P95 Ms","description":"95th percentile latency"},"success_rate":{"type":"number","title":"Success Rate","description":"Percentage of successful requests (0.0-1.0)"},"errors":{"type":"integer","title":"Errors","description":"Number of failed requests","default":0}},"type":"object","required":["requests","latency_avg_ms","success_rate"],"title":"UsageTotals","description":"Aggregated usage totals."},"cbai__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbai__app__models__types__HealthResponse":{"properties":{"ollama":{"type":"boolean","title":"Ollama","description":"Ollama local LLM availability. True if server is running.","examples":[true,false]},"claude":{"type":"boolean","title":"Claude","description":"Claude API availability. True if API key valid and API reachable.","examples":[true,false]},"mistral":{"type":"boolean","title":"Mistral","description":"Mistral API availability. True if API key valid and API reachable.","examples":[true,false]},"unstructured":{"type":"boolean","title":"Unstructured","description":"Unstructured API availability. True if endpoint reachable.","examples":[true,false]}},"type":"object","required":["ollama","claude","mistral","unstructured"],"title":"HealthResponse","description":"Health check response showing provider availability.\n\nUse this endpoint to verify which AI providers are currently available\nbefore making requests. A provider shows `true` if:\n- API key is configured (for cloud providers)\n- Service is reachable and responding\n\n### Interpreting Results\n- **ollama**: Local LLM server running on 10.8.0.2:11434\n- **claude**: Anthropic API key valid and API reachable\n- **mistral**: Mistral API key valid and API reachable\n- **unstructured**: Unstructured API endpoint reachable","example":{"claude":true,"mistral":true,"ollama":true,"unstructured":true}},"cbai__sysforge__core__health__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbai__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbai__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbairtable__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbairtable__CredentialCreate":{"properties":{"label":{"type":"string","maxLength":200,"title":"Label","description":"Human-readable label for this credential.","default":"","examples":["Production PAT","Dev PAT"]},"pat":{"type":"string","minLength":3,"title":"Pat","description":"Airtable Personal Access Token. Encrypted at rest.","examples":["patXXXXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"]},"base_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Id","description":"Default Airtable base ID for this credential.","examples":["appABC123def456"]}},"type":"object","required":["pat"],"title":"CredentialCreate","description":"Request to store an Airtable credential.","example":{"base_id":"appABC123def456","label":"Production PAT","pat":"patXXXXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}},"cbairtable__CredentialSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Credential identifier.","examples":["cred-a1b2c3d4"]},"tenant_id":{"type":"string","title":"Tenant Id","description":"Owning tenant UUID.","examples":["a6ad44c7-3051-40fd-8cef-3126975d59cb"]},"label":{"type":"string","title":"Label","description":"Human-readable label for this credential.","examples":["Production PAT"]},"base_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Id","description":"Default Airtable base ID for this credential, if set.","examples":["appABC123def456"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when the credential was stored."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when the credential was last modified."},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"When this credential was last used to call Airtable."},"use_count":{"type":"integer","title":"Use Count","description":"Number of times this credential has been used."},"pat_preview":{"type":"string","title":"Pat Preview","description":"Masked PAT preview, e.g. 'pat...4G'. Never contains the full token.","default":""}},"type":"object","required":["id","tenant_id","label","base_id","created_at","updated_at","last_used_at","use_count"],"title":"CredentialSummary","description":"Public credential view — never exposes the PAT.","example":{"base_id":"appABC123def456","id":"cred-a1b2c3d4","label":"Production PAT","pat_preview":"pat...4G","tenant_id":"a6ad44c7-3051-40fd-8cef-3126975d59cb","use_count":12}},"cbairtable__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbairtable__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbairtable__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbairtable__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbairtable__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbairtable__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbairtable__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbairtable__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbairtable__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbairtable__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbairtable__PublishSnapshotRequest":{"properties":{"tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id","description":"Restrict the snapshot to a single tenant. Omit to cover all tenants.","examples":["a6ad44c7-3051-40fd-8cef-3126975d59cb"]},"base_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Id","description":"Restrict the snapshot to a single base. Requires tenant_id (rejected with 400 if supplied without one).","examples":["appABC123def456"]}},"type":"object","title":"PublishSnapshotRequest","description":"Filter for which bases to snapshot.\n\n- ``{}`` (no args) — all bases under DATA_ROOT\n- ``{tenant_id: \"foo\"}`` — all bases for that tenant\n- ``{tenant_id: \"foo\", base_id: \"appXXX\"}`` — one specific base\n- ``{base_id: \"appXXX\"}`` without tenant_id — 400 (ambiguous)","example":{"base_id":"appABC123def456","tenant_id":"a6ad44c7-3051-40fd-8cef-3126975d59cb"}},"cbairtable__PublishSnapshotResponse":{"properties":{"ok":{"type":"integer","title":"Ok","description":"Number of bases whose snapshot was freshly published."},"failed":{"type":"integer","title":"Failed","description":"Number of bases whose snapshot publish raised or returned failure."},"skipped_fresh":{"type":"integer","title":"Skipped Fresh","description":"Bases skipped because their snapshot was already newer than the live file."},"duration_ms":{"type":"number","title":"Duration Ms","description":"Wall-clock duration of the publish run, in milliseconds."},"failures":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Failures","description":"Per-base failure details ({tenant_id, base_id, error}) for each failed base.","default":[]}},"type":"object","required":["ok","failed","skipped_fresh","duration_ms"],"title":"PublishSnapshotResponse","description":"Summary of a snapshot-publish run across the matched bases.","example":{"duration_ms":412.7,"failed":0,"failures":[],"ok":3,"skipped_fresh":1}},"cbairtable__QueryRequest":{"properties":{"where":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Where","description":"SQL WHERE clause (without the WHERE keyword).","examples":["\"Name\" = 'Alice'","\"Age\" > 30"]},"order_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order By","description":"SQL ORDER BY clause (without the ORDER BY keyword).","examples":["\"Name\" ASC","\"created_at\" DESC"]},"limit":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Limit","description":"Maximum number of rows to return.","default":100},"offset":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Offset","description":"Number of rows to skip before returning results."}},"type":"object","title":"QueryRequest","description":"Request body for querying synced data from local DuckDB."},"cbairtable__SQLRequest":{"properties":{"sql":{"type":"string","title":"Sql","description":"Read-only DuckDB SQL (SELECT, WITH, EXPLAIN only).","examples":["SELECT name, COUNT(*) FROM deployments GROUP BY 1"]},"limit":{"type":"integer","maximum":10000.0,"minimum":1.0,"title":"Limit","description":"Maximum rows to return.","default":10000}},"type":"object","required":["sql"],"title":"SQLRequest","description":"Request body for raw SQL queries against synced DuckDB."},"cbairtable__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbairtable__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbairtable__SyncJob":{"properties":{"id":{"type":"string","title":"Id","description":"Unique sync job identifier.","examples":["sync-a1b2c3d4"]},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant that owns this sync job.","examples":["a6ad44c7-3051-40fd-8cef-3126975d59cb"]},"credential_id":{"type":"string","title":"Credential Id","description":"Credential used to authenticate the Airtable pull.","examples":["cred-a1b2c3d4"]},"base_id":{"type":"string","title":"Base Id","description":"Airtable base being synced.","examples":["appABC123def456"]},"table_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Table Id","description":"Specific table being synced, or null for full-base sync."},"sync_mode":{"$ref":"#/components/schemas/cbairtable__SyncMode","description":"Sync strategy used for this job.","default":"auto"},"status":{"$ref":"#/components/schemas/cbairtable__SyncStatus","description":"Current status of the sync job.","default":"pending"},"tables_synced":{"type":"integer","title":"Tables Synced","description":"Number of tables synced so far.","default":0},"records_synced":{"type":"integer","title":"Records Synced","description":"Total records synced so far.","default":0},"records_deleted":{"type":"integer","title":"Records Deleted","description":"Records soft-deleted during reconciliation.","default":0},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if status is 'failed'."},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"UTC timestamp when the sync job started."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"UTC timestamp when the sync job finished, or null if still running."},"duration_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration Ms","description":"Total sync duration in milliseconds."}},"type":"object","required":["tenant_id","credential_id","base_id"],"title":"SyncJob","description":"Tracks progress and result of an Airtable → DuckDB sync operation.","example":{"base_id":"appABC123def456","credential_id":"cred-a1b2c3d4","duration_ms":5821.4,"id":"sync-a1b2c3d4","records_deleted":3,"records_synced":1280,"status":"completed","sync_mode":"auto","tables_synced":4,"tenant_id":"a6ad44c7-3051-40fd-8cef-3126975d59cb"}},"cbairtable__SyncMode":{"type":"string","enum":["full","incremental","auto"],"title":"SyncMode","description":"How records are fetched from Airtable.\n\nFULL: Drop and recreate table, fetch all records.\nINCREMENTAL: Fetch only records modified since last sync checkpoint.\nAUTO: Incremental if a checkpoint exists, otherwise full."},"cbairtable__SyncStatus":{"type":"string","enum":["pending","running","completed","failed"],"title":"SyncStatus","description":"Status of a sync operation.\n\nState machine: PENDING → RUNNING → COMPLETED | FAILED.\nUse PENDING for newly created jobs, RUNNING once sync begins,\nCOMPLETED on success, FAILED on any error."},"cbairtable__Tenant":{"properties":{"id":{"type":"string","title":"Id","description":"Tenant UUID from the central tenant system (cbtenant).","examples":["a6ad44c7-3051-40fd-8cef-3126975d59cb"]},"name":{"type":"string","title":"Name","description":"Display name for the tenant.","examples":["CC Campaign"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when the tenant record was created."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when the tenant record was last modified."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Arbitrary key-value metadata.","examples":[{"region":"us-east"}]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the tenant is active. Inactive tenants cannot access resources.","default":true}},"type":"object","required":["id","name"],"title":"Tenant","description":"A tenant represents an organization with Airtable access.\n\nThe tenant ID is a UUID from the central cbtenant system.\nA tenant may have one or more Airtable credentials (bases).","example":{"id":"a6ad44c7-3051-40fd-8cef-3126975d59cb","is_active":true,"metadata":{},"name":"CC Campaign"}},"cbairtable__TenantCreate":{"properties":{"id":{"type":"string","minLength":1,"title":"Id","description":"Tenant UUID from cbtenant.","examples":["a6ad44c7-3051-40fd-8cef-3126975d59cb"]},"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Display name for the tenant.","examples":["CC Campaign","Gallrein for State Rep"]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Arbitrary key-value metadata to attach to the tenant.","examples":[{"region":"us-east"}]}},"type":"object","required":["id","name"],"title":"TenantCreate","description":"Request model for creating a tenant.\n\nThe caller must provide the tenant ID from the central tenant system."},"cbairtable__TenantUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name","description":"New display name. Omit to leave unchanged.","examples":["CC Campaign (renamed)"]},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Replacement metadata dict. Omit to leave unchanged.","examples":[{"region":"us-west"}]},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Set to false to deactivate or true to reactivate the tenant.","examples":[true]}},"type":"object","title":"TenantUpdate","description":"Request model for updating a tenant. Only the supplied fields are changed."},"cbairtable__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbapp__ActionLog":{"properties":{"action_type_id":{"type":"string","title":"Action Type Id","description":"Type of action (call, text, door, etc.)"},"action_result_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Result Id","description":"Result of the action"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Notes about the contact"},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds","description":"Duration of the contact in seconds"}},"type":"object","required":["action_type_id"],"title":"ActionLog","description":"Request to log an action on a contact."},"cbapp__AlertData":{"properties":{"severity":{"type":"string","enum":["info","warning","critical"],"title":"Severity"},"service":{"type":"string","title":"Service"},"message":{"type":"string","title":"Message"},"action_required":{"type":"boolean","title":"Action Required","default":false},"action_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Url"},"auto_resolve":{"type":"boolean","title":"Auto Resolve","default":false},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At"}},"type":"object","required":["severity","service","message"],"title":"AlertData","description":"Data payload for alert system messages."},"cbapp__AssignmentCreate":{"properties":{"segment_id":{"type":"string","title":"Segment Id","description":"ID of the segment to assign"},"user_id":{"type":"string","title":"User Id","description":"ID of the user to assign to"},"priority":{"type":"integer","title":"Priority","description":"Priority (higher = more important)","default":0},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Notes about the assignment"}},"type":"object","required":["segment_id","user_id"],"title":"AssignmentCreate","description":"Request to create a segment assignment."},"cbapp__AssignmentUpdate":{"properties":{"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"AssignmentUpdate","description":"Request to update an assignment."},"cbapp__AttachmentListResponse":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/cbapp__AttachmentWithUrl"},"type":"array","title":"Attachments"},"total":{"type":"integer","title":"Total","default":0}},"type":"object","required":["attachments"],"title":"AttachmentListResponse","description":"Response for list attachments endpoint."},"cbapp__AttachmentUploadResponse":{"properties":{"id":{"type":"string","title":"Id"},"filename":{"type":"string","title":"Filename"},"file_path":{"type":"string","title":"File Path"},"file_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size"},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type"},"file_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Category"},"analysis_status":{"type":"string","title":"Analysis Status","default":"pending"},"download_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Download Url"},"message":{"type":"string","title":"Message","default":"File uploaded successfully"}},"type":"object","required":["id","filename","file_path"],"title":"AttachmentUploadResponse","description":"Response after successful file upload."},"cbapp__AttachmentWithUrl":{"properties":{"filename":{"type":"string","maxLength":255,"minLength":1,"title":"Filename"},"id":{"type":"string","title":"Id"},"event_page_id":{"type":"string","title":"Event Page Id"},"file_path":{"type":"string","title":"File Path"},"file_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size"},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type"},"file_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Category"},"analysis_status":{"type":"string","title":"Analysis Status","default":"pending"},"analysis_started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Analysis Started At"},"analysis_completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Analysis Completed At"},"analysis_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Error"},"analysis_results":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Analysis Results"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"download_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Download Url"}},"type":"object","required":["filename","id","event_page_id","file_path","created_at"],"title":"AttachmentWithUrl","description":"Attachment with computed download URL."},"cbapp__BatchEmbeddingRequest":{"properties":{"texts":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1,"title":"Texts","description":"List of texts to embed"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Ollama model to use","default":"nomic-embed-text:latest"},"normalize":{"type":"boolean","title":"Normalize","description":"Whether to normalize the embedding","default":true}},"type":"object","required":["texts"],"title":"BatchEmbeddingRequest","description":"Request model for batch embedding generation"},"cbapp__BatchEmbeddingResponse":{"properties":{"embeddings":{"items":{"items":{"type":"number"},"type":"array"},"type":"array","title":"Embeddings","description":"List of generated embedding vectors"},"dimension":{"type":"integer","title":"Dimension","description":"Dimension of the embeddings"},"cached_count":{"type":"integer","title":"Cached Count","description":"Number of results served from cache","default":0},"processing_time_ms":{"type":"integer","title":"Processing Time Ms","description":"Total processing time in milliseconds"}},"type":"object","required":["embeddings","dimension","processing_time_ms"],"title":"BatchEmbeddingResponse","description":"Response model for batch embedding generation"},"cbapp__BatchImportRequest":{"properties":{"persons":{"items":{"$ref":"#/components/schemas/cbapp__PersonCreate"},"type":"array","title":"Persons"},"batch_id":{"type":"string","title":"Batch Id"},"list_name":{"type":"string","title":"List Name"},"skip_duplicates":{"type":"boolean","title":"Skip Duplicates","default":true}},"type":"object","required":["persons","batch_id","list_name"],"title":"BatchImportRequest","description":"Request model for batch import."},"cbapp__BatchImportResult":{"properties":{"batch_id":{"type":"string","title":"Batch Id"},"list_name":{"type":"string","title":"List Name"},"total":{"type":"integer","title":"Total"},"imported":{"type":"integer","title":"Imported"},"duplicates":{"type":"integer","title":"Duplicates"},"failed":{"type":"integer","title":"Failed"},"errors":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Errors","default":[]},"imported_ids":{"items":{"type":"string"},"type":"array","title":"Imported Ids","default":[]}},"type":"object","required":["batch_id","list_name","total","imported","duplicates","failed"],"title":"BatchImportResult","description":"Result of a batch import operation."},"cbapp__Body_login_for_access_token_api_auth_token_post":{"properties":{"grant_type":{"anyOf":[{"type":"string","pattern":"^password$"},{"type":"null"}],"title":"Grant Type"},"username":{"type":"string","title":"Username"},"password":{"type":"string","format":"password","title":"Password"},"scope":{"type":"string","title":"Scope","default":""},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"password","title":"Client Secret"}},"type":"object","required":["username","password"],"title":"Body_login_for_access_token_api_auth_token_post"},"cbapp__Body_upload_attachment_api_events__event_id__page_attachments_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_attachment_api_events__event_id__page_attachments_post"},"cbapp__Body_upload_file_api_files_upload_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_file_api_files_upload_post"},"cbapp__Body_upload_file_api_list_loader_upload_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_file_api_list_loader_upload_post"},"cbapp__Body_upload_logo_api_branding_logo__logo_type__post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_logo_api_branding_logo__logo_type__post"},"cbapp__BrandingSettings":{"properties":{"primary_color":{"type":"string","title":"Primary Color","default":"#0e173e"},"secondary_color":{"type":"string","title":"Secondary Color","default":"#f1c613"},"accent_color":{"type":"string","title":"Accent Color","default":"#10b981"},"text_color":{"type":"string","title":"Text Color","default":"#374151"},"sidebar_color":{"type":"string","title":"Sidebar Color","default":"#f8fafc"},"app_name":{"type":"string","title":"App Name","default":"CampaignBrain"},"tagline":{"type":"string","title":"Tagline","default":""},"logos":{"additionalProperties":{"type":"string"},"type":"object","title":"Logos","default":{}}},"type":"object","title":"BrandingSettings","description":"Full branding settings response."},"cbapp__BrandingUpdate":{"properties":{"primary_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Color"},"secondary_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secondary Color"},"accent_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accent Color"},"text_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text Color"},"sidebar_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sidebar Color"},"app_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Name"},"tagline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tagline"}},"additionalProperties":false,"type":"object","title":"BrandingUpdate","description":"Request model for updating branding settings."},"cbapp__BridgeBroadcastRequest":{"properties":{"address":{"type":"string","title":"Address"},"content":{"type":"string","title":"Content"},"notification_type":{"type":"string","enum":["info","warning","alert"],"title":"Notification Type","default":"info"},"sender_service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Service"}},"type":"object","required":["address","content"],"title":"BridgeBroadcastRequest","description":"Request to broadcast a message to multiple recipients.\n\nUsed by the bridge for announcements and notifications."},"cbapp__BridgeBroadcastResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"recipient_count":{"type":"integer","title":"Recipient Count","default":0},"message_ids":{"items":{"type":"string"},"type":"array","title":"Message Ids"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"}},"type":"object","required":["success"],"title":"BridgeBroadcastResponse","description":"Response from bridge broadcast endpoint."},"cbapp__BridgePublishRequest":{"properties":{"room":{"type":"string","title":"Room"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"content_type":{"type":"string","enum":["text","object","system"],"title":"Content Type","default":"text"},"object_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Object Type"},"object_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Object Data"},"sender_service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Service"}},"type":"object","required":["room"],"title":"BridgePublishRequest","description":"Request to publish a message via semantic address.\n\nUsed by the bridge for flexible room addressing."},"cbapp__BridgePublishResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Id"},"room_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Id"},"sequence_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sequence Number"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["success"],"title":"BridgePublishResponse","description":"Response from bridge publish endpoint."},"cbapp__BulkAddPersonsRequest":{"properties":{"person_ids":{"items":{"type":"string"},"type":"array","title":"Person Ids"}},"type":"object","required":["person_ids"],"title":"BulkAddPersonsRequest","description":"Request to add multiple persons to a segment."},"cbapp__BulkImportRequest":{"properties":{"file_path":{"type":"string","title":"File Path"},"mappings":{"additionalProperties":true,"type":"object","title":"Mappings"},"batch_id":{"type":"string","title":"Batch Id"},"list_name":{"type":"string","title":"List Name"},"skip_duplicates":{"type":"boolean","title":"Skip Duplicates","default":true}},"type":"object","required":["file_path","mappings","batch_id","list_name"],"title":"BulkImportRequest","description":"Request model for direct DuckDB bulk import from CSV."},"cbapp__BulkImportResult":{"properties":{"batch_id":{"type":"string","title":"Batch Id"},"list_name":{"type":"string","title":"List Name"},"imported":{"type":"integer","title":"Imported"},"duplicates_skipped":{"type":"integer","title":"Duplicates Skipped"},"tag_name":{"type":"string","title":"Tag Name"},"duration_seconds":{"type":"number","title":"Duration Seconds"}},"type":"object","required":["batch_id","list_name","imported","duplicates_skipped","tag_name","duration_seconds"],"title":"BulkImportResult","description":"Result of a bulk import operation."},"cbapp__ChatAction":{"properties":{"label":{"type":"string","title":"Label"},"action":{"type":"string","title":"Action"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["label","action"],"title":"ChatAction","description":"An action button to display in the chat response."},"cbapp__ChatMessage":{"properties":{"role":{"type":"string","enum":["user","assistant"],"title":"Role"},"content":{"type":"string","title":"Content"}},"type":"object","required":["role","content"],"title":"ChatMessage","description":"A message in the chat history."},"cbapp__ClarificationQuestion":{"properties":{"question":{"type":"string","title":"Question"},"options":{"items":{"type":"string"},"type":"array","title":"Options"},"field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field"}},"type":"object","required":["question"],"title":"ClarificationQuestion","description":"A question to ask the user for clarification."},"cbapp__Communication":{"properties":{"id":{"type":"string","title":"Id"},"channel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel"},"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"person_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Person Id"},"recipient_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Address"},"recipient_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Name"},"recipient_meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Recipient Meta"},"sender_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Address"},"sender_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Name"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html"},"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id"},"template_vars":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Vars"},"attachments":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Attachments"},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority","default":3},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider"},"provider_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Message Id"},"provider_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Status"},"retry_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Retry Count","default":0},"max_retries":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Retries","default":3},"retry_after":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Retry After"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"delivery_log":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Delivery Log"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"queued_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Queued At"},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At"},"delivered_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Delivered At"},"failed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Failed At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id"},"campaign_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Id"},"segment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Segment Id"},"batch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Batch Id"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["id"],"title":"Communication","description":"Complete communication model with all data."},"cbapp__CommunicationCreate":{"properties":{"channel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel"},"direction":{"type":"string","title":"Direction","default":"outbound"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"person_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Person Id"},"recipient_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Address"},"recipient_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Name"},"recipient_meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Recipient Meta"},"sender_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Address"},"sender_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Name"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html"},"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id"},"template_vars":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Vars"},"attachments":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Attachments"},"priority":{"type":"integer","title":"Priority","default":3},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"},"max_retries":{"type":"integer","title":"Max Retries","default":3},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id"},"campaign_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Id"},"segment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Segment Id"},"batch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Batch Id"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","title":"CommunicationCreate","description":"Model for creating a new communication."},"cbapp__CommunicationQueue":{"properties":{"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority"},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"}},"type":"object","title":"CommunicationQueue","description":"Model for queueing a communication."},"cbapp__CommunicationUpdate":{"properties":{"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html"},"template_vars":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Vars"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority"},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"},"recipient_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Address"},"recipient_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Name"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","title":"CommunicationUpdate","description":"Model for updating an existing communication."},"cbapp__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbapp__Conversation":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"is_public":{"type":"boolean","title":"Is Public","default":true},"id":{"type":"string","title":"Id"},"share_id":{"type":"string","title":"Share Id"},"message_count":{"type":"integer","title":"Message Count","default":0},"total_tokens_used":{"type":"integer","title":"Total Tokens Used","default":0},"last_message_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Message At"},"is_archived":{"type":"boolean","title":"Is Archived","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","required":["id","share_id","created_at","updated_at"],"title":"Conversation","description":"Full conversation model with metadata."},"cbapp__ConversationListItem":{"properties":{"id":{"type":"string","title":"Id"},"share_id":{"type":"string","title":"Share Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"message_count":{"type":"integer","title":"Message Count","default":0},"total_tokens_used":{"type":"integer","title":"Total Tokens Used","default":0},"last_message_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Message At"},"is_public":{"type":"boolean","title":"Is Public","default":true},"created_at":{"type":"string","format":"date-time","title":"Created At"},"share_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Url"}},"type":"object","required":["id","share_id","created_at"],"title":"ConversationListItem","description":"Lightweight conversation item for list views."},"cbapp__ConversationListResponse":{"properties":{"conversations":{"items":{"$ref":"#/components/schemas/cbapp__ConversationListItem"},"type":"array","title":"Conversations"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["conversations","total"],"title":"ConversationListResponse","description":"Response model for conversation list endpoint."},"cbapp__ConversationMessage":{"properties":{"role":{"type":"string","enum":["user","assistant"],"title":"Role"},"content":{"type":"string","title":"Content"},"response_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Type"},"response_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Response Data"},"actions":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Actions"},"questions":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Questions"},"sql_query":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sql Query"},"debug_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Debug Info"},"tokens_used":{"type":"integer","title":"Tokens Used","default":0},"execution_time_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Execution Time Ms"},"id":{"type":"string","title":"Id"},"conversation_id":{"type":"string","title":"Conversation Id"},"sequence_number":{"type":"integer","title":"Sequence Number"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["role","content","id","conversation_id","sequence_number","created_at"],"title":"ConversationMessage","description":"Full conversation message model with all fields."},"cbapp__ConversationUpdate":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"is_public":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Public"},"is_archived":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Archived"}},"type":"object","title":"ConversationUpdate","description":"Model for updating a conversation."},"cbapp__ConversationWithMessages":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"is_public":{"type":"boolean","title":"Is Public","default":true},"id":{"type":"string","title":"Id"},"share_id":{"type":"string","title":"Share Id"},"message_count":{"type":"integer","title":"Message Count","default":0},"total_tokens_used":{"type":"integer","title":"Total Tokens Used","default":0},"last_message_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Message At"},"is_archived":{"type":"boolean","title":"Is Archived","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"messages":{"items":{"$ref":"#/components/schemas/cbapp__ConversationMessage"},"type":"array","title":"Messages"},"share_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Url"}},"type":"object","required":["id","share_id","created_at","updated_at"],"title":"ConversationWithMessages","description":"Conversation with all its messages."},"cbapp__CrawlRequest":{"properties":{"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url"},"prompt_type":{"type":"string","title":"Prompt Type","description":"Analysis type: investigative, question_answering, competitive_analysis, trend_analysis","default":"investigative"},"max_urls":{"type":"integer","maximum":1000.0,"minimum":1.0,"title":"Max Urls","description":"Maximum URLs to crawl","default":100},"skip_embeddings":{"type":"boolean","title":"Skip Embeddings","description":"Skip embedding generation","default":false},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic routing"}},"type":"object","required":["url"],"title":"CrawlRequest","description":"Request to start a crawl job from a URL."},"cbapp__DebugInfo":{"properties":{"request_id":{"type":"string","title":"Request Id"},"claude_model":{"type":"string","title":"Claude Model"},"claude_tokens_used":{"type":"integer","title":"Claude Tokens Used","default":0},"tools_called":{"items":{"type":"string"},"type":"array","title":"Tools Called"},"execution_time_ms":{"type":"integer","title":"Execution Time Ms","default":0},"logs":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Logs"}},"type":"object","required":["request_id","claude_model"],"title":"DebugInfo","description":"Debug information returned when debug mode is enabled."},"cbapp__DeltaSyncAction":{"properties":{"id":{"type":"string","title":"Id"},"person_id":{"type":"string","title":"Person Id"},"action_type":{"type":"string","title":"Action Type"},"action_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Result"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"survey_responses":{"anyOf":[{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object"},{"type":"null"}],"title":"Survey Responses"},"logged_at":{"type":"string","format":"date-time","title":"Logged At"},"synced_at":{"type":"string","format":"date-time","title":"Synced At"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"user_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Name"}},"type":"object","required":["id","person_id","action_type","logged_at","synced_at"],"title":"DeltaSyncAction","description":"An action returned by delta sync endpoint."},"cbapp__DeltaSyncResponse":{"properties":{"actions":{"items":{"$ref":"#/components/schemas/cbapp__DeltaSyncAction"},"type":"array","title":"Actions"},"has_more":{"type":"boolean","title":"Has More"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["actions","has_more"],"title":"DeltaSyncResponse","description":"Response from delta sync endpoint for mobile catch-up."},"cbapp__EmbeddingRequest":{"properties":{"text":{"type":"string","maxLength":10000,"minLength":1,"title":"Text","description":"Text to embed"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Ollama model to use","default":"nomic-embed-text:latest"},"normalize":{"type":"boolean","title":"Normalize","description":"Whether to normalize the embedding","default":true}},"type":"object","required":["text"],"title":"EmbeddingRequest","description":"Request model for embedding generation"},"cbapp__EmbeddingResponse":{"properties":{"embedding":{"items":{"type":"number"},"type":"array","title":"Embedding","description":"The generated embedding vector"},"dimension":{"type":"integer","title":"Dimension","description":"Dimension of the embedding"},"cached":{"type":"boolean","title":"Cached","description":"Whether result was served from cache","default":false},"processing_time_ms":{"type":"integer","title":"Processing Time Ms","description":"Processing time in milliseconds"}},"type":"object","required":["embedding","dimension","processing_time_ms"],"title":"EmbeddingResponse","description":"Response model for embedding generation"},"cbapp__EmbeddingStats":{"properties":{"total_requests":{"type":"integer","title":"Total Requests"},"cache_hits":{"type":"integer","title":"Cache Hits"},"cache_misses":{"type":"integer","title":"Cache Misses"},"average_processing_time_ms":{"type":"number","title":"Average Processing Time Ms"},"ollama_status":{"type":"string","title":"Ollama Status"}},"type":"object","required":["total_requests","cache_hits","cache_misses","average_processing_time_ms","ollama_status"],"title":"EmbeddingStats","description":"Statistics about the embedding service"},"cbapp__Event":{"properties":{"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"location":{"type":"string","title":"Location"},"start_datetime":{"type":"string","format":"date-time","title":"Start Datetime"},"end_datetime":{"type":"string","format":"date-time","title":"End Datetime"},"is_paid":{"type":"boolean","title":"Is Paid","default":false},"ticket_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ticket Price"},"max_capacity":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Capacity"},"id":{"type":"string","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"type":"string","title":"Created By"},"registrations":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Registrations","default":[]}},"type":"object","required":["title","location","start_datetime","end_datetime","id","created_at","created_by"],"title":"Event","description":"Complete event model with all data."},"cbapp__EventCreate":{"properties":{"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"location":{"type":"string","title":"Location"},"start_datetime":{"type":"string","format":"date-time","title":"Start Datetime"},"end_datetime":{"type":"string","format":"date-time","title":"End Datetime"},"is_paid":{"type":"boolean","title":"Is Paid","default":false},"ticket_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ticket Price"},"max_capacity":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Capacity"}},"type":"object","required":["title","location","start_datetime","end_datetime"],"title":"EventCreate","description":"Model for creating a new event."},"cbapp__EventPageCreate":{"properties":{"slug":{"type":"string","maxLength":100,"minLength":3,"title":"Slug"},"template_id":{"type":"string","title":"Template Id","default":"classic"},"header_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Header Text"},"description_markdown":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description Markdown"},"footer_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Footer Text"},"form_fields":{"$ref":"#/components/schemas/cbapp__FormFields"},"registration_opens_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Registration Opens At"},"registration_closes_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Registration Closes At"}},"type":"object","required":["slug"],"title":"EventPageCreate","description":"Model for creating a new event page."},"cbapp__EventPagePublicView":{"properties":{"slug":{"type":"string","title":"Slug"},"template_id":{"type":"string","title":"Template Id"},"header_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Header Text"},"description_markdown":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description Markdown"},"footer_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Footer Text"},"form_fields":{"$ref":"#/components/schemas/cbapp__FormFields"},"registration_opens_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Registration Opens At"},"registration_closes_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Registration Closes At"},"event_title":{"type":"string","title":"Event Title"},"event_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Description"},"event_location":{"type":"string","title":"Event Location"},"event_start":{"type":"string","format":"date-time","title":"Event Start"},"event_end":{"type":"string","format":"date-time","title":"Event End"},"event_is_paid":{"type":"boolean","title":"Event Is Paid","default":false},"event_ticket_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Event Ticket Price"},"event_max_capacity":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Event Max Capacity"},"is_registration_open":{"type":"boolean","title":"Is Registration Open","default":true},"spots_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Spots Remaining"},"registration_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registration Message"}},"type":"object","required":["slug","template_id","form_fields","event_title","event_location","event_start","event_end"],"title":"EventPagePublicView","description":"Public view of an event page (no admin-only fields)."},"cbapp__EventPageStatusUpdate":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"EventPageStatusUpdate","description":"Model for updating event page status."},"cbapp__EventPageUpdate":{"properties":{"slug":{"anyOf":[{"type":"string","maxLength":100,"minLength":3},{"type":"null"}],"title":"Slug"},"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id"},"header_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Header Text"},"description_markdown":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description Markdown"},"footer_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Footer Text"},"form_fields":{"anyOf":[{"$ref":"#/components/schemas/cbapp__FormFields"},{"type":"null"}]},"registration_opens_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Registration Opens At"},"registration_closes_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Registration Closes At"}},"type":"object","title":"EventPageUpdate","description":"Model for updating an existing event page."},"cbapp__EventPageWithUrls":{"properties":{"slug":{"type":"string","maxLength":100,"minLength":3,"title":"Slug"},"template_id":{"type":"string","title":"Template Id","default":"classic"},"header_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Header Text"},"description_markdown":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description Markdown"},"footer_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Footer Text"},"form_fields":{"$ref":"#/components/schemas/cbapp__FormFields"},"registration_opens_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Registration Opens At"},"registration_closes_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Registration Closes At"},"id":{"type":"string","title":"Id"},"event_id":{"type":"string","title":"Event Id"},"status":{"type":"string","title":"Status","default":"draft"},"preview_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Token"},"segment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Segment Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"},"public_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Url"},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Url"},"registration_count":{"type":"integer","title":"Registration Count","default":0}},"type":"object","required":["slug","id","event_id","created_at"],"title":"EventPageWithUrls","description":"Event page with computed URL fields."},"cbapp__EventUpdate":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location"},"start_datetime":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Datetime"},"end_datetime":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Datetime"},"is_paid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Paid"},"ticket_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ticket Price"},"max_capacity":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Capacity"}},"type":"object","title":"EventUpdate","description":"Model for updating an existing event."},"cbapp__FetchRequest":{"properties":{"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url"},"include_links":{"type":"boolean","title":"Include Links","description":"Include extracted links","default":false},"include_screenshot":{"type":"boolean","title":"Include Screenshot","description":"Include page screenshot","default":false},"timeout_seconds":{"type":"integer","maximum":120.0,"minimum":5.0,"title":"Timeout Seconds","description":"Timeout in seconds","default":30},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic routing: us, us:ca, de, gb, jp"}},"type":"object","required":["url"],"title":"FetchRequest","description":"Request to fetch a single URL."},"cbapp__FieldAction":{"properties":{"id":{"type":"string","title":"Id"},"person_id":{"type":"string","title":"Person Id"},"action_type":{"type":"string","title":"Action Type","description":"door, call, text, canvass"},"action_result":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Result","description":"contacted, not_home, refused, moved, deceased"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"survey_responses":{"anyOf":[{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object"},{"type":"null"}],"title":"Survey Responses"},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds"},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"},"logged_at":{"type":"string","format":"date-time","title":"Logged At"}},"type":"object","required":["id","person_id","action_type","logged_at"],"title":"FieldAction","description":"A single field action recorded during canvassing."},"cbapp__FileItem":{"properties":{"name":{"type":"string","title":"Name"},"path":{"type":"string","title":"Path"},"is_folder":{"type":"boolean","title":"Is Folder"},"size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size"},"modified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Modified At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type"}},"type":"object","required":["name","path","is_folder"],"title":"FileItem","description":"Represents a file or folder in the file system."},"cbapp__FileUploadResponse":{"properties":{"name":{"type":"string","title":"Name"},"path":{"type":"string","title":"Path"},"size":{"type":"integer","title":"Size"},"content_type":{"type":"string","title":"Content Type"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","required":["name","path","size","content_type"],"title":"FileUploadResponse","description":"Response model for file upload."},"cbapp__FilesHealthResponse":{"properties":{"status":{"type":"string","title":"Status"},"bucket":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bucket"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["status"],"title":"FilesHealthResponse","description":"Response model for files service health check."},"cbapp__FilterOptions":{"properties":{"counties":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Counties"},"cities":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Cities"},"ethnicities":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Ethnicities"},"religions":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Religions"},"congressional_districts":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Congressional Districts"},"score_ranges":{"additionalProperties":{"additionalProperties":{"type":"integer"},"type":"object"},"type":"object","title":"Score Ranges"},"birth_year_range":{"additionalProperties":{"type":"integer"},"type":"object","title":"Birth Year Range"},"total_voters":{"type":"integer","title":"Total Voters"}},"type":"object","required":["counties","cities","ethnicities","religions","congressional_districts","score_ranges","birth_year_range","total_voters"],"title":"FilterOptions","description":"Filter options for search form."},"cbapp__FolderContents":{"properties":{"path":{"type":"string","title":"Path"},"items":{"items":{"$ref":"#/components/schemas/cbapp__FileItem"},"type":"array","title":"Items"},"can_write":{"type":"boolean","title":"Can Write","default":true},"can_delete_all":{"type":"boolean","title":"Can Delete All","default":false}},"type":"object","required":["path","items"],"title":"FolderContents","description":"Response model for folder listing."},"cbapp__FolderCreate":{"properties":{"path":{"type":"string","title":"Path","description":"Parent folder path"},"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"New folder name"}},"type":"object","required":["path","name"],"title":"FolderCreate","description":"Request model for creating a folder."},"cbapp__FolderRoot":{"properties":{"name":{"type":"string","title":"Name"},"path":{"type":"string","title":"Path"},"icon":{"type":"string","title":"Icon","default":"folder"}},"type":"object","required":["name","path"],"title":"FolderRoot","description":"Represents an accessible root folder."},"cbapp__FormFieldConfig":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":true},"required":{"type":"boolean","title":"Required","default":false}},"type":"object","title":"FormFieldConfig","description":"Configuration for a single form field."},"cbapp__FormFields":{"properties":{"first_name":{"$ref":"#/components/schemas/cbapp__FormFieldConfig","default":{"enabled":true,"required":true}},"last_name":{"$ref":"#/components/schemas/cbapp__FormFieldConfig","default":{"enabled":true,"required":true}},"email":{"$ref":"#/components/schemas/cbapp__FormFieldConfig","default":{"enabled":true,"required":true}},"phone":{"$ref":"#/components/schemas/cbapp__FormFieldConfig","default":{"enabled":true,"required":false}},"address1":{"$ref":"#/components/schemas/cbapp__FormFieldConfig","default":{"enabled":false,"required":false}},"address2":{"$ref":"#/components/schemas/cbapp__FormFieldConfig","default":{"enabled":false,"required":false}},"city":{"$ref":"#/components/schemas/cbapp__FormFieldConfig","default":{"enabled":false,"required":false}},"state":{"$ref":"#/components/schemas/cbapp__FormFieldConfig","default":{"enabled":false,"required":false}},"zip":{"$ref":"#/components/schemas/cbapp__FormFieldConfig","default":{"enabled":false,"required":false}}},"type":"object","title":"FormFields","description":"Configuration for all registration form fields."},"cbapp__Goal":{"properties":{"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"target_value":{"type":"integer","title":"Target Value"},"goal_type":{"type":"string","title":"Goal Type"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"id":{"type":"string","title":"Id"},"current_value":{"type":"integer","title":"Current Value","default":0},"status":{"type":"string","title":"Status","default":"active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"type":"string","title":"Created By"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["name","target_value","goal_type","id","created_at","created_by"],"title":"Goal","description":"Complete goal model with all data."},"cbapp__GoalCreate":{"properties":{"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"target_value":{"type":"integer","title":"Target Value"},"goal_type":{"type":"string","title":"Goal Type"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"current_value":{"type":"integer","title":"Current Value","default":0}},"type":"object","required":["name","target_value","goal_type"],"title":"GoalCreate","description":"Model for creating a new goal."},"cbapp__GoalStats":{"properties":{"active_count":{"type":"integer","title":"Active Count","default":0},"completed_count":{"type":"integer","title":"Completed Count","default":0},"expired_count":{"type":"integer","title":"Expired Count","default":0},"total_target":{"type":"integer","title":"Total Target","default":0},"total_current":{"type":"integer","title":"Total Current","default":0}},"type":"object","title":"GoalStats","description":"Aggregate statistics for goals."},"cbapp__GoalUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"target_value":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Target Value"},"current_value":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Current Value"},"goal_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Goal Type"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},"type":"object","title":"GoalUpdate","description":"Model for updating an existing goal."},"cbapp__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbapp__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbapp__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbapp__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbapp__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbapp__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbapp__HelloPageConfig":{"properties":{"template_id":{"type":"string","title":"Template Id","description":"Template: simple, detailed, or campaign","default":"simple"},"hero_image_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hero Image Path"},"welcome_text":{"type":"string","maxLength":500,"title":"Welcome Text","default":"Thank you for your support!"},"campaign_blurb":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Campaign Blurb"},"footer_text":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Footer Text"},"show_candidate_name":{"type":"boolean","title":"Show Candidate Name","default":true},"show_campaign_logo":{"type":"boolean","title":"Show Campaign Logo","default":true},"show_social_links":{"type":"boolean","title":"Show Social Links","default":false},"social_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Social Links","description":"Dict of platform -> URL (e.g., {'twitter': 'https://...'})"},"custom_primary_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Primary Color","description":"Hex color code (e.g., #FF5733)"},"custom_secondary_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Secondary Color","description":"Hex color code (e.g., #33FF57)"},"is_active":{"type":"boolean","title":"Is Active","default":true},"id":{"type":"string","title":"Id","default":"default"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"}},"type":"object","title":"HelloPageConfig","description":"Full hello page config model."},"cbapp__HelloPageConfigUpdate":{"properties":{"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id"},"hero_image_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hero Image Path"},"welcome_text":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Welcome Text"},"campaign_blurb":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Campaign Blurb"},"footer_text":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Footer Text"},"show_candidate_name":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Show Candidate Name"},"show_campaign_logo":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Show Campaign Logo"},"show_social_links":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Show Social Links"},"social_links":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Social Links"},"custom_primary_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Primary Color"},"custom_secondary_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Secondary Color"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"HelloPageConfigUpdate","description":"Request to update hello page config."},"cbapp__I360ImportRequest":{"properties":{"svids":{"items":{"type":"integer"},"type":"array","title":"Svids","description":"List of voter SVIDs to import"},"tag_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag Name","description":"Optional tag to apply to imported persons"}},"type":"object","required":["svids"],"title":"I360ImportRequest","description":"Request to import i360 voters to audience."},"cbapp__I360ImportResponse":{"properties":{"imported":{"type":"integer","title":"Imported"},"skipped":{"type":"integer","title":"Skipped"},"errors":{"type":"integer","title":"Errors"},"batch_id":{"type":"string","title":"Batch Id"},"persons_created":{"items":{"type":"string"},"type":"array","title":"Persons Created"},"message":{"type":"string","title":"Message"}},"type":"object","required":["imported","skipped","errors","batch_id","persons_created","message"],"title":"I360ImportResponse","description":"Response from i360 import."},"cbapp__ImportOptions":{"properties":{"skip_duplicates":{"type":"boolean","title":"Skip Duplicates","default":true},"list_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"List Name"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","default":[]}},"type":"object","title":"ImportOptions","description":"Options for executing an import."},"cbapp__IntegrationList":{"properties":{"integrations":{"items":{"$ref":"#/components/schemas/cbapp__IntegrationStatus"},"type":"array","title":"Integrations"}},"type":"object","required":["integrations"],"title":"IntegrationList","description":"List of all integrations."},"cbapp__IntegrationSetting":{"properties":{"key":{"type":"string","title":"Key"},"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value"},"display_value":{"type":"string","title":"Display Value"},"is_secret":{"type":"boolean","title":"Is Secret"},"env_key":{"type":"string","title":"Env Key"}},"type":"object","required":["key","value","display_value","is_secret","env_key"],"title":"IntegrationSetting","description":"A single setting within an integration."},"cbapp__IntegrationStatus":{"properties":{"integration":{"type":"string","title":"Integration"},"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"status":{"type":"string","title":"Status"},"settings":{"items":{"$ref":"#/components/schemas/cbapp__IntegrationSetting"},"type":"array","title":"Settings"}},"type":"object","required":["integration","name","description","status","settings"],"title":"IntegrationStatus","description":"Status and settings for an integration."},"cbapp__JobProgressData":{"properties":{"status":{"type":"string","enum":["queued","running","completed","failed","cancelled"],"title":"Status"},"progress":{"type":"number","title":"Progress","default":0.0},"current_step":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Step"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"eta":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Eta"},"can_cancel":{"type":"boolean","title":"Can Cancel","default":false},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["status"],"title":"JobProgressData","description":"Data payload for job progress system messages."},"cbapp__LogEntryData":{"properties":{"level":{"type":"string","enum":["debug","info","warning","error","critical"],"title":"Level"},"service":{"type":"string","title":"Service"},"message":{"type":"string","title":"Message"},"details":{"additionalProperties":true,"type":"object","title":"Details"},"timestamp":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Timestamp"},"trace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trace Id"}},"type":"object","required":["level","service","message"],"title":"LogEntryData","description":"Data payload for log entry system messages."},"cbapp__LogoDeleteResponse":{"properties":{"message":{"type":"string","title":"Message"},"logo_type":{"type":"string","title":"Logo Type"}},"type":"object","required":["message","logo_type"],"title":"LogoDeleteResponse","description":"Response model for logo deletion."},"cbapp__LogoInfo":{"properties":{"url":{"type":"string","title":"Url"},"width":{"type":"integer","title":"Width"},"height":{"type":"integer","title":"Height"},"description":{"type":"string","title":"Description"},"exists":{"type":"boolean","title":"Exists","default":true}},"type":"object","required":["url","width","height","description"],"title":"LogoInfo","description":"Information about a single logo."},"cbapp__LogoUploadResponse":{"properties":{"message":{"type":"string","title":"Message"},"logo_type":{"type":"string","title":"Logo Type"},"path":{"type":"string","title":"Path"},"filename":{"type":"string","title":"Filename"}},"type":"object","required":["message","logo_type","path","filename"],"title":"LogoUploadResponse","description":"Response model for logo upload."},"cbapp__LogosResponse":{"properties":{"logo_full":{"$ref":"#/components/schemas/cbapp__LogoInfo"},"logo_name":{"$ref":"#/components/schemas/cbapp__LogoInfo"},"logo_cropped":{"$ref":"#/components/schemas/cbapp__LogoInfo"},"logo_icon":{"$ref":"#/components/schemas/cbapp__LogoInfo"},"logo_mobile":{"$ref":"#/components/schemas/cbapp__LogoInfo"}},"type":"object","required":["logo_full","logo_name","logo_cropped","logo_icon","logo_mobile"],"title":"LogosResponse","description":"Response model for logo listing."},"cbapp__MappingUpdate":{"properties":{"mappings":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Mappings"}},"type":"object","required":["mappings"],"title":"MappingUpdate","description":"Request to update field mappings."},"cbapp__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbapp__Patch":{"properties":{"id":{"type":"string","title":"Id"},"person_id":{"type":"string","title":"Person Id"},"field_name":{"type":"string","title":"Field Name"},"original_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Original Value"},"proposed_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proposed Value"},"status":{"type":"string","title":"Status","default":"pending"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"reviewed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reviewed By"},"reviewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reviewed At"},"review_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Notes"},"person_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Person Name"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name"},"reviewed_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reviewed By Name"}},"type":"object","required":["id","person_id","field_name","created_at"],"title":"Patch","description":"Full patch model with all fields."},"cbapp__PatchBulkCreate":{"properties":{"patches":{"items":{"$ref":"#/components/schemas/cbapp__PatchCreate"},"type":"array","title":"Patches"}},"type":"object","required":["patches"],"title":"PatchBulkCreate","description":"Model for creating multiple patches at once."},"cbapp__PatchCreate":{"properties":{"field_name":{"type":"string","title":"Field Name"},"proposed_value":{"type":"string","title":"Proposed Value"}},"type":"object","required":["field_name","proposed_value"],"title":"PatchCreate","description":"Model for creating a single patch."},"cbapp__PatchReview":{"properties":{"status":{"type":"string","title":"Status"},"review_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Notes"}},"type":"object","required":["status"],"title":"PatchReview","description":"Model for reviewing (approving/rejecting) a patch."},"cbapp__PatchStats":{"properties":{"pending":{"type":"integer","title":"Pending","default":0},"approved":{"type":"integer","title":"Approved","default":0},"rejected":{"type":"integer","title":"Rejected","default":0},"total":{"type":"integer","title":"Total","default":0}},"type":"object","title":"PatchStats","description":"Statistics about patches."},"cbapp__Person":{"properties":{"first_name":{"type":"string","title":"First Name","default":""},"last_name":{"type":"string","title":"Last Name","default":""},"address1":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address1"},"address2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address2"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zip"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"congressional_district":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Congressional District"},"home_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Phone"},"cell_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cell Phone"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"precinct":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct"},"precinct_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct Number"},"precinct_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct Status"},"precinct_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct Role"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"import_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Import Source"},"import_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Import Date"},"import_batch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Import Batch Id"},"import_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Import Filename"},"original_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Original Data"},"id":{"type":"string","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"},"tags":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Tags","default":[]},"custom_fields":{"additionalProperties":true,"type":"object","title":"Custom Fields","default":{}},"whip_status":{"type":"string","title":"Whip Status","default":"Unknown"},"communications":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Communications","default":[]},"email_locate":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Email Locate"},"phone_locate":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Phone Locate"},"email_data":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Email Data"},"p2p_data":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"P2P Data"},"i360_data_mobile":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"I360 Data Mobile"},"i360_data_landline":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"I360 Data Landline"},"i360_data_email":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"I360 Data Email"}},"type":"object","required":["id","created_at","updated_at"],"title":"Person","description":"Complete person model with all data.","example":{"address1":"123 Main Street","cell_phone":"313-555-1234","city":"Detroit","county":"Wayne","created_at":"2025-01-01T00:00:00","custom_fields":{"Party":"Republican","Voter Score":"85"},"email":"jane.smith@example.com","first_name":"Jane","id":"488e7825-b5ae-4436-ac69-5527fb95ca2e","last_name":"Smith","state":"MI","tags":[{"category":"Priority","id":"tag-uuid","name":"VIP"}],"updated_at":"2025-01-01T00:00:00","whip_status":"Soft Support","zip":"48201"}},"cbapp__PersonCreate":{"properties":{"first_name":{"type":"string","title":"First Name","default":""},"last_name":{"type":"string","title":"Last Name","default":""},"address1":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address1"},"address2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address2"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zip"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"congressional_district":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Congressional District"},"home_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Phone"},"cell_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cell Phone"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"precinct":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct"},"precinct_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct Number"},"precinct_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct Status"},"precinct_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct Role"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"import_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Import Source"},"import_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Import Date"},"import_batch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Import Batch Id"},"import_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Import Filename"},"original_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Original Data"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"custom_fields":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Custom Fields"},"whip_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Whip Status","default":"Unknown"}},"type":"object","title":"PersonCreate","description":"Model for creating a new person.","example":{"address1":"123 Main Street","cell_phone":"313-555-1234","city":"Detroit","county":"Wayne","custom_fields":{"Party":"Republican","Voter Score":"85"},"email":"jane.smith@example.com","first_name":"Jane","last_name":"Smith","state":"MI","tags":["VIP","Volunteer"],"whip_status":"Soft Support","zip":"48201"}},"cbapp__PersonUpdate":{"properties":{"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"address1":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address1"},"address2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address2"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zip"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"congressional_district":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Congressional District"},"home_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Phone"},"cell_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cell Phone"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"custom_fields":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Custom Fields"},"whip_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Whip Status"},"import_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Import Source"},"import_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Import Date"},"import_batch_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Import Batch Id"},"import_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Import Filename"},"original_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Original Data"}},"type":"object","title":"PersonUpdate","description":"Model for updating an existing person."},"cbapp__PersonsResponse":{"properties":{"persons":{"items":{"$ref":"#/components/schemas/cbapp__Person"},"type":"array","title":"Persons"},"total":{"type":"integer","title":"Total"},"skip":{"type":"integer","title":"Skip"},"limit":{"type":"integer","title":"Limit"},"has_next":{"type":"boolean","title":"Has Next"},"page":{"type":"integer","title":"Page"},"total_pages":{"type":"integer","title":"Total Pages"}},"type":"object","required":["persons","total","skip","limit","has_next","page","total_pages"],"title":"PersonsResponse","description":"Paginated response wrapper for person list endpoints."},"cbapp__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbapp__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbapp__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbapp__PublicRegistration":{"properties":{"first_name":{"type":"string","maxLength":100,"minLength":1,"title":"First Name"},"last_name":{"type":"string","maxLength":100,"minLength":1,"title":"Last Name"},"email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Phone"},"address1":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Address1"},"address2":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Address2"},"city":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"City"},"state":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"State"},"zip":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Zip"},"id":{"type":"string","title":"Id"},"event_page_id":{"type":"string","title":"Event Page Id"},"event_registration_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Registration Id"},"resolution_status":{"type":"string","title":"Resolution Status","default":"pending"},"person_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Person Id"},"match_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Match Method"},"submitted_at":{"type":"string","format":"date-time","title":"Submitted At"},"processed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Processed At"},"source_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Ip"}},"type":"object","required":["first_name","last_name","id","event_page_id","submitted_at"],"title":"PublicRegistration","description":"Complete public registration model."},"cbapp__PublicRegistrationCreate":{"properties":{"first_name":{"type":"string","maxLength":100,"minLength":1,"title":"First Name"},"last_name":{"type":"string","maxLength":100,"minLength":1,"title":"Last Name"},"email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Phone"},"address1":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Address1"},"address2":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Address2"},"city":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"City"},"state":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"State"},"zip":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Zip"}},"type":"object","required":["first_name","last_name"],"title":"PublicRegistrationCreate","description":"Model for public registration form submission."},"cbapp__QuestionCreate":{"properties":{"type":{"type":"string","title":"Type","description":"Question type: checkbox, single_select, multi_select"},"label":{"type":"string","minLength":1,"title":"Label","description":"Short label for the question"},"text":{"type":"string","minLength":1,"title":"Text","description":"Full question text"},"help_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help Text"},"options":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Options","description":"Options for select types"},"required":{"type":"boolean","title":"Required","default":false}},"type":"object","required":["type","label","text"],"title":"QuestionCreate","description":"Model for creating a new question."},"cbapp__QuestionUpdate":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"help_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help Text"},"options":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Options"},"required":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Required"}},"type":"object","title":"QuestionUpdate","description":"Model for updating a question."},"cbapp__QueueStats":{"properties":{"total":{"type":"integer","title":"Total","default":0},"by_status":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Status","default":{}},"by_channel":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Channel","default":{}},"by_priority":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Priority","default":{}},"pending":{"type":"integer","title":"Pending","default":0},"processing":{"type":"integer","title":"Processing","default":0},"failed_today":{"type":"integer","title":"Failed Today","default":0},"sent_today":{"type":"integer","title":"Sent Today","default":0}},"type":"object","title":"QueueStats","description":"Statistics for the communication queue."},"cbapp__Registration":{"properties":{"person_id":{"type":"string","title":"Person Id"},"status":{"type":"string","title":"Status","default":"registered"},"id":{"type":"string","title":"Id"},"event_id":{"type":"string","title":"Event Id"},"registration_time":{"type":"string","format":"date-time","title":"Registration Time"},"check_in_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Check In Time"},"payment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"type":"string","title":"Created By"}},"type":"object","required":["person_id","id","event_id","registration_time","created_at","created_by"],"title":"Registration","description":"Complete registration model with all data."},"cbapp__RegistrationCreate":{"properties":{"person_id":{"type":"string","title":"Person Id"},"status":{"type":"string","title":"Status","default":"registered"},"payment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Id"}},"type":"object","required":["person_id"],"title":"RegistrationCreate","description":"Model for creating a new registration."},"cbapp__RegistrationResponse":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"type":"string","title":"Message","default":"Thank you for registering!"},"registration_id":{"type":"string","title":"Registration Id"},"event_title":{"type":"string","title":"Event Title"},"event_date":{"type":"string","format":"date-time","title":"Event Date"},"event_location":{"type":"string","title":"Event Location"}},"type":"object","required":["registration_id","event_title","event_date","event_location"],"title":"RegistrationResponse","description":"Response after successful registration."},"cbapp__RegistrationUpdate":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"check_in_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Check In Time"}},"type":"object","title":"RegistrationUpdate","description":"Model for updating an existing registration."},"cbapp__ResponseSubmit":{"properties":{"survey_id":{"type":"string","title":"Survey Id"},"person_id":{"type":"string","title":"Person Id","description":"The person being surveyed"},"answers":{"additionalProperties":true,"type":"object","title":"Answers","description":"Map of question_id to answer value"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"Field agent's user ID"},"user_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Name","description":"Field agent's name"},"assignment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignment Id","description":"Segment assignment ID"},"queue_item_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Queue Item Id","description":"Work queue item ID"},"segment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Segment Id","description":"Segment ID"}},"type":"object","required":["survey_id","person_id","answers"],"title":"ResponseSubmit","description":"Model for submitting a survey response."},"cbapp__SaveAsSegmentRequest":{"properties":{"name":{"type":"string","minLength":1,"title":"Name","description":"Segment name (required)"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Segment description"},"is_dynamic":{"type":"boolean","title":"Is Dynamic","description":"True for dynamic (query-based), False for static (snapshot)","default":true},"filter_criteria":{"additionalProperties":true,"type":"object","title":"Filter Criteria","description":"i360 search filter criteria"},"svids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Svids","description":"For static segments, list of voter SVIDs"}},"type":"object","required":["name","filter_criteria"],"title":"SaveAsSegmentRequest","description":"Request to save i360 search as a segment."},"cbapp__SaveAsSegmentResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"is_dynamic":{"type":"boolean","title":"Is Dynamic"},"person_count":{"type":"integer","title":"Person Count"},"message":{"type":"string","title":"Message"}},"type":"object","required":["id","name","is_dynamic","person_count","message"],"title":"SaveAsSegmentResponse","description":"Response from save as segment."},"cbapp__ScoutMessage":{"properties":{"id":{"type":"string","title":"Id"},"room_id":{"type":"string","title":"Room Id"},"sequence_number":{"type":"integer","title":"Sequence Number"},"sender_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Id"},"sender_type":{"type":"string","enum":["user","scout","system"],"title":"Sender Type","default":"user"},"sender_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Name"},"sender_avatar":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Avatar"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"content_type":{"type":"string","enum":["text","object","system"],"title":"Content Type","default":"text"},"object":{"anyOf":[{"$ref":"#/components/schemas/cbapp__ScoutObject"},{"type":"null"}]},"reply_to_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reply To Id"},"reply_to_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reply To Preview"},"is_edited":{"type":"boolean","title":"Is Edited","default":false},"is_deleted":{"type":"boolean","title":"Is Deleted","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","room_id","sequence_number","created_at"],"title":"ScoutMessage","description":"Full message model with all fields."},"cbapp__ScoutMessageCreate":{"properties":{"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"content_type":{"type":"string","enum":["text","object","system"],"title":"Content Type","default":"text"},"object":{"anyOf":[{"$ref":"#/components/schemas/cbapp__ScoutObjectCreate"},{"type":"null"}]},"reply_to_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reply To Id"}},"type":"object","title":"ScoutMessageCreate","description":"Model for creating a new message."},"cbapp__ScoutMessageListResponse":{"properties":{"messages":{"items":{"$ref":"#/components/schemas/cbapp__ScoutMessage"},"type":"array","title":"Messages"},"total":{"type":"integer","title":"Total"},"has_more":{"type":"boolean","title":"Has More"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["messages","total","has_more"],"title":"ScoutMessageListResponse","description":"Response model for message list endpoint."},"cbapp__ScoutMessageUpdate":{"properties":{"content":{"type":"string","title":"Content"}},"type":"object","required":["content"],"title":"ScoutMessageUpdate","description":"Model for editing a message."},"cbapp__ScoutObject":{"properties":{"type":{"type":"string","enum":["file","map","query","segment","person","event","log","alert","job","system_event"],"title":"Type"},"id":{"type":"string","title":"Id"},"preview":{"$ref":"#/components/schemas/cbapp__ScoutObjectPreview"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["type","id","preview"],"title":"ScoutObject","description":"Generic object that can be shared in Scout messages."},"cbapp__ScoutObjectCreate":{"properties":{"type":{"type":"string","enum":["file","map","query","segment","person","event","log","alert","job","system_event"],"title":"Type"},"id":{"type":"string","title":"Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["type","id"],"title":"ScoutObjectCreate","description":"Model for creating a shared object."},"cbapp__ScoutObjectDB":{"properties":{"id":{"type":"string","title":"Id"},"message_id":{"type":"string","title":"Message Id"},"room_id":{"type":"string","title":"Room Id"},"object_type":{"type":"string","title":"Object Type"},"object_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Object Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"preview_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Preview Data"},"full_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Full Data"},"shared_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shared By"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","message_id","room_id","object_type","created_at"],"title":"ScoutObjectDB","description":"Database model for scout_object table."},"cbapp__ScoutObjectListResponse":{"properties":{"objects":{"items":{"$ref":"#/components/schemas/cbapp__ScoutObjectDB"},"type":"array","title":"Objects"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["objects","total"],"title":"ScoutObjectListResponse","description":"Response model for shared objects in a room."},"cbapp__ScoutObjectPreview":{"properties":{"title":{"type":"string","title":"Title"},"subtitle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subtitle"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"}},"type":"object","required":["title"],"title":"ScoutObjectPreview","description":"Lightweight preview data for shared objects."},"cbapp__ScoutRoom":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"id":{"type":"string","title":"Id"},"room_type":{"type":"string","enum":["dm","group","system"],"title":"Room Type"},"is_archived":{"type":"boolean","title":"Is Archived","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"member_count":{"type":"integer","title":"Member Count","default":0},"unread_count":{"type":"integer","title":"Unread Count","default":0},"last_message":{"anyOf":[{"$ref":"#/components/schemas/cbapp__ScoutMessage"},{"type":"null"}]},"last_message_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Message At"},"is_system":{"type":"boolean","title":"Is System","default":false},"system_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"System Type"}},"type":"object","required":["id","room_type","created_at","updated_at"],"title":"ScoutRoom","description":"Full room model with metadata."},"cbapp__ScoutRoomCreate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"room_type":{"type":"string","enum":["dm","group"],"title":"Room Type","default":"group"},"member_ids":{"items":{"type":"string"},"type":"array","title":"Member Ids"}},"type":"object","title":"ScoutRoomCreate","description":"Model for creating a new room."},"cbapp__ScoutRoomListItem":{"properties":{"id":{"type":"string","title":"Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"room_type":{"type":"string","enum":["dm","group","system"],"title":"Room Type"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"member_count":{"type":"integer","title":"Member Count","default":0},"unread_count":{"type":"integer","title":"Unread Count","default":0},"last_message_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Preview"},"last_message_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Message At"},"last_message_sender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Sender"},"is_muted":{"type":"boolean","title":"Is Muted","default":false},"dm_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dm User Id"},"dm_user_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dm User Name"},"dm_user_online":{"type":"boolean","title":"Dm User Online","default":false},"is_system":{"type":"boolean","title":"Is System","default":false},"system_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"System Type"}},"type":"object","required":["id","room_type"],"title":"ScoutRoomListItem","description":"Lightweight room item for list views."},"cbapp__ScoutRoomListResponse":{"properties":{"rooms":{"items":{"$ref":"#/components/schemas/cbapp__ScoutRoomListItem"},"type":"array","title":"Rooms"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["rooms","total"],"title":"ScoutRoomListResponse","description":"Response model for room list endpoint."},"cbapp__ScoutRoomMember":{"properties":{"role":{"type":"string","enum":["owner","admin","member"],"title":"Role","default":"member"},"is_muted":{"type":"boolean","title":"Is Muted","default":false},"room_id":{"type":"string","title":"Room Id"},"user_id":{"type":"string","title":"User Id"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"joined_at":{"type":"string","format":"date-time","title":"Joined At"},"last_read_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Read At"},"last_read_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Read Message Id"},"is_online":{"type":"boolean","title":"Is Online","default":false}},"type":"object","required":["room_id","user_id","joined_at"],"title":"ScoutRoomMember","description":"Full room member model."},"cbapp__ScoutRoomMemberCreate":{"properties":{"role":{"type":"string","enum":["owner","admin","member"],"title":"Role","default":"member"},"is_muted":{"type":"boolean","title":"Is Muted","default":false},"user_id":{"type":"string","title":"User Id"}},"type":"object","required":["user_id"],"title":"ScoutRoomMemberCreate","description":"Model for adding a member to a room."},"cbapp__ScoutRoomUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"is_archived":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Archived"}},"type":"object","title":"ScoutRoomUpdate","description":"Model for updating a room."},"cbapp__ScoutRoomWithMembers":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"id":{"type":"string","title":"Id"},"room_type":{"type":"string","enum":["dm","group","system"],"title":"Room Type"},"is_archived":{"type":"boolean","title":"Is Archived","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"member_count":{"type":"integer","title":"Member Count","default":0},"unread_count":{"type":"integer","title":"Unread Count","default":0},"last_message":{"anyOf":[{"$ref":"#/components/schemas/cbapp__ScoutMessage"},{"type":"null"}]},"last_message_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Message At"},"is_system":{"type":"boolean","title":"Is System","default":false},"system_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"System Type"},"members":{"items":{"$ref":"#/components/schemas/cbapp__ScoutRoomMember"},"type":"array","title":"Members"}},"type":"object","required":["id","room_type","created_at","updated_at"],"title":"ScoutRoomWithMembers","description":"Room with full member list."},"cbapp__SearchRequest":{"properties":{"query":{"type":"string","maxLength":500,"minLength":2,"title":"Query","description":"Search query (keywords to research)"},"prompt_type":{"type":"string","title":"Prompt Type","description":"Analysis type: investigative, question_answering, competitive_analysis, trend_analysis","default":"investigative"},"max_urls":{"type":"integer","maximum":1000.0,"minimum":1.0,"title":"Max Urls","description":"Maximum URLs to crawl","default":100},"skip_embeddings":{"type":"boolean","title":"Skip Embeddings","description":"Skip embedding generation","default":false},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic routing"}},"type":"object","required":["query"],"title":"SearchRequest","description":"Request to start a keyword search job."},"cbapp__SearchResponse":{"properties":{"voters":{"items":{"$ref":"#/components/schemas/cbapp__VoterSummary"},"type":"array","title":"Voters"},"total_count":{"type":"integer","title":"Total Count"},"page":{"type":"integer","title":"Page"},"per_page":{"type":"integer","title":"Per Page"},"total_pages":{"type":"integer","title":"Total Pages"},"has_next":{"type":"boolean","title":"Has Next"},"has_prev":{"type":"boolean","title":"Has Prev"}},"type":"object","required":["voters","total_count","page","per_page","total_pages","has_next","has_prev"],"title":"SearchResponse","description":"Response model for voter search."},"cbapp__Segment":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"natural_language_query":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Natural Language Query"},"is_active":{"type":"boolean","title":"Is Active","default":true},"is_dynamic":{"type":"boolean","title":"Is Dynamic","default":true},"id":{"type":"string","title":"Id"},"generated_sql":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated Sql"},"filter_criteria":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Filter Criteria"},"person_count":{"type":"integer","title":"Person Count","default":0},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"}},"type":"object","required":["name","id","created_at","updated_at"],"title":"Segment","description":"Full segment record as stored in the database."},"cbapp__SegmentCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"natural_language_query":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Natural Language Query"},"is_active":{"type":"boolean","title":"Is Active","default":true},"is_dynamic":{"type":"boolean","title":"Is Dynamic","default":true}},"type":"object","required":["name"],"title":"SegmentCreate","description":"Schema for creating a new audience segment."},"cbapp__SegmentExecutionLog":{"properties":{"id":{"type":"string","title":"Id"},"segment_id":{"type":"string","title":"Segment Id"},"executed_at":{"type":"string","format":"date-time","title":"Executed At"},"execution_time_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Execution Time Ms"},"person_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Person Count"},"success":{"type":"boolean","title":"Success","default":true},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"executed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Executed By"}},"type":"object","required":["id","segment_id","executed_at"],"title":"SegmentExecutionLog","description":"Record of a single segment query execution."},"cbapp__SegmentSurveyCreate":{"properties":{"survey_id":{"type":"string","title":"Survey Id"},"display_order":{"type":"integer","title":"Display Order","default":0},"is_required":{"type":"boolean","title":"Is Required","default":false}},"type":"object","required":["survey_id"],"title":"SegmentSurveyCreate","description":"Model for linking a survey to a segment."},"cbapp__SegmentUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"natural_language_query":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Natural Language Query"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"is_dynamic":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Dynamic"}},"type":"object","title":"SegmentUpdate","description":"Schema for partial segment updates."},"cbapp__SegmentWithStats":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"natural_language_query":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Natural Language Query"},"is_active":{"type":"boolean","title":"Is Active","default":true},"is_dynamic":{"type":"boolean","title":"Is Dynamic","default":true},"id":{"type":"string","title":"Id"},"generated_sql":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated Sql"},"filter_criteria":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Filter Criteria"},"person_count":{"type":"integer","title":"Person Count","default":0},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"},"last_execution":{"anyOf":[{"$ref":"#/components/schemas/cbapp__SegmentExecutionLog"},{"type":"null"}]},"total_executions":{"type":"integer","title":"Total Executions","default":0},"avg_execution_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Execution Time Ms"}},"type":"object","required":["name","id","created_at","updated_at"],"title":"SegmentWithStats","description":"Segment enriched with execution statistics."},"cbapp__SegmentsResponse":{"properties":{"segments":{"items":{"$ref":"#/components/schemas/cbapp__SegmentWithStats"},"type":"array","title":"Segments"},"total":{"type":"integer","title":"Total"},"skip":{"type":"integer","title":"Skip"},"limit":{"type":"integer","title":"Limit"},"has_next":{"type":"boolean","title":"Has Next"},"page":{"type":"integer","title":"Page"},"total_pages":{"type":"integer","title":"Total Pages"}},"type":"object","required":["segments","total","skip","limit","has_next","page","total_pages"],"title":"SegmentsResponse","description":"Paginated response for segments list."},"cbapp__SendRequest":{"properties":{"channel":{"type":"string","title":"Channel"},"person_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Person Id"},"recipient_address":{"type":"string","title":"Recipient Address"},"recipient_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Name"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html"},"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id"},"template_vars":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Vars"},"priority":{"type":"integer","title":"Priority","default":3},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id"},"campaign_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Id"}},"type":"object","required":["channel","recipient_address"],"title":"SendRequest","description":"Request to send a communication immediately."},"cbapp__SendToEventRequest":{"properties":{"event_id":{"type":"string","title":"Event Id"},"channel":{"type":"string","title":"Channel"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html"},"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id"},"template_vars":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Vars"},"priority":{"type":"integer","title":"Priority","default":2},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"}},"type":"object","required":["event_id","channel"],"title":"SendToEventRequest","description":"Request to send to all registrants of an event."},"cbapp__SendToSegmentRequest":{"properties":{"segment_id":{"type":"string","title":"Segment Id"},"channel":{"type":"string","title":"Channel"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html"},"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id"},"template_vars":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Vars"},"priority":{"type":"integer","title":"Priority","default":5},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At"},"campaign_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Id"},"delivery_method":{"type":"string","title":"Delivery Method","default":"queue"}},"type":"object","required":["segment_id","channel"],"title":"SendToSegmentRequest","description":"Request to send to all members of a segment."},"cbapp__SettingResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"message":{"type":"string","title":"Message"},"integration":{"type":"string","title":"Integration"},"key":{"type":"string","title":"Key"},"display_value":{"type":"string","title":"Display Value"}},"type":"object","required":["success","message","integration","key","display_value"],"title":"SettingResponse","description":"Response after updating a setting."},"cbapp__SettingUpdate":{"properties":{"value":{"type":"string","title":"Value"}},"type":"object","required":["value"],"title":"SettingUpdate","description":"Request to update a setting value."},"cbapp__SignedUrlRequest":{"properties":{"expires":{"type":"integer","maximum":604800.0,"minimum":60.0,"title":"Expires","description":"Expiry in seconds","default":3600}},"type":"object","title":"SignedUrlRequest","description":"Request model for signed URL generation."},"cbapp__SignedUrlResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["url"],"title":"SignedUrlResponse","description":"Response model for signed URL."},"cbapp__SkipRequest":{"properties":{"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Reason for skipping"}},"type":"object","title":"SkipRequest","description":"Request to skip a contact."},"cbapp__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbapp__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbapp__Survey":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"id":{"type":"string","title":"Id"},"state":{"type":"string","title":"State"},"questions":{"items":{"$ref":"#/components/schemas/cbapp__SurveyQuestion"},"type":"array","title":"Questions","default":[]},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["name","id","state"],"title":"Survey","description":"Complete survey model with all data."},"cbapp__SurveyCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["name"],"title":"SurveyCreate","description":"Model for creating a new survey."},"cbapp__SurveyQuestion":{"properties":{"id":{"type":"string","title":"Id"},"type":{"type":"string","title":"Type"},"label":{"type":"string","title":"Label"},"text":{"type":"string","title":"Text"},"help_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help Text"},"options":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Options"},"required":{"type":"boolean","title":"Required","default":false},"display_order":{"type":"integer","title":"Display Order","default":0},"default_checked":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Default Checked"}},"type":"object","required":["id","type","label","text"],"title":"SurveyQuestion","description":"Model for a survey question."},"cbapp__SurveySummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"state":{"type":"string","title":"State"},"question_count":{"type":"integer","title":"Question Count","default":0},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["id","name","state"],"title":"SurveySummary","description":"Summary model for survey lists (without questions)."},"cbapp__SurveyUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","title":"SurveyUpdate","description":"Model for updating a survey."},"cbapp__SystemEventData":{"properties":{"event_type":{"type":"string","title":"Event Type"},"service":{"type":"string","title":"Service"},"summary":{"type":"string","title":"Summary"},"action_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Url"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"}},"type":"object","required":["event_type","service","summary"],"title":"SystemEventData","description":"Data payload for system event messages."},"cbapp__SystemMessageCreate":{"properties":{"system_type":{"type":"string","enum":["logs","alerts","jobs","announcements"],"title":"System Type"},"object_type":{"type":"string","enum":["log","alert","job","system_event"],"title":"Object Type"},"title":{"type":"string","title":"Title"},"data":{"anyOf":[{"$ref":"#/components/schemas/cbapp__LogEntryData"},{"$ref":"#/components/schemas/cbapp__AlertData"},{"$ref":"#/components/schemas/cbapp__JobProgressData"},{"$ref":"#/components/schemas/cbapp__SystemEventData"}],"title":"Data"},"update_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Update Message Id"}},"type":"object","required":["system_type","object_type","title","data"],"title":"SystemMessageCreate","description":"Model for publishing system messages via the bridge.\n\nUsed by mesh services to post messages to system rooms."},"cbapp__SystemMessageResponse":{"properties":{"message_id":{"type":"string","title":"Message Id"},"room_id":{"type":"string","title":"Room Id"},"sequence_number":{"type":"integer","title":"Sequence Number"}},"type":"object","required":["message_id","room_id","sequence_number"],"title":"SystemMessageResponse","description":"Response from publishing a system message."},"cbapp__Tag":{"properties":{"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"},"id":{"type":"string","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"type":"string","title":"Created By"},"children":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbapp__Tag"},"type":"array"},{"type":"null"}],"title":"Children","default":[]}},"type":"object","required":["name","id","created_at","created_by"],"title":"Tag","description":"Complete tag model with all data."},"cbapp__TagCreate":{"properties":{"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"}},"type":"object","required":["name"],"title":"TagCreate","description":"Model for creating a new tag."},"cbapp__TagUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"}},"type":"object","title":"TagUpdate","description":"Model for updating an existing tag."},"cbapp__Template":{"properties":{"name":{"type":"string","title":"Name"},"channel":{"type":"string","title":"Channel"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"content":{"type":"string","title":"Content"},"content_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Html"},"variables":{"items":{"type":"string"},"type":"array","title":"Variables","default":[]},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"id":{"type":"string","title":"Id"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","required":["name","channel","content","id"],"title":"Template","description":"Complete template model with all data."},"cbapp__TemplateCreate":{"properties":{"name":{"type":"string","title":"Name"},"channel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"content":{"type":"string","title":"Content"},"content_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Html"},"variables":{"items":{"type":"string"},"type":"array","title":"Variables","default":[]},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},"type":"object","required":["name","content"],"title":"TemplateCreate","description":"Model for creating a new template."},"cbapp__TemplateRender":{"properties":{"variables":{"additionalProperties":true,"type":"object","title":"Variables","default":{}}},"type":"object","title":"TemplateRender","description":"Request to render a template with variables."},"cbapp__TemplateRendered":{"properties":{"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"content":{"type":"string","title":"Content"},"content_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Html"}},"type":"object","required":["content"],"title":"TemplateRendered","description":"Rendered template content."},"cbapp__TemplateUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"channel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"content_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Html"},"variables":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Variables"}},"type":"object","title":"TemplateUpdate","description":"Model for updating an existing template."},"cbapp__TestResult":{"properties":{"integration":{"type":"string","title":"Integration"},"status":{"type":"string","title":"Status"},"message":{"type":"string","title":"Message"},"tested_at":{"type":"string","format":"date-time","title":"Tested At"}},"type":"object","required":["integration","status","message","tested_at"],"title":"TestResult","description":"Result of testing an integration connection."},"cbapp__Token":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["access_token"],"title":"Token","description":"Token model for JWT authentication.","example":{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0ODhlNzgyNS1iNWFlLTQ0MzYtYWM2OS01NTI3ZmI5NWNhMmUiLCJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzY3MDQ2MTI4fQ.abc123","token_type":"bearer"}},"cbapp__TranscriptRequest":{"properties":{"url":{"type":"string","minLength":5,"title":"Url","description":"YouTube video URL or video ID"},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type","description":"Force content type: debate, lecture, tutorial, interview, conversation, general"},"skip_classification":{"type":"boolean","title":"Skip Classification","description":"Skip AI classification for faster processing","default":false},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic routing (us, us:ca, de, gb, jp, etc.)"}},"type":"object","required":["url"],"title":"TranscriptRequest","description":"Request to fetch a YouTube video transcript."},"cbapp__User":{"properties":{"username":{"type":"string","title":"Username"},"email":{"type":"string","format":"email","title":"Email"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"role":{"type":"string","title":"Role","default":"field"},"id":{"type":"string","title":"Id"},"is_active":{"type":"boolean","title":"Is Active"},"osm_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Osm Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"organizations":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Organizations","default":[]}},"type":"object","required":["username","email","first_name","last_name","id","is_active","created_at","updated_at"],"title":"User","description":"Complete user model with all data.","example":{"created_at":"2025-01-01T00:00:00","email":"john.smith@example.com","first_name":"John","id":"488e7825-b5ae-4436-ac69-5527fb95ca2e","is_active":true,"last_name":"Smith","organizations":[],"role":"field","updated_at":"2025-01-01T00:00:00","username":"jsmith"}},"cbapp__UserCreate":{"properties":{"username":{"type":"string","title":"Username"},"email":{"type":"string","format":"email","title":"Email"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"role":{"type":"string","title":"Role","default":"field"},"password":{"type":"string","title":"Password"},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id"}},"type":"object","required":["username","email","first_name","last_name","password"],"title":"UserCreate","description":"Model for creating a new user.","example":{"email":"john.smith@example.com","first_name":"John","last_name":"Smith","password":"securePassword123!","role":"field","username":"jsmith"}},"cbapp__UserFolderCreate":{"properties":{"user_id":{"type":"string","title":"User Id"}},"type":"object","required":["user_id"],"title":"UserFolderCreate","description":"Request model for creating a user folder (admin only)."},"cbapp__UserFolderInfo":{"properties":{"user_id":{"type":"string","title":"User Id"},"path":{"type":"string","title":"Path"},"exists":{"type":"boolean","title":"Exists","default":true}},"type":"object","required":["user_id","path"],"title":"UserFolderInfo","description":"Information about a user's folder."},"cbapp__UserUpdate":{"properties":{"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"organization_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Id"}},"type":"object","title":"UserUpdate","description":"Model for updating an existing user."},"cbapp__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbapp__VoterSummary":{"properties":{"svid":{"type":"integer","title":"Svid"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"zip_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zip Code"},"party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party"},"calculated_party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Calculated Party"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"birth_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Birth Year"},"turnout_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Turnout Score"},"trump_support_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Trump Support Score"},"biden_oppose_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Biden Oppose Score"},"turnout_score_categorized":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Turnout Score Categorized"},"trump_support_score_categorized":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trump Support Score Categorized"},"cell_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cell Phone"},"email_mydata":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Mydata"},"congressional_district":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Congressional District"},"general_2020":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"General 2020"},"general_2022":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"General 2022"},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"}},"type":"object","required":["svid","first_name","last_name","city","county","state","zip_code","party","calculated_party","gender","birth_year","turnout_score","trump_support_score","biden_oppose_score","turnout_score_categorized","trump_support_score_categorized","cell_phone","email_mydata","congressional_district","general_2020","general_2022","latitude","longitude"],"title":"VoterSummary","description":"Summary model for voter search results."},"cbapp__Walkbook":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"ordering_strategy":{"type":"string","title":"Ordering Strategy","description":"geographic, alphabetic, or custom","default":"geographic"},"ordering_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ordering Config"},"sync_interval_minutes":{"type":"integer","maximum":1440.0,"minimum":1.0,"title":"Sync Interval Minutes","default":15},"id":{"type":"string","title":"Id"},"segment_id":{"type":"string","title":"Segment Id"},"status":{"type":"string","title":"Status","default":"draft"},"export_version":{"type":"integer","title":"Export Version","default":0},"last_exported_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Exported At"},"export_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Export Path"},"export_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Export Size Bytes"},"person_count":{"type":"integer","title":"Person Count","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"}},"type":"object","required":["name","id","segment_id","created_at","updated_at"],"title":"Walkbook","description":"Full walkbook model with all fields."},"cbapp__WalkbookAssignment":{"properties":{"priority":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Priority","default":0},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id"},"walkbook_id":{"type":"string","title":"Walkbook Id"},"user_id":{"type":"string","title":"User Id"},"assigned_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned By"},"assigned_at":{"type":"string","format":"date-time","title":"Assigned At"},"status":{"type":"string","title":"Status","default":"active"},"is_checked_out":{"type":"boolean","title":"Is Checked Out","default":false},"checked_out_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Checked Out At"},"checkout_device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkout Device Id"},"last_sync_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Sync At"},"total_contacts":{"type":"integer","title":"Total Contacts","default":0},"completed_contacts":{"type":"integer","title":"Completed Contacts","default":0},"skipped_contacts":{"type":"integer","title":"Skipped Contacts","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","walkbook_id","user_id","assigned_at","created_at","updated_at"],"title":"WalkbookAssignment","description":"Full assignment model."},"cbapp__WalkbookAssignmentCreate":{"properties":{"priority":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Priority","default":0},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"user_id":{"type":"string","title":"User Id"}},"type":"object","required":["user_id"],"title":"WalkbookAssignmentCreate","description":"Request to assign a user to a walkbook."},"cbapp__WalkbookAssignmentUpdate":{"properties":{"priority":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":0.0},{"type":"null"}],"title":"Priority"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"active, paused, or completed"}},"type":"object","title":"WalkbookAssignmentUpdate","description":"Request to update an assignment."},"cbapp__WalkbookAssignmentWithUser":{"properties":{"priority":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Priority","default":0},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id"},"walkbook_id":{"type":"string","title":"Walkbook Id"},"user_id":{"type":"string","title":"User Id"},"assigned_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned By"},"assigned_at":{"type":"string","format":"date-time","title":"Assigned At"},"status":{"type":"string","title":"Status","default":"active"},"is_checked_out":{"type":"boolean","title":"Is Checked Out","default":false},"checked_out_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Checked Out At"},"checkout_device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkout Device Id"},"last_sync_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Sync At"},"total_contacts":{"type":"integer","title":"Total Contacts","default":0},"completed_contacts":{"type":"integer","title":"Completed Contacts","default":0},"skipped_contacts":{"type":"integer","title":"Skipped Contacts","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"user_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Name"},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email"}},"type":"object","required":["id","walkbook_id","user_id","assigned_at","created_at","updated_at"],"title":"WalkbookAssignmentWithUser","description":"Assignment with joined user info."},"cbapp__WalkbookAssignmentsResponse":{"properties":{"assignments":{"items":{"$ref":"#/components/schemas/cbapp__WalkbookAssignmentWithUser"},"type":"array","title":"Assignments"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["assignments","total"],"title":"WalkbookAssignmentsResponse","description":"Response with list of assignments."},"cbapp__WalkbookCheckinRequest":{"properties":{"device_id":{"type":"string","title":"Device Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["device_id"],"title":"WalkbookCheckinRequest","description":"Request to check in after field work."},"cbapp__WalkbookCheckoutRequest":{"properties":{"device_id":{"type":"string","maxLength":100,"minLength":1,"title":"Device Id"},"device_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Device Info"}},"type":"object","required":["device_id"],"title":"WalkbookCheckoutRequest","description":"Request to check out a walkbook for field use."},"cbapp__WalkbookCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"ordering_strategy":{"type":"string","title":"Ordering Strategy","description":"geographic, alphabetic, or custom","default":"geographic"},"ordering_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ordering Config"},"sync_interval_minutes":{"type":"integer","maximum":1440.0,"minimum":1.0,"title":"Sync Interval Minutes","default":15},"segment_id":{"type":"string","title":"Segment Id"}},"type":"object","required":["name","segment_id"],"title":"WalkbookCreate","description":"Request to create a walkbook from a segment."},"cbapp__WalkbookExportRequest":{"properties":{"regenerate":{"type":"boolean","title":"Regenerate","description":"Force regeneration even if export exists","default":false}},"type":"object","title":"WalkbookExportRequest","description":"Request to generate a walkbook export."},"cbapp__WalkbookExportResponse":{"properties":{"walkbook_id":{"type":"string","title":"Walkbook Id"},"export_version":{"type":"integer","title":"Export Version"},"export_path":{"type":"string","title":"Export Path"},"export_size_bytes":{"type":"integer","title":"Export Size Bytes"},"person_count":{"type":"integer","title":"Person Count"},"exported_at":{"type":"string","format":"date-time","title":"Exported At"}},"type":"object","required":["walkbook_id","export_version","export_path","export_size_bytes","person_count","exported_at"],"title":"WalkbookExportResponse","description":"Response from export operation."},"cbapp__WalkbookStatusResponse":{"properties":{"walkbook_id":{"type":"string","title":"Walkbook Id"},"walkbook_name":{"type":"string","title":"Walkbook Name"},"total_assignments":{"type":"integer","title":"Total Assignments"},"checked_out_count":{"type":"integer","title":"Checked Out Count"},"assignments":{"items":{"$ref":"#/components/schemas/cbapp__WalkbookAssignmentWithUser"},"type":"array","title":"Assignments"}},"type":"object","required":["walkbook_id","walkbook_name","total_assignments","checked_out_count","assignments"],"title":"WalkbookStatusResponse","description":"Response with checkout status for all assignments."},"cbapp__WalkbookSyncHistoryResponse":{"properties":{"syncs":{"items":{"$ref":"#/components/schemas/cbapp__WalkbookSyncLog"},"type":"array","title":"Syncs"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["syncs","total"],"title":"WalkbookSyncHistoryResponse","description":"Response with sync history."},"cbapp__WalkbookSyncLog":{"properties":{"id":{"type":"string","title":"Id"},"assignment_id":{"type":"string","title":"Assignment Id"},"sync_started_at":{"type":"string","format":"date-time","title":"Sync Started At"},"sync_completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sync Completed At"},"status":{"type":"string","title":"Status","default":"in_progress"},"actions_received":{"type":"integer","title":"Actions Received","default":0},"actions_processed":{"type":"integer","title":"Actions Processed","default":0},"actions_failed":{"type":"integer","title":"Actions Failed","default":0},"error_log":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Error Log"},"device_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Device Info"}},"type":"object","required":["id","assignment_id","sync_started_at"],"title":"WalkbookSyncLog","description":"Sync log entry."},"cbapp__WalkbookSyncRequest":{"properties":{"assignment_id":{"type":"string","title":"Assignment Id"},"device_id":{"type":"string","title":"Device Id"},"export_version":{"type":"integer","title":"Export Version"},"actions":{"items":{"$ref":"#/components/schemas/cbapp__FieldAction"},"type":"array","title":"Actions"}},"type":"object","required":["assignment_id","device_id","export_version","actions"],"title":"WalkbookSyncRequest","description":"Request to sync field actions from mobile device."},"cbapp__WalkbookSyncResponse":{"properties":{"sync_id":{"type":"string","title":"Sync Id"},"actions_received":{"type":"integer","title":"Actions Received"},"actions_processed":{"type":"integer","title":"Actions Processed"},"actions_failed":{"type":"integer","title":"Actions Failed"},"errors":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Errors"},"server_time":{"type":"string","format":"date-time","title":"Server Time"}},"type":"object","required":["sync_id","actions_received","actions_processed","actions_failed","server_time"],"title":"WalkbookSyncResponse","description":"Response from sync operation."},"cbapp__WalkbookUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"ordering_strategy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ordering Strategy"},"ordering_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ordering Config"},"sync_interval_minutes":{"anyOf":[{"type":"integer","maximum":1440.0,"minimum":1.0},{"type":"null"}],"title":"Sync Interval Minutes"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"draft, active, or archived"}},"type":"object","title":"WalkbookUpdate","description":"Request to update walkbook settings."},"cbapp__WalkbookWithSegment":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"ordering_strategy":{"type":"string","title":"Ordering Strategy","description":"geographic, alphabetic, or custom","default":"geographic"},"ordering_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ordering Config"},"sync_interval_minutes":{"type":"integer","maximum":1440.0,"minimum":1.0,"title":"Sync Interval Minutes","default":15},"id":{"type":"string","title":"Id"},"segment_id":{"type":"string","title":"Segment Id"},"status":{"type":"string","title":"Status","default":"draft"},"export_version":{"type":"integer","title":"Export Version","default":0},"last_exported_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Exported At"},"export_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Export Path"},"export_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Export Size Bytes"},"person_count":{"type":"integer","title":"Person Count","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"},"segment_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Segment Name"},"segment_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Segment Description"}},"type":"object","required":["name","id","segment_id","created_at","updated_at"],"title":"WalkbookWithSegment","description":"Walkbook with joined segment info."},"cbapp__WalkbooksResponse":{"properties":{"walkbooks":{"items":{"$ref":"#/components/schemas/cbapp__WalkbookWithSegment"},"type":"array","title":"Walkbooks"},"total":{"type":"integer","title":"Total"},"skip":{"type":"integer","title":"Skip"},"limit":{"type":"integer","title":"Limit"},"has_next":{"type":"boolean","title":"Has Next"},"page":{"type":"integer","title":"Page"},"total_pages":{"type":"integer","title":"Total Pages"}},"type":"object","required":["walkbooks","total","skip","limit","has_next","page","total_pages"],"title":"WalkbooksResponse","description":"Paginated response for walkbooks list."},"cbapp__WorksheetSelect":{"properties":{"worksheet":{"type":"string","title":"Worksheet"}},"type":"object","required":["worksheet"],"title":"WorksheetSelect","description":"Request to select a worksheet for Excel files."},"cbapp__src__api__routes__chat__ChatRequest":{"properties":{"query":{"type":"string","title":"Query"},"workflow":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow"},"context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Context"}},"type":"object","required":["query"],"title":"ChatRequest","description":"Incoming chat query with optional workflow hint."},"cbapp__src__api__routes__chat__ChatResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"response":{"type":"string","title":"Response"},"workflow_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Used"},"generated_sql":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated Sql"},"segment_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Segment Data"},"execution_time_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Execution Time Ms"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["success","response"],"title":"ChatResponse","description":"Response from the chat pipeline including generated SQL and results."},"cbapp__src__api__services__cb_query_ir__ChatRequest":{"properties":{"message":{"type":"string","title":"Message","description":"The user's natural language query"},"history":{"items":{"$ref":"#/components/schemas/cbapp__ChatMessage"},"type":"array","title":"History","description":"Previous messages in conversation"},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"Existing conversation ID to continue"},"debug":{"type":"boolean","title":"Debug","description":"Enable debug info in response","default":false}},"type":"object","required":["message"],"title":"ChatRequest","description":"Request model for chat endpoint.","example":{"debug":false,"history":[],"message":"How many Republicans have a turnout score above 80?"}},"cbapp__src__api__services__cb_query_ir__ChatResponse":{"properties":{"message":{"type":"string","title":"Message","description":"Natural language response to display"},"type":{"type":"string","enum":["results","clarify","error","info"],"title":"Type","description":"Response type: results, clarify, error, or info"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data","description":"Query results data"},"actions":{"items":{"$ref":"#/components/schemas/cbapp__ChatAction"},"type":"array","title":"Actions","description":"Action buttons to display"},"questions":{"items":{"$ref":"#/components/schemas/cbapp__ClarificationQuestion"},"type":"array","title":"Questions","description":"Questions when type=clarify"},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"Conversation UUID"},"share_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Id","description":"8-char shareable ID for URL"},"sql":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sql","description":"Generated SQL query"},"debug":{"anyOf":[{"$ref":"#/components/schemas/cbapp__DebugInfo"},{"type":"null"}],"description":"Debug info when enabled"}},"type":"object","required":["message","type"],"title":"ChatResponse","description":"Response model for chat endpoint.","example":{"actions":[{"action":"save_segment","data":{"count":15234},"label":"Save as Segment"}],"conversation_id":"488e7825-b5ae-4436-ac69-5527fb95ca2e","data":{"count":15234,"intent":"count"},"message":"I found 15,234 Republicans with turnout scores above 80.","share_id":"ABC12345","sql":"SELECT COUNT(*) FROM i360_voters WHERE party = 'Republican' AND turnout_score > 80","type":"results"}},"cbauth__APIKeyCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Human-readable label for the key.","examples":["ci-pipeline","local-dev"]},"note":{"type":"string","title":"Note","description":"Optional free-form note about the key's purpose.","default":"","examples":["Used by the nightly export job"]}},"type":"object","required":["name"],"title":"APIKeyCreate","description":"Create a new API key for the authenticated user."},"cbauth__APIKeyCreated":{"properties":{"id":{"type":"string","title":"Id","description":"API key identifier.","examples":["key_01HX..."]},"name":{"type":"string","title":"Name","description":"Human-readable label for the key.","examples":["ci-pipeline"]},"prefix":{"type":"string","title":"Prefix","description":"Non-secret prefix of the key, safe to display for identification.","examples":["cbauth_AbC1"]},"user_id":{"type":"string","title":"User Id","description":"Id of the user who owns the key; the key inherits this user's group permissions.","examples":["usr_01HX..."]},"note":{"type":"string","title":"Note","description":"Free-form note about the key.","examples":["Used by the nightly export job"]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the key is currently usable.","examples":[true]},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"Timestamp the key was last used, if ever."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp the key was created."},"raw_key":{"type":"string","title":"Raw Key","description":"The full secret key value. Shown exactly once, at creation; store it securely as it cannot be retrieved again.","examples":["cbauth_AbC1dEf2GhI3jKl4MnO5pQr6StU7vWx8"]}},"type":"object","required":["id","name","prefix","user_id","note","is_active","created_at","raw_key"],"title":"APIKeyCreated","description":"API key details including the raw key, returned only at creation time.","examples":[{"created_at":"2026-01-15T12:00:00Z","id":"key_01HX...","is_active":true,"last_used_at":"2026-06-19T08:30:00Z","name":"ci-pipeline","note":"Used by the nightly export job","prefix":"cbauth_AbC1","user_id":"usr_01HX..."}]},"cbauth__APIKeyResponse":{"properties":{"id":{"type":"string","title":"Id","description":"API key identifier.","examples":["key_01HX..."]},"name":{"type":"string","title":"Name","description":"Human-readable label for the key.","examples":["ci-pipeline"]},"prefix":{"type":"string","title":"Prefix","description":"Non-secret prefix of the key, safe to display for identification.","examples":["cbauth_AbC1"]},"user_id":{"type":"string","title":"User Id","description":"Id of the user who owns the key; the key inherits this user's group permissions.","examples":["usr_01HX..."]},"note":{"type":"string","title":"Note","description":"Free-form note about the key.","examples":["Used by the nightly export job"]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the key is currently usable.","examples":[true]},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"Timestamp the key was last used, if ever."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp the key was created."}},"type":"object","required":["id","name","prefix","user_id","note","is_active","created_at"],"title":"APIKeyResponse","description":"API key metadata (excludes the raw key value).","examples":[{"created_at":"2026-01-15T12:00:00Z","id":"key_01HX...","is_active":true,"last_used_at":"2026-06-19T08:30:00Z","name":"ci-pipeline","note":"Used by the nightly export job","prefix":"cbauth_AbC1","user_id":"usr_01HX..."}]},"cbauth__ActiveSessionResponse":{"properties":{"user_id":{"type":"string","title":"User Id","description":"Id of the user.","examples":["usr_01HX..."]},"email":{"type":"string","title":"Email","description":"Account email address.","examples":["jane@campaignbrain.dev"]},"last_login":{"type":"string","format":"date-time","title":"Last Login","description":"Timestamp of the most recent successful login."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"Estimated session expiry timestamp."},"ip_address":{"type":"string","title":"Ip Address","description":"Source IP address of the session.","examples":["203.0.113.42"]},"method":{"type":"string","title":"Method","description":"Authentication method used.","examples":["password"]}},"type":"object","required":["user_id","email","last_login","expires_at","ip_address","method"],"title":"ActiveSessionResponse","description":"Approximate active session derived from recent login events.","examples":[{"email":"jane@campaignbrain.dev","expires_at":"2026-06-20T08:30:00Z","ip_address":"203.0.113.42","last_login":"2026-06-19T08:30:00Z","method":"password","user_id":"usr_01HX..."}]},"cbauth__AppCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Unique application name.","examples":["Campaigns","Loom"]},"description":{"type":"string","title":"Description","description":"Human-readable description of the application.","default":"","examples":["Voter outreach and campaign management"]}},"type":"object","required":["name"],"title":"AppCreate","description":"Register a new application."},"cbauth__AppResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Application identifier.","examples":["app_campaigns"]},"name":{"type":"string","title":"Name","description":"Application name.","examples":["Campaigns"]},"description":{"type":"string","title":"Description","description":"Human-readable description.","examples":["Voter outreach and campaign management"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp the application was registered."}},"type":"object","required":["id","name","description","created_at"],"title":"AppResponse","description":"Application details returned by the API.","examples":[{"created_at":"2026-01-15T12:00:00Z","description":"Voter outreach and campaign management","id":"app_campaigns","name":"Campaigns"}]},"cbauth__AppUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Name","description":"New application name.","examples":["Campaigns v2"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"New application description.","examples":["Voter outreach platform"]}},"type":"object","title":"AppUpdate","description":"Partial update to a registered application."},"cbauth__AssignPermission":{"properties":{"permission_id":{"type":"string","title":"Permission Id","description":"Id of the permission to assign to the group.","examples":["perm_01HX..."]}},"type":"object","required":["permission_id"],"title":"AssignPermission","description":"Assign a permission to a group by permission ID."},"cbauth__Body_change_password_auth_change_password_post":{"properties":{"current_password":{"type":"string","title":"Current Password"},"new_password":{"type":"string","minLength":8,"title":"New Password"},"confirm_password":{"type":"string","title":"Confirm Password"}},"type":"object","required":["current_password","new_password","confirm_password"],"title":"Body_change_password_auth_change_password_post"},"cbauth__Body_login_auth_login_post":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"next_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Url"},"remember":{"type":"boolean","title":"Remember","default":true}},"type":"object","required":["email","password"],"title":"Body_login_auth_login_post"},"cbauth__Body_register_auth_register_post":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","minLength":8,"title":"Password"}},"type":"object","required":["email","password"],"title":"Body_register_auth_register_post"},"cbauth__Body_revoke_token_oauth_revoke_post":{"properties":{"token":{"type":"string","title":"Token"},"token_type_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Type Hint"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"}},"type":"object","required":["token"],"title":"Body_revoke_token_oauth_revoke_post"},"cbauth__BusinessAssignment":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"cbpulse business id the user is assigned to.","examples":["biz_the-saint"]},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"The user's role at that property.","examples":["manager","staff"]}},"type":"object","required":["business_id"],"title":"BusinessAssignment","description":"Property assignment for a user (cross-service ref to cbpulse business).","examples":[{"business_id":"biz_the-saint","role":"manager"}]},"cbauth__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbauth__ForgotPasswordRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Email address to send the password-reset link to. The response is intentionally identical whether or not the address exists, to avoid account enumeration.","examples":["jane@campaignbrain.dev"]}},"type":"object","required":["email"],"title":"ForgotPasswordRequest","description":"Request a password reset email."},"cbauth__GroupBrief":{"properties":{"id":{"type":"string","title":"Id","description":"Group identifier.","examples":["grp_staff"]},"name":{"type":"string","title":"Name","description":"Human-readable group name.","examples":["Staff"]}},"type":"object","required":["id","name"],"title":"GroupBrief","description":"Compact group reference for embedding in user payloads.","examples":[{"id":"grp_staff","name":"Staff"}]},"cbauth__GroupCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Unique group name.","examples":["Staff","HR Managers"]},"description":{"type":"string","title":"Description","description":"Human-readable description of the group's purpose.","default":"","examples":["All full-time staff members"]}},"type":"object","required":["name"],"title":"GroupCreate","description":"Create a new permission group."},"cbauth__GroupResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Group identifier.","examples":["grp_staff"]},"name":{"type":"string","title":"Name","description":"Group name.","examples":["Staff"]},"description":{"type":"string","title":"Description","description":"Human-readable description of the group.","examples":["All full-time staff members"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp the group was created."}},"type":"object","required":["id","name","description","created_at"],"title":"GroupResponse","description":"Group details returned by the API.","examples":[{"created_at":"2026-01-15T12:00:00Z","description":"All full-time staff members","id":"grp_staff","name":"Staff"}]},"cbauth__GroupUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Name","description":"New group name.","examples":["Senior Staff"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"New group description.","examples":["Senior full-time staff"]}},"type":"object","title":"GroupUpdate","description":"Partial update to an existing group."},"cbauth__GroupWithPermissions":{"properties":{"id":{"type":"string","title":"Id","description":"Group identifier.","examples":["grp_staff"]},"name":{"type":"string","title":"Name","description":"Group name.","examples":["Staff"]},"description":{"type":"string","title":"Description","description":"Human-readable description of the group.","examples":["All full-time staff members"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp the group was created."},"permissions":{"items":{"type":"string"},"type":"array","title":"Permissions","description":"Dot-notation permission strings assigned to this group.","default":[],"examples":[["campaigns.voters.read","cbloom.*.*"]]}},"type":"object","required":["id","name","description","created_at"],"title":"GroupWithPermissions","description":"Group details including its assigned permission strings.","examples":[{"created_at":"2026-01-15T12:00:00Z","description":"All full-time staff members","id":"grp_staff","name":"Staff"}]},"cbauth__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbauth__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbauth__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbauth__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbauth__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbauth__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbauth__KioskTokenRequest":{"properties":{"device_id":{"type":"string","maxLength":64,"minLength":1,"title":"Device Id","description":"Identifier of the provisioned kiosk device requesting a token. Resolves to a synthetic service principal.","examples":["lobby-display-01","front-desk-ipad"]},"scope":{"items":{"type":"string"},"type":"array","title":"Scope","description":"Advisory only: retained for caller compatibility + audit. The gate resolves the principal's real permissions from the DB; this scope does not grant anything. Kept clamped to the allowlist so logs stay honest.","examples":[["cbloom.kiosk.read"]]}},"type":"object","required":["device_id"],"title":"KioskTokenRequest","description":"Request body to mint a device-bound kiosk token."},"cbauth__KioskTokenResponse":{"properties":{"token":{"type":"string","title":"Token","description":"The signed kiosk JWT to present as the auth cookie/bearer.","examples":["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]},"expires_in":{"type":"integer","title":"Expires In","description":"Lifetime of the kiosk token in seconds.","examples":[86400]},"scope":{"items":{"type":"string"},"type":"array","title":"Scope","description":"Advisory clamped scope — informational, not authority.","examples":[["cbloom.kiosk.read"]]}},"type":"object","required":["token","expires_in","scope"],"title":"KioskTokenResponse","description":"Minted kiosk token plus its lifetime and clamped advisory scope."},"cbauth__LoginEventResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Login-event identifier.","examples":["evt_01HX..."]},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"Id of the user, if the email matched a known account.","examples":["usr_01HX..."]},"email":{"type":"string","title":"Email","description":"Email address used in the login attempt.","examples":["jane@campaignbrain.dev"]},"timestamp":{"type":"string","format":"date-time","title":"Timestamp","description":"When the login attempt occurred."},"ip_address":{"type":"string","title":"Ip Address","description":"Source IP address.","examples":["203.0.113.42"]},"user_agent":{"type":"string","title":"User Agent","description":"Client User-Agent string.","examples":["Mozilla/5.0 ..."]},"method":{"type":"string","title":"Method","description":"Authentication method used.","examples":["password","oauth","api_key"]},"success":{"type":"boolean","title":"Success","description":"Whether the attempt succeeded.","examples":[true]},"failure_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure Reason","description":"Reason the attempt failed, if it failed.","examples":["invalid_credentials","account_locked"]}},"type":"object","required":["id","user_id","email","timestamp","ip_address","user_agent","method","success","failure_reason"],"title":"LoginEventResponse","description":"A single login event record.","examples":[{"email":"jane@campaignbrain.dev","id":"evt_01HX...","ip_address":"203.0.113.42","method":"password","success":true,"timestamp":"2026-06-19T08:30:00Z","user_agent":"Mozilla/5.0 ...","user_id":"usr_01HX..."}]},"cbauth__MagicLinkIssueRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Target account. Must resolve to an active human user (404 otherwise — this endpoint is authenticated, so no anti-enumeration blurring).","examples":["jane@campaignbrain.dev"]},"next":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Next","description":"Optional destination after sign-in. Must be a relative path or a campaignbrain domain URL; unsafe values are a 400 — a service passing a bad destination is a bug, not noise to drop.","examples":["/app/reports/q2-fundraising"]},"deliver":{"type":"string","enum":["none","email","sms"],"title":"Deliver","description":"'none' (default) returns the link so the caller can embed it in its own message. 'email'/'sms' make cbauth send its generic sign-in template instead; the link is then omitted from the response.","default":"none"},"ttl":{"anyOf":[{"type":"integer","maximum":86400.0,"minimum":60.0},{"type":"null"}],"title":"Ttl","description":"Link lifetime in seconds. Defaults to magic_link_ttl; capped at 24h for overnight report flows."}},"additionalProperties":false,"type":"object","required":["email"],"title":"MagicLinkIssueRequest","description":"Mint a one-time sign-in link for a target user (service-to-user handoff).\n\nRequires the ``auth.magic.issue`` permission — meant for service accounts\nthat finish an asset (report, image, document) and need to hand the user a\nlogin that lands on it."},"cbauth__MagicLinkIssueResponse":{"properties":{"user_id":{"type":"string","title":"User Id"},"email":{"type":"string","format":"email","title":"Email"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"deliver":{"type":"string","enum":["none","email","sms"],"title":"Deliver"},"delivered":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Delivered","description":"Send result when deliver != 'none'; null otherwise."},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link","description":"The one-time sign-in link — present only when deliver='none'. Treat it like a credential: it logs the user in."}},"type":"object","required":["user_id","email","expires_at","deliver"],"title":"MagicLinkIssueResponse","description":"Result of minting a service-issued magic link."},"cbauth__MagicLinkRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Email address to send the password-reset link to. The response is intentionally identical whether or not the address exists, to avoid account enumeration.","examples":["jane@campaignbrain.dev"]},"next":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Next","description":"Optional destination to land on after signing in. Must be a relative path or a campaignbrain domain URL; anything else is silently ignored and the default landing page is used.","examples":["/app/reports/q2-fundraising"]},"channel":{"type":"string","enum":["email","sms"],"title":"Channel","description":"Delivery channel. 'sms' texts the link to the phone number on file for the account; if the account has no phone, nothing is sent (response stays the uniform 200).","default":"email"}},"additionalProperties":false,"type":"object","required":["email"],"title":"MagicLinkRequest","description":"Request a one-time passwordless sign-in link."},"cbauth__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbauth__PermissionCreate":{"properties":{"permission_string":{"type":"string","pattern":"^[\\w*]+\\.[\\w*]+\\.[\\w*]+$","title":"Permission String","description":"Dot-notation permission in ``service.resource.action`` form; each segment may be a wildcard ``*``. e.g. campaigns.voters.read","examples":["campaigns.voters.read","cbloom.*.*","*.*.*"]},"description":{"type":"string","title":"Description","description":"Human-readable description of what the permission grants.","default":"","examples":["Read voter data"]},"app_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Id","description":"Optional id of the application this permission belongs to.","examples":["app_campaigns"]}},"type":"object","required":["permission_string"],"title":"PermissionCreate","description":"Create a new dot-notation permission string."},"cbauth__PermissionResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Permission identifier.","examples":["perm_01HX..."]},"permission_string":{"type":"string","title":"Permission String","description":"The dot-notation permission string.","examples":["campaigns.voters.read"]},"description":{"type":"string","title":"Description","description":"Human-readable description of the permission.","examples":["Read voter data"]},"app_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Id","description":"Id of the owning application, if any.","examples":["app_campaigns"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp the permission was created."}},"type":"object","required":["id","permission_string","description","app_id","created_at"],"title":"PermissionResponse","description":"Permission details returned by the API.","examples":[{"app_id":"app_campaigns","created_at":"2026-01-15T12:00:00Z","description":"Read voter data","id":"perm_01HX...","permission_string":"campaigns.voters.read"}]},"cbauth__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbauth__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbauth__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbauth__ResetPasswordRequest":{"properties":{"token":{"type":"string","title":"Token","description":"Single-use reset token from the password-reset email.","examples":["8f3c9d2e-...-reset-token"]},"new_password":{"type":"string","minLength":8,"title":"New Password","description":"Replacement password; minimum 8 characters.","examples":["brand-new-passphrase"]}},"type":"object","required":["token","new_password"],"title":"ResetPasswordRequest","description":"Set a new password using a reset token."},"cbauth__ServiceAccountCreate":{"properties":{"slug":{"type":"string","maxLength":64,"pattern":"^[a-z0-9][a-z0-9_-]{0,63}$","title":"Slug","description":"Service-account slug. A leading digit is allowed for fleet consistency with cbtenant; political tenant slugs like ``190personnel`` / ``mi20`` need to round-trip through ``cbinfra.secrets.<slug>`` permissions without rewriting.","examples":["mi20","190personnel"]},"permissions":{"items":{"type":"string"},"type":"array","title":"Permissions","description":"Explicit permission allow-list; may include wildcards.","examples":[["cbinfra.secrets.mi20","cbmodels.voters.read"]]},"include_default_secrets_perm":{"type":"boolean","title":"Include Default Secrets Perm","description":"When true (default), auto-adds ``cbinfra.secrets.<slug>`` if the caller didn't include it.","default":true,"examples":[true]},"note":{"type":"string","title":"Note","description":"Optional free-form note about the service account.","default":"","examples":["Michigan 2026 tenant service account"]}},"type":"object","required":["slug"],"title":"ServiceAccountCreate","description":"Payload for POST /api/v1/service-accounts.\n\n``permissions`` is the explicit allow-list; it may include wildcards\n(``cbinfra.secrets.*``). If the caller omits ``cbinfra.secrets.<slug>``\nand doesn't set ``include_default_secrets_perm=false``, it will be\nadded automatically.","examples":[{"include_default_secrets_perm":true,"note":"Michigan 2026 tenant service account","permissions":["cbinfra.secrets.mi20","cbmodels.voters.read"],"slug":"mi20"}]},"cbauth__ServiceAccountCreated":{"properties":{"user_id":{"type":"string","title":"User Id","description":"Id of the synthetic user backing the service account.","examples":["usr_01HX..."]},"email":{"type":"string","title":"Email","description":"Synthetic email address of the service account.","examples":["svc-mi20@service.campaignbrain.dev"]},"kind":{"type":"string","title":"Kind","description":"Account kind; always ``service`` here.","examples":["service"]},"group_name":{"type":"string","title":"Group Name","description":"Name of the group created to hold the account's permissions.","examples":["svc:mi20"]},"permissions":{"items":{"type":"string"},"type":"array","title":"Permissions","description":"Resolved permission allow-list granted to the account.","examples":[["cbinfra.secrets.mi20"]]},"api_key":{"$ref":"#/components/schemas/cbauth__APIKeyCreated","description":"The freshly minted API key, including its raw value (shown only once)."}},"type":"object","required":["user_id","email","kind","group_name","permissions","api_key"],"title":"ServiceAccountCreated","description":"Response body for POST /api/v1/service-accounts.","examples":[{"api_key":{"created_at":"2026-01-15T12:00:00Z","id":"key_01HX...","is_active":true,"name":"svc:mi20","note":"","prefix":"cbauth_AbC1","raw_key":"cbauth_AbC1dEf2GhI3...","user_id":"usr_01HX..."},"email":"svc-mi20@service.campaignbrain.dev","group_name":"svc:mi20","kind":"service","permissions":["cbinfra.secrets.mi20"],"user_id":"usr_01HX..."}]},"cbauth__SmsCodeRequest":{"properties":{"phone":{"type":"string","maxLength":32,"minLength":7,"title":"Phone","description":"Phone number in any common format — digits are extracted and matched against the phone on file. The response is intentionally identical whether or not a matching account exists.","examples":["(616) 727-1150"]}},"additionalProperties":false,"type":"object","required":["phone"],"title":"SmsCodeRequest","description":"Request a texted sign-in code for the account matching a phone number."},"cbauth__SmsLoginRequest":{"properties":{"phone":{"type":"string","maxLength":32,"minLength":7,"title":"Phone"},"code":{"type":"string","maxLength":10,"minLength":4,"title":"Code","description":"The 6-digit code from the text message.","examples":["123456"]},"next_url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Next Url","description":"Where to send the browser after sign-in. Honoured only when it is a relative path or a campaignbrain.dev/.ai URL (same guard as the password and magic-link paths); anything else falls back to /auth/dashboard. Echoed back as `redirect_url` in the response.","examples":["https://svi.campaignbrain.dev/app/svi"]}},"additionalProperties":false,"type":"object","required":["phone","code"],"title":"SmsLoginRequest","description":"Exchange a phone number + texted code for a session."},"cbauth__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbauth__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbauth__TenantMembership":{"properties":{"tenant_slug":{"type":"string","title":"Tenant Slug","description":"Slug of the tenant the user is a member of.","examples":["greenroom"]},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"The user's role within that tenant.","examples":["admin","member"]}},"type":"object","required":["tenant_slug"],"title":"TenantMembership","description":"Tenant the user belongs to + their role within it.","examples":[{"role":"admin","tenant_slug":"greenroom"}]},"cbauth__TenantMembershipCreate":{"properties":{"tenant_slug":{"type":"string","title":"Tenant Slug","description":"Slug of the tenant to add the user to.","examples":["greenroom","mi20"]},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"The user's role within the tenant.","default":"admin","examples":["admin","member"]}},"type":"object","required":["tenant_slug"],"title":"TenantMembershipCreate","description":"Body for POST /api/v1/users/{user_id}/tenants."},"cbauth__TenantSwitchRequest":{"properties":{"tenant_slug":{"type":"string","title":"Tenant Slug","description":"Slug of the tenant to switch the current session to. Must be a tenant the caller is a member of.","examples":["greenroom","mi20"]}},"type":"object","required":["tenant_slug"],"title":"TenantSwitchRequest","description":"Body for POST /api/v1/users/me/tenants/switch — change the active tenant."},"cbauth__UserActivityResponse":{"properties":{"user_id":{"type":"string","title":"User Id","description":"Id of the user.","examples":["usr_01HX..."]},"email":{"type":"string","title":"Email","description":"Account email address.","examples":["jane@campaignbrain.dev"]},"logins_24h":{"type":"integer","title":"Logins 24H","description":"Successful logins in the last 24 hours.","examples":[3]},"logins_7d":{"type":"integer","title":"Logins 7D","description":"Successful logins in the last 7 days.","examples":[21]},"last_login":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login","description":"Timestamp of the most recent login, if any."},"last_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Ip","description":"Source IP of the most recent login, if any.","examples":["203.0.113.42"]},"groups":{"items":{"type":"string"},"type":"array","title":"Groups","description":"Names of groups the user belongs to.","examples":[["Staff"]]},"permissions":{"items":{"type":"string"},"type":"array","title":"Permissions","description":"Effective permission strings for the user.","examples":[["campaigns.voters.read"]]}},"type":"object","required":["user_id","email","logins_24h","logins_7d","groups","permissions"],"title":"UserActivityResponse","description":"Per-user activity summary.","examples":[{"email":"jane@campaignbrain.dev","groups":["Staff"],"last_ip":"203.0.113.42","last_login":"2026-06-19T08:30:00Z","logins_24h":3,"logins_7d":21,"permissions":["campaigns.voters.read"],"user_id":"usr_01HX..."}]},"cbauth__UserCreate":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Email address for the new account. Stored lower-cased; must be unique case-insensitively — a second account on the same address is a 409 ``email_taken``.","examples":["new.user@campaignbrain.dev"]},"password":{"anyOf":[{"type":"string","minLength":8},{"type":"null"}],"title":"Password","description":"Initial password, plaintext; hashed on receipt. Optional — cbonboard's kiosk path creates provisional users with no password (``password_hash`` stays null until the user sets one via reset); its invite path sets one at create (cbonboard#6).","examples":["my-strong-passphrase"]},"kind":{"type":"string","title":"Kind","description":"Account kind. ``human`` (default) for normal user accounts. Service-account users are minted via ``/api/v1/service-accounts``, not this endpoint.","default":"human","examples":["human"]},"group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Id","description":"Legacy single-group FK. Prefer ``group_ids`` for multi-group membership.","examples":["grp_staff"]},"group_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Group Ids","description":"Optional M2M group set. When provided, replaces user_groups rows on create; ``group_id`` (legacy) is set to the first id for back-compat.","examples":[["grp_staff","grp_hr"]]},"business_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Business Ids","description":"Optional property assignments (cbpulse business ids).","examples":[["biz_the-saint","biz_lovango"]]},"phone_confirmed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Phone Confirmed","description":"cbonboard creates provisional users with ``phone_confirmed=false``; an explicit override here lets admin tooling skip the verify step.","examples":[true]},"email_confirmed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Confirmed","description":"Override the email-confirmed flag at creation time.","examples":[true]},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Whether the account is active. Inactive users cannot log in.","examples":[true]},"first_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"First Name","description":"User's given name.","examples":["Jane"]},"last_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Last Name","description":"User's family name.","examples":["Doe"]},"phone":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Phone","description":"Contact phone number.","examples":["+1-202-555-0142"]},"display_name":{"anyOf":[{"type":"string","maxLength":150},{"type":"null"}],"title":"Display Name","description":"Preferred display name shown in UIs.","examples":["Jane Doe"]},"title":{"anyOf":[{"type":"string","maxLength":150},{"type":"null"}],"title":"Title","description":"Job title or role label.","examples":["Field Director"]},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url","description":"URL of the user's avatar image.","examples":["https://cdn.campaignbrain.dev/avatars/jane.png"]},"timezone":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Timezone","description":"IANA timezone name for the user.","examples":["America/New_York"]},"locale":{"anyOf":[{"type":"string","maxLength":35},{"type":"null"}],"title":"Locale","description":"BCP-47 locale tag for the user's preferred language.","examples":["en-US"]},"external_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"External Id","description":"Opaque identifier from an external system (SSO, HRIS, etc.).","examples":["okta|00u1a2b3c4"]},"user_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"User Metadata","description":"Free-form JSON metadata bag for caller-defined attributes.","examples":[{"cohort":"2026","department":"field"}]}},"additionalProperties":false,"type":"object","required":["email"],"title":"UserCreate","description":"Create a new user account.\n\nUsed by admin tooling and cbonboard. Service-account principals are minted\nvia ``POST /api/v1/service-accounts`` instead, not through this endpoint.","examples":[{"email":"new.user@campaignbrain.dev","first_name":"Jane","group_ids":["grp_staff"],"kind":"human","last_name":"Doe","password":"my-strong-passphrase"}]},"cbauth__UserMe":{"properties":{"id":{"type":"string","title":"Id","description":"User identifier.","examples":["usr_01HX..."]},"email":{"type":"string","title":"Email","description":"Account email address.","examples":["jane@campaignbrain.dev"]},"group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Id","description":"Legacy single-group FK. Prefer ``groups``.","examples":["grp_staff"]},"groups":{"items":{"$ref":"#/components/schemas/cbauth__GroupBrief"},"type":"array","title":"Groups","description":"M2M groups — the authoritative membership list (user_groups table). Downstream services (cbloom auth resolver, HR module, etc.) must aggregate permissions across all groups here, not just ``group_id``.","default":[]},"businesses":{"items":{"$ref":"#/components/schemas/cbauth__BusinessAssignment"},"type":"array","title":"Businesses","description":"Property assignments for per-property scoping (cbauth#60).","default":[]},"current_tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Tenant","description":"The tenant slug the active session is scoped to (Trinity Step 3).","examples":["greenroom"]},"tenants":{"items":{"$ref":"#/components/schemas/cbauth__TenantMembership"},"type":"array","title":"Tenants","description":"Every tenant membership for this user.","default":[]},"permissions":{"items":{"type":"string"},"type":"array","title":"Permissions","description":"Effective permissions sieved by ``current_tenant``. Saves downstream services (cbloom card filter, etc.) from a second cbauth round-trip.","default":[],"examples":[["cbloom.*.*","campaigns.voters.read"]]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the account is active.","examples":[true]},"email_confirmed":{"type":"boolean","title":"Email Confirmed","description":"Whether the email address has been confirmed.","examples":[true]},"email_confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Email Confirmed At","description":"Timestamp the email was confirmed, if any."},"phone_confirmed":{"type":"boolean","title":"Phone Confirmed","description":"Whether the phone number has been confirmed.","default":false,"examples":[false]},"phone_confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Phone Confirmed At","description":"Timestamp the phone was confirmed, if any."},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"User's given name.","examples":["Jane"]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"User's family name.","examples":["Doe"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Contact phone number.","examples":["+1-202-555-0142"]},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name","description":"Preferred display name.","examples":["Jane Doe"]},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Job title or role label.","examples":["Field Director"]},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url","description":"URL of the user's avatar image."},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone","description":"IANA timezone name.","examples":["America/New_York"]},"locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale","description":"BCP-47 locale tag.","examples":["en-US"]},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Opaque identifier from an external system."},"user_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"User Metadata","description":"Free-form JSON metadata bag."},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen At","description":"Timestamp the user was last seen active."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp the account was created."}},"type":"object","required":["id","email","group_id","is_active","email_confirmed","created_at"],"title":"UserMe","description":"Current authenticated user's profile.\n\nReturned by ``GET /api/v1/users/me``. Carries the caller's effective\npermissions already sieved by ``current_tenant`` so downstream services\ndon't need a second cbauth round-trip.","examples":[{"businesses":[],"created_at":"2026-01-15T12:00:00Z","current_tenant":"greenroom","email":"jane@campaignbrain.dev","email_confirmed":true,"group_id":"grp_staff","groups":[{"id":"grp_staff","name":"Staff"}],"id":"usr_01HX...","is_active":true,"permissions":["cbloom.*.*","campaigns.voters.read"],"phone_confirmed":false,"tenants":[{"role":"admin","tenant_slug":"greenroom"}]}]},"cbauth__UserResponse":{"properties":{"id":{"type":"string","title":"Id","description":"User identifier.","examples":["usr_01HX..."]},"email":{"type":"string","title":"Email","description":"Account email address.","examples":["jane@campaignbrain.dev"]},"group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Id","description":"Legacy single-group FK. Prefer ``groups``.","examples":["grp_staff"]},"groups":{"items":{"$ref":"#/components/schemas/cbauth__GroupBrief"},"type":"array","title":"Groups","description":"Current many-to-many group memberships (from the user_groups table). Prefer this over ``group_id``, which is the legacy single-group FK.","default":[]},"businesses":{"items":{"$ref":"#/components/schemas/cbauth__BusinessAssignment"},"type":"array","title":"Businesses","description":"Property assignments (from the user_businesses table). Used by HR + any module that needs per-property scoping. Empty for users with no property assignment (e.g. portfolio admins who see all properties via permissions, not assignments).","default":[]},"tenants":{"items":{"$ref":"#/components/schemas/cbauth__TenantMembership"},"type":"array","title":"Tenants","description":"Tenant memberships (from the user_tenants table). Symmetric with ``UserMe``. Lets admins filter the global user list to 'users on tenant X' without an N+1 detail-fetch pass — required by the cbloom Atlas/Boarding wizard.","default":[]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the account is active.","examples":[true]},"email_confirmed":{"type":"boolean","title":"Email Confirmed","description":"Whether the email address has been confirmed.","examples":[true]},"email_confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Email Confirmed At","description":"Timestamp the email was confirmed, if any."},"phone_confirmed":{"type":"boolean","title":"Phone Confirmed","description":"Whether the phone number has been confirmed.","default":false,"examples":[false]},"phone_confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Phone Confirmed At","description":"Timestamp the phone was confirmed, if any."},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"User's given name.","examples":["Jane"]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"User's family name.","examples":["Doe"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Contact phone number.","examples":["+1-202-555-0142"]},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name","description":"Preferred display name.","examples":["Jane Doe"]},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Job title or role label.","examples":["Field Director"]},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url","description":"URL of the user's avatar image."},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone","description":"IANA timezone name.","examples":["America/New_York"]},"locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale","description":"BCP-47 locale tag.","examples":["en-US"]},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Opaque identifier from an external system."},"user_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"User Metadata","description":"Free-form JSON metadata bag."},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen At","description":"Timestamp the user was last seen active."},"totp_enabled":{"type":"boolean","title":"Totp Enabled","description":"Whether authenticator-app (TOTP) 2FA is active. Surfaced so admin UIs can show a 2FA badge and offer a reset when a user is locked out of both their authenticator and backup codes (``POST /users/{id}/mfa/reset``).","default":false,"examples":[false]},"sms_mfa_enabled":{"type":"boolean","title":"Sms Mfa Enabled","description":"Whether SMS one-time-code 2FA is active.","default":false,"examples":[false]},"failed_login_attempts":{"type":"integer","title":"Failed Login Attempts","description":"Lockout counter — surfaced so admin UIs (cbloom auth.html) can show a 'Locked' badge + offer an unlock action. Cleared automatically on next successful password login, OR via ``POST /users/{id}/unlock``.","default":0,"examples":[0,3]},"locked_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Locked Until","description":"If set, the account is locked until this timestamp."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp the account was created."}},"type":"object","required":["id","email","group_id","is_active","email_confirmed","created_at"],"title":"UserResponse","description":"User account details returned by the API (admin list/detail shape).","examples":[{"businesses":[],"created_at":"2026-01-15T12:00:00Z","email":"jane@campaignbrain.dev","email_confirmed":true,"failed_login_attempts":0,"first_name":"Jane","group_id":"grp_staff","groups":[{"id":"grp_staff","name":"Staff"}],"id":"usr_01HX...","is_active":true,"last_name":"Doe","phone_confirmed":false,"tenants":[{"role":"admin","tenant_slug":"greenroom"}]}]},"cbauth__UserUpdate":{"properties":{"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"New email address (must remain unique).","examples":["jane.doe@campaignbrain.dev"]},"group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Id","description":"Legacy single-group FK. Prefer ``group_ids``.","examples":["grp_staff"]},"group_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Group Ids","description":"When provided, replaces user_groups M2M rows wholesale. An empty list means 'remove from all groups'. Use this instead of ``group_id`` to set multi-group membership.","examples":[["grp_staff","grp_hr"]]},"business_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Business Ids","description":"When provided, replaces user_businesses M2M rows wholesale. An empty list removes all property assignments.","examples":[["biz_the-saint"]]},"confirm_remove_protected":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Confirm Remove Protected","description":"Required to remove a user from a protected group (e.g. superadmin). The server returns 409 if the PATCH would drop a protected membership and this flag is not explicitly true. Belt-and-suspenders against stale-UI writes (cbloom#70).","examples":[true]},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Activate or deactivate the account.","examples":[true]},"email_confirmed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Confirmed","description":"Override the email-confirmed flag.","examples":[true]},"phone_confirmed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Phone Confirmed","description":"Override the phone-confirmed flag.","examples":[true]},"first_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"First Name","description":"User's given name.","examples":["Jane"]},"last_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Last Name","description":"User's family name.","examples":["Doe"]},"phone":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Phone","description":"Contact phone number.","examples":["+1-202-555-0142"]},"display_name":{"anyOf":[{"type":"string","maxLength":150},{"type":"null"}],"title":"Display Name","description":"Preferred display name shown in UIs.","examples":["Jane Doe"]},"title":{"anyOf":[{"type":"string","maxLength":150},{"type":"null"}],"title":"Title","description":"Job title or role label.","examples":["Field Director"]},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url","description":"URL of the user's avatar image.","examples":["https://cdn.campaignbrain.dev/avatars/jane.png"]},"timezone":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Timezone","description":"IANA timezone name for the user.","examples":["America/New_York"]},"locale":{"anyOf":[{"type":"string","maxLength":35},{"type":"null"}],"title":"Locale","description":"BCP-47 locale tag for the user's preferred language.","examples":["en-US"]},"external_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"External Id","description":"Opaque identifier from an external system (SSO, HRIS, etc.).","examples":["okta|00u1a2b3c4"]},"user_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"User Metadata","description":"Free-form JSON metadata bag for caller-defined attributes.","examples":[{"department":"field"}]}},"additionalProperties":false,"type":"object","title":"UserUpdate","description":"Partial update to an existing user account.\n\nAll fields are optional; only supplied fields are changed. ``group_ids`` and\n``business_ids`` replace their respective M2M tables wholesale when present.","examples":[{"is_active":true,"title":"Senior Field Director"},{"confirm_remove_protected":true,"group_ids":["grp_staff"]}]},"cbauth__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbbanking__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbbanking__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbbanking__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbbanking__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbbanking__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbbanking__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbbanking__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbbanking__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbbanking__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbbanking__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbbanking__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbbanking__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbbanking__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbbanking__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbbilling__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbbilling__GenerateRequest":{"properties":{"slug":{"type":"string","title":"Slug","examples":["victory"]},"month":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}$"},{"type":"null"}],"title":"Month","examples":["2026-06"]}},"type":"object","required":["slug"],"title":"GenerateRequest"},"cbbilling__GrantRequest":{"properties":{"tenant":{"type":"string","title":"Tenant","examples":["victory"]},"bundle":{"type":"string","title":"Bundle","examples":["elections"]}},"type":"object","required":["tenant","bundle"],"title":"GrantRequest"},"cbbilling__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbbilling__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbbilling__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbbilling__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbbilling__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbbilling__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbbilling__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbbilling__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbbilling__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbbilling__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbbilling__RunRequest":{"properties":{"month":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}$"},{"type":"null"}],"title":"Month","examples":["2026-06"]}},"type":"object","title":"RunRequest"},"cbbilling__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbbilling__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbbilling__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbcalendar__Attendee":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Attendee email."},"display_name":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Display Name","description":"Display name. May be None if the provider does not return one."},"response_status":{"$ref":"#/components/schemas/cbcalendar__AttendeeResponseStatus","description":"RSVP state.","default":"needs_action"},"is_organizer":{"type":"boolean","title":"Is Organizer","description":"Whether this attendee is the organizer.","default":false},"is_optional":{"type":"boolean","title":"Is Optional","description":"Whether attendance is optional.","default":false}},"additionalProperties":false,"type":"object","required":["email"],"title":"Attendee","description":"An attendee of an event.\n\nPersisted with the event but never resolved against external contact\ndirectories by cbcalendar; that is the consumer's responsibility (e.g.,\ncbgreenroom checks recusal lists before adding attendees).","example":{"display_name":"Press Lead","email":"press@example.org","is_optional":false,"is_organizer":false,"response_status":"accepted"}},"cbcalendar__AttendeeResponseStatus":{"type":"string","enum":["needs_action","accepted","declined","tentative"],"title":"AttendeeResponseStatus","description":"RSVP state for an attendee."},"cbcalendar__Calendar":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record."},"tenant_id":{"type":"string","maxLength":64,"minLength":2,"pattern":"^[a-z0-9][a-z0-9_-]*$","title":"Tenant Id","description":"Tenant this record belongs to. Matches the slug-form Tenant.id (e.g. 'perry-2026'). cbcalendar stores tenants by slug rather than UUID so external callers can address them stably."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified."},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records."},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records."},"connection_id":{"type":"string","format":"uuid","title":"Connection Id","description":"The CalendarConnection this calendar is exposed through."},"user_id":{"type":"string","format":"uuid","title":"User Id","description":"The owning User. Cached for query convenience."},"provider":{"$ref":"#/components/schemas/cbcalendar__CalendarProvider","description":"Provider tag, mirrored from the connection."},"provider_calendar_id":{"type":"string","maxLength":500,"title":"Provider Calendar Id","description":"Provider-side calendar identifier."},"display_name":{"type":"string","maxLength":300,"title":"Display Name","description":"Human-readable calendar name."},"timezone":{"type":"string","maxLength":100,"title":"Timezone","description":"IANA timezone name. Calendar-local timezone reported by the provider.","default":"UTC"},"is_primary":{"type":"boolean","title":"Is Primary","description":"Whether this is the user's primary calendar with the provider.","default":false},"is_writable":{"type":"boolean","title":"Is Writable","description":"Whether the connection has write access to this calendar.","default":true},"color_hex":{"anyOf":[{"type":"string","maxLength":7},{"type":"null"}],"title":"Color Hex","description":"Provider-reported display color, if any."}},"additionalProperties":false,"type":"object","required":["tenant_id","connection_id","user_id","provider","provider_calendar_id","display_name"],"title":"Calendar","description":"A logical calendar exposed by a CalendarConnection.\n\nOne Calendar per provider-side calendar. Most users have multiple\n(primary, secondary, shared). Carries the provider's calendar identifier\nplus display metadata.","example":{"color_hex":"#3b6ef5","connection_id":"3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f","display_name":"Scheduler — Primary","id":"7b9c1e2a-4d5f-4a6b-8c0d-1e2f3a4b5c6d","is_primary":true,"is_writable":true,"provider":"google","provider_calendar_id":"scheduler@example.org","tenant_id":"perry-2026","timezone":"America/New_York","user_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"}},"cbcalendar__CalendarConnection":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record."},"tenant_id":{"type":"string","maxLength":64,"minLength":2,"pattern":"^[a-z0-9][a-z0-9_-]*$","title":"Tenant Id","description":"Tenant this record belongs to. Matches the slug-form Tenant.id (e.g. 'perry-2026'). cbcalendar stores tenants by slug rather than UUID so external callers can address them stably."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified."},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records."},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records."},"user_id":{"type":"string","format":"uuid","title":"User Id","description":"The User who owns this connection."},"provider":{"$ref":"#/components/schemas/cbcalendar__CalendarProvider","description":"Calendar provider this connection talks to."},"provider_account_identifier":{"type":"string","maxLength":500,"title":"Provider Account Identifier","description":"Provider-side account identifier. For Google, the account email. For CalDAV, the server URL plus username. For Mock, a synthetic id."},"status":{"$ref":"#/components/schemas/cbcalendar__ConnectionStatus","description":"Current connection status.","default":"active"},"access_token":{"anyOf":[{"$ref":"#/components/schemas/cbcalendar__EncryptedSecret"},{"type":"null"}],"description":"Encrypted access token. None for Mock connections. May be None for Google connections immediately after refresh failure."},"refresh_token":{"anyOf":[{"$ref":"#/components/schemas/cbcalendar__EncryptedSecret"},{"type":"null"}],"description":"Encrypted refresh token. None for Mock connections and providers without refresh."},"access_token_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Access Token Expires At","description":"UTC timestamp when the access token expires."},"last_refreshed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refreshed At","description":"UTC timestamp of the last successful token refresh."},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"UTC timestamp of the last operation against this connection."},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At","description":"UTC timestamp when this connection was revoked. None unless status is REVOKED."},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"OAuth scopes granted to this connection."},"service_account_json":{"anyOf":[{"$ref":"#/components/schemas/cbcalendar__EncryptedSecret"},{"type":"null"}],"description":"Encrypted Google service-account JSON. Populated for Workspace connections that use Domain-Wide Delegation; None for Mock and for OAuth-2.0-style user-consent connections (when those land)."},"impersonated_user_email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Impersonated User Email","description":"The Workspace user this connection acts as. The service account calls Google APIs with `Credentials.with_subject(<this email>)`. Required when service_account_json is set; ignored otherwise."}},"additionalProperties":false,"type":"object","required":["tenant_id","user_id","provider","provider_account_identifier"],"title":"CalendarConnection","description":"A user's authenticated connection to a calendar provider.\n\nOwned by a specific user. Stores encrypted refresh and access tokens.\nThe provider-specific account identifier (Google account email, CalDAV\nserver URL, etc.) is recorded for display.","example":{"id":"3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f","impersonated_user_email":"scheduler@example.org","provider":"google","provider_account_identifier":"scheduler@example.org","scopes":["https://www.googleapis.com/auth/calendar","https://www.googleapis.com/auth/calendar.events"],"service_account_json":{"ciphertext":"gAAAAABl...base64-fernet-envelope...","encrypted_at":"2026-07-01T09:00:00Z","key_id":"tenant-data-key-v1"},"status":"active","tenant_id":"perry-2026","user_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"}},"cbcalendar__CalendarProvider":{"type":"string","enum":["mock","google","apple_caldav","outlook"],"title":"CalendarProvider","description":"Calendar provider implementations.\n\nMOCK is used in demo mode and tests; it persists events to the cbcalendar\ndatabase but never reaches a real provider API. GOOGLE is the Phase 1\nimplementation. APPLE_CALDAV and OUTLOOK ship in Phase 2."},"cbcalendar__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbcalendar__ConnectionStatus":{"type":"string","enum":["active","expired","revoked"],"title":"ConnectionStatus","description":"Lifecycle of a CalendarConnection.\n\nACTIVE connections have valid refresh tokens. EXPIRED indicates the refresh\ntoken failed (typically because the user revoked access provider-side).\nREVOKED indicates the user or operator explicitly revoked through cbcalendar."},"cbcalendar__EncryptedSecret":{"properties":{"ciphertext":{"type":"string","title":"Ciphertext","description":"Base64-encoded encrypted bytes."},"key_id":{"type":"string","title":"Key Id","description":"Identifier for the data key used to encrypt this secret."},"encrypted_at":{"type":"string","format":"date-time","title":"Encrypted At","description":"UTC timestamp when this secret was encrypted."}},"additionalProperties":false,"type":"object","required":["ciphertext","key_id","encrypted_at"],"title":"EncryptedSecret","description":"Container for an encrypted secret.\n\nThe plaintext is never present on this model in flight. The storage layer\nencrypts on write and decrypts on read using a tenant-scoped data key.","example":{"ciphertext":"gAAAAABl...base64-fernet-envelope...","encrypted_at":"2026-07-01T09:00:00Z","key_id":"tenant-data-key-v1"}},"cbcalendar__Event":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record."},"tenant_id":{"type":"string","maxLength":64,"minLength":2,"pattern":"^[a-z0-9][a-z0-9_-]*$","title":"Tenant Id","description":"Tenant this record belongs to. Matches the slug-form Tenant.id (e.g. 'perry-2026'). cbcalendar stores tenants by slug rather than UUID so external callers can address them stably."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified."},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records."},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records."},"calendar_id":{"type":"string","format":"uuid","title":"Calendar Id","description":"The Calendar this event lives on."},"provider":{"$ref":"#/components/schemas/cbcalendar__CalendarProvider","description":"Provider this event was written to."},"provider_event_id":{"type":"string","maxLength":500,"title":"Provider Event Id","description":"Provider-side event identifier. For Mock connections, a synthetic id. Stable across patches; used for provider-side updates."},"title":{"type":"string","maxLength":500,"title":"Title","description":"Event title."},"description":{"anyOf":[{"type":"string","maxLength":20000},{"type":"null"}],"title":"Description","description":"Event description."},"start_at":{"type":"string","format":"date-time","title":"Start At","description":"UTC start time."},"end_at":{"type":"string","format":"date-time","title":"End At","description":"UTC end time."},"location":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Location","description":"Free-text location."},"attendees":{"items":{"$ref":"#/components/schemas/cbcalendar__Attendee"},"type":"array","title":"Attendees","description":"Attendees as last synced from the provider."},"timezone":{"type":"string","maxLength":100,"title":"Timezone","description":"IANA timezone for display.","default":"UTC"},"status":{"$ref":"#/components/schemas/cbcalendar__EventStatus","description":"Event lifecycle status.","default":"confirmed"},"mode":{"$ref":"#/components/schemas/cbcalendar__EventMode","description":"Which mode this event was written in. DEMO events did not reach a real provider. LIVE events did. Set by the service at create time; never modified after."},"source_system":{"type":"string","title":"Source System","description":"Originating CB service."},"source_uri":{"type":"string","title":"Source Uri","description":"cb:// URI of the originating record."},"source_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Source Metadata","description":"Optional unstructured context from the originating record."},"related_event_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Related Event Id","description":"Reference to a related Event. Used by composite endpoints: a prep block points to its primary event via related_event_id."},"last_synced_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Synced At","description":"UTC timestamp of the last successful provider sync for this event."},"cancelled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cancelled At","description":"UTC timestamp when this event was cancelled. None unless status is CANCELLED."},"cancellation_reason":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Cancellation Reason","description":"Reason for cancellation. Free text. Pulled into audit on cancel."}},"additionalProperties":false,"type":"object","required":["tenant_id","calendar_id","provider","provider_event_id","title","start_at","end_at","mode","source_system","source_uri"],"title":"Event","description":"A calendar event.\n\nAuthoritative for cbcalendar metadata; the canonical event lives at the\nprovider. Carries source linkage and mode for audit. Events can reference\neach other via related_event_id (used for primary + prep block pairs).","example":{"attendees":[],"calendar_id":"7b9c1e2a-4d5f-4a6b-8c0d-1e2f3a4b5c6d","created_at":"2026-07-01T09:00:00Z","description":"5-minute live remote.","end_at":"2026-07-15T13:10:00Z","id":"0c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f","location":"WMUR Studio B, Manchester NH","mode":"live","provider":"google","provider_event_id":"g_4f8d2c1b9a","source_system":"cbgreenroom","source_uri":"cb://cbgreenroom/bookings/abc123","start_at":"2026-07-15T13:00:00Z","status":"confirmed","tenant_id":"perry-2026","timezone":"America/New_York","title":"WMUR morning hit — Gov. candidate","updated_at":"2026-07-01T09:00:00Z"}},"cbcalendar__EventDraft":{"properties":{"calendar_id":{"type":"string","format":"uuid","title":"Calendar Id","description":"The Calendar to create this event on."},"title":{"type":"string","maxLength":500,"title":"Title","description":"Event title."},"description":{"anyOf":[{"type":"string","maxLength":20000},{"type":"null"}],"title":"Description","description":"Event description. Plain text or HTML; format is preserved as given. Disclosure paragraphs from cbgreenroom arrive pre-injected here."},"start_at":{"type":"string","format":"date-time","title":"Start At","description":"UTC start time."},"end_at":{"type":"string","format":"date-time","title":"End At","description":"UTC end time."},"location":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Location","description":"Free-text location. Studio address, dial-in URL, conference room name, etc."},"attendees":{"items":{"$ref":"#/components/schemas/cbcalendar__Attendee"},"type":"array","title":"Attendees","description":"Attendees to invite. Empty list means no invitations sent."},"timezone":{"type":"string","maxLength":100,"title":"Timezone","description":"IANA timezone for display rendering. Storage is always UTC.","default":"UTC"},"source_system":{"type":"string","title":"Source System","description":"Originating CB service. e.g. 'cbgreenroom'."},"source_uri":{"type":"string","title":"Source Uri","description":"cb:// URI of the originating record."},"source_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Source Metadata","description":"Optional unstructured context from the originating record."},"related_event_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Related Event Id","description":"Reference to a related Event. Used by composite endpoints: a prep block points to its primary event via related_event_id."}},"additionalProperties":false,"type":"object","required":["calendar_id","title","start_at","end_at","source_system","source_uri"],"title":"EventDraft","description":"Input payload for creating an Event.\n\nCarries everything required to create. Server-generated fields (id,\ntimestamps, provider_event_id, mode, audit chain) are not included.","example":{"attendees":[{"display_name":"Press Lead","email":"press@example.org","is_optional":false,"is_organizer":true,"response_status":"needs_action"}],"calendar_id":"7b9c1e2a-4d5f-4a6b-8c0d-1e2f3a4b5c6d","description":"5-minute live remote. Disclosure paragraph injected by cbgreenroom.","end_at":"2026-07-15T13:10:00Z","location":"WMUR Studio B, Manchester NH","source_system":"cbgreenroom","source_uri":"cb://cbgreenroom/bookings/abc123","start_at":"2026-07-15T13:00:00Z","timezone":"America/New_York","title":"WMUR morning hit — Gov. candidate"}},"cbcalendar__EventMode":{"type":"string","enum":["demo","live"],"title":"EventMode","description":"Which mode an event was written in.\n\nRecorded on every Event so the audit trail can distinguish demo-mode-only\nevents from real-provider events when a tenant is promoted to live."},"cbcalendar__EventPatch":{"properties":{"title":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Title","description":"New event title. Omit to leave unchanged."},"description":{"anyOf":[{"type":"string","maxLength":20000},{"type":"null"}],"title":"Description","description":"New event description. Omit to leave unchanged."},"start_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start At","description":"New UTC start time. Omit to leave unchanged."},"end_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End At","description":"New UTC end time. Omit to leave unchanged."},"location":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Location","description":"New free-text location. Omit to leave unchanged."},"attendees":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbcalendar__Attendee"},"type":"array"},{"type":"null"}],"title":"Attendees","description":"Replacement attendee list. When provided, replaces the full set; omit to leave the existing attendees unchanged."},"status":{"anyOf":[{"$ref":"#/components/schemas/cbcalendar__EventStatus"},{"type":"null"}],"description":"New lifecycle status. Omit to leave unchanged."}},"additionalProperties":false,"type":"object","title":"EventPatch","description":"Patch payload for updating an Event.\n\nAll fields optional. Only provided fields are updated. Patch operations\naudit the before/after deltas.","example":{"end_at":"2026-07-15T14:10:00Z","location":"WMUR Studio A (room change)","start_at":"2026-07-15T14:00:00Z"}},"cbcalendar__EventStatus":{"type":"string","enum":["confirmed","tentative","cancelled"],"title":"EventStatus","description":"Calendar event status.\n\nMirrors the standard tri-state used by Google, CalDAV, and Outlook."},"cbcalendar__GoogleConnectionRegistration":{"properties":{"user_id":{"type":"string","format":"uuid","title":"User Id","description":"The cbcalendar User this connection belongs to."},"service_account_json":{"type":"string","title":"Service Account Json","description":"The full JSON contents of a Google service-account key file (must include ``\"type\": \"service_account\"``). Generated via Google Cloud Console per docs/GOOGLE.md §6.1."},"impersonated_user_email":{"type":"string","format":"email","title":"Impersonated User Email","description":"The Workspace user this connection acts as via Domain-Wide Delegation. Must exist in the Workspace org and be covered by the DWD grant configured in admin.google.com."},"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes","description":"OAuth scopes granted via DWD. Defaults to the cbcalendar set (``calendar`` + ``calendar.events``). Must match what was actually granted in the Workspace admin console."}},"type":"object","required":["user_id","service_account_json","impersonated_user_email"],"title":"GoogleConnectionRegistration","description":"Body for POST /connections/google/register.\n\nThe plaintext ``service_account_json`` lives on this model only for the\nduration of one HTTPS request — the service Fernet-encrypts it before\nit touches DuckDB. Never logged.","example":{"impersonated_user_email":"scheduler@example.org","scopes":["https://www.googleapis.com/auth/calendar","https://www.googleapis.com/auth/calendar.events"],"service_account_json":"{\"type\": \"service_account\", \"project_id\": \"cb-calendar\", \"private_key_id\": \"…\", \"client_email\": \"cal-sa@cb-calendar.iam.gserviceaccount.com\"}","user_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"}},"cbcalendar__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbcalendar__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbcalendar__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbcalendar__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbcalendar__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcalendar__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbcalendar__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbcalendar__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbcalendar__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbcalendar__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbcalendar__PromoteToLiveBody":{"properties":{"promotion_evidence":{"additionalProperties":true,"type":"object","title":"Promotion Evidence","description":"Evidence required for the promotion. Should include ``counsel_signoff_id``, ``operator_user_id``, and a free-text ``confirmation``. Structure validated server-side in Phase 3."}},"type":"object","required":["promotion_evidence"],"title":"PromoteToLiveBody","description":"Request body for ``POST /tenants/{id}/mode/promote_to_live``.\n\nCarries the evidence that justifies flipping a tenant from demo to live.\nThe payload is recorded verbatim on the emitted ``mode_changed`` audit\nevent so the promotion is traceable after the fact.","example":{"promotion_evidence":{"confirmation":"Verified DWD grant + counsel approval on file.","counsel_signoff_id":"signoff-2026-0042","operator_user_id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"}}},"cbcalendar__ServiceMode":{"properties":{"mode":{"type":"string","title":"Mode","description":"Either 'demo' or 'live'."}},"type":"object","required":["mode"],"title":"ServiceMode","description":"The service-level mode flag.\n\nReturned by ``GET /mode``. Independent of any tenant's mode — this is the\nprocess-wide setting from ``CBCALENDAR_MODE``. When the service is in demo,\nevery tenant's outbound calls are suppressed regardless of tenant mode.","example":{"mode":"demo"}},"cbcalendar__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbcalendar__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcalendar__Tenant":{"properties":{"id":{"type":"string","maxLength":64,"minLength":2,"pattern":"^[a-z0-9][a-z0-9_-]*$","title":"Id","description":"Stable tenant identifier (the slug). e.g. 'perry-2026'."},"display_name":{"type":"string","maxLength":200,"title":"Display Name","description":"Human-readable tenant name."},"mode":{"$ref":"#/components/schemas/cbcalendar__TenantMode","description":"Current operating mode. New tenants always start in DEMO.","default":"demo"},"outbound_locked":{"type":"boolean","title":"Outbound Locked","description":"Hard lock on real provider calls. True forces MockProvider routing regardless of mode. Defaults to True for safety.","default":true}},"additionalProperties":false,"type":"object","required":["id","display_name"],"title":"Tenant","description":"A tenant in cbcalendar.\n\nNot a Resource (does not carry tenant_id; it is a tenant). Mode and\noutbound_locked drive whether real provider calls happen for this tenant's\noperations.","example":{"display_name":"Perry for Governor 2026","id":"perry-2026","mode":"demo","outbound_locked":true}},"cbcalendar__TenantMode":{"type":"string","enum":["demo","live"],"title":"TenantMode","description":"Demo vs live mode for a tenant in cbcalendar.\n\nTenants in DEMO produce no real provider calls. Promotion requires evidence.\nMirrors cbgreenroom.enums.TenantMode but is its own enum because cbcalendar\nis independently deployable."},"cbcalendar__TenantModeView":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant slug this view describes."},"mode":{"type":"string","title":"Mode","description":"The tenant's own mode flag: 'demo' or 'live'."},"outbound_locked":{"type":"boolean","title":"Outbound Locked","description":"The tenant's hard lock on real provider calls."},"outbound_allowed":{"type":"boolean","title":"Outbound Allowed","description":"True iff this tenant's calls reach a real provider. False when the service is in demo mode, the tenant is in demo mode, or outbound_locked is true."}},"type":"object","required":["tenant_id","mode","outbound_locked","outbound_allowed"],"title":"TenantModeView","description":"Effective mode for a tenant, accounting for tenant override.\n\nReturned by ``GET /tenants/{id}/mode`` and the promotion endpoint. The\n``outbound_allowed`` field is the load-bearing signal — the others are the\ninputs that determine it.","example":{"mode":"demo","outbound_allowed":false,"outbound_locked":true,"tenant_id":"perry-2026"}},"cbcalendar__User":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record."},"tenant_id":{"type":"string","maxLength":64,"minLength":2,"pattern":"^[a-z0-9][a-z0-9_-]*$","title":"Tenant Id","description":"Tenant this record belongs to. Matches the slug-form Tenant.id (e.g. 'perry-2026'). cbcalendar stores tenants by slug rather than UUID so external callers can address them stably."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified."},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records."},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records."},"auth_user_id":{"type":"string","maxLength":200,"title":"Auth User Id","description":"Reference to the cbauth user record. Stored as a string identifier."},"email":{"type":"string","format":"email","title":"Email","description":"Primary email for this user."},"display_name":{"type":"string","maxLength":200,"title":"Display Name","description":"Human-readable user name."},"active":{"type":"boolean","title":"Active","description":"Whether this user is currently active.","default":true}},"additionalProperties":false,"type":"object","required":["tenant_id","auth_user_id","email","display_name"],"title":"User","description":"A user principal in cbcalendar.\n\nAuthenticated through cbauth. Owns one or more CalendarConnection records.\ncbcalendar does not concern itself with the user's role in cbgreenroom;\npermissions are scoped to calendar operations only.","example":{"active":true,"auth_user_id":"auth_usr_8842","display_name":"Campaign Scheduler","email":"scheduler@example.org","id":"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","tenant_id":"perry-2026"}},"cbcalendar__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbcampaign__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbcampaign__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbcampaign__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbcampaign__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcampaign__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbcampaign__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbcampaign__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbcampaign__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbcampaign__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbcampaign__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbcampaign__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcaptive__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbcaptive__ConnectBody":{"properties":{"mac":{"type":"string","title":"Mac"},"ap_mac":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ap Mac"},"ssid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ssid"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Phone"},"marketing_opt_in":{"type":"boolean","title":"Marketing Opt In","default":false},"tos":{"type":"boolean","title":"Tos","default":false},"returning":{"type":"boolean","title":"Returning","default":false},"website":{"type":"string","title":"Website","default":""}},"type":"object","required":["mac"],"title":"ConnectBody"},"cbcaptive__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbcaptive__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbcaptive__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbcaptive__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbcaptive__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcaptive__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbcaptive__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbcaptive__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbcaptive__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbcaptive__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbcaptive__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbcaptive__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcaptive__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbcaptive__VoucherBody":{"properties":{"mac":{"type":"string","title":"Mac"},"code":{"type":"string","maxLength":16,"minLength":4,"title":"Code"},"ap_mac":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ap Mac"},"ssid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ssid"}},"type":"object","required":["mac","code"],"title":"VoucherBody"},"cbcast__ChannelBody":{"properties":{"id":{"type":"string","title":"Id","description":"Channel id (the machine key devices bind to).","examples":["lobby","results-wall"]},"mode":{"type":"string","title":"Mode","description":"Initial scene-advance mode: `manual` (operator pins the scene) or `slideshow` (rotate a playlist on an interval).","default":"manual","examples":["manual","slideshow"]}},"type":"object","required":["id"],"title":"ChannelBody","description":"A channel to create (idempotent by ``id``)."},"cbcast__ChannelMetaBody":{"properties":{"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name","description":"Operator-facing label; UIs fall back to the channel id when empty.","examples":["Main Lobby"]},"hidden":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Hidden","description":"Pull the channel out of active programming/pickers (kept, not deleted)."},"channel_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Channel Number","description":"Numeric channel for a personal remote (1-99, unique across channels). Pass `null` to clear it.","examples":[2,12]},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind","description":"Delivery kind: `base` (SVG kiosk) or `enhanced` (a web app / URL rendered by the box's browser).","examples":["base","enhanced"]},"web_app":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Web App","description":"Enhanced channels: id of the bundled app under `/opt/cbcast-web/<web_app>`.","examples":["results"]},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Enhanced 'URL flavor': the URL cog loads on the box (e.g. a gated Loom app).","examples":["https://greenroom.campaignbrain.dev/app/elections-map"]},"classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification","description":"Content sensitivity (DESIGN §14): one of `public`, `internal`, `confidential`, `restricted`. A device shows this channel only if its clearance is at or above this level — so e.g. a financial board set to `confidential` won't appear on a `public` Lobby screen.","examples":["public","confidential"]}},"type":"object","title":"ChannelMetaBody","description":"Partial update of channel metadata. Only fields actually present in the\nrequest body are applied (via model_fields_set), so omitting a field leaves\nit unchanged while passing channel_number: null clears it.","examples":[{"channel_number":2,"display_name":"Main Lobby"},{"kind":"enhanced","url":"https://greenroom.campaignbrain.dev/app/elections-map"},{"classification":"confidential"},{"hidden":true}]},"cbcast__ClaimBody":{"properties":{"device_id":{"type":"string","title":"Device Id","description":"Stable machine id of the box (e.g. its serial / MAC-derived id).","examples":["03A8BD","8F17C0"]},"hostname":{"type":"string","title":"Hostname","description":"The box's reported hostname (display/diagnostic only).","default":"","examples":["lobby-tv"]},"channel_id":{"type":"string","title":"Channel Id","description":"Optional channel to bind the box to at claim time.","default":"","examples":["lobby"]}},"type":"object","required":["device_id"],"title":"ClaimBody","description":"Claim (register) a box and mint it a device token. Idempotent by\n``device_id`` — re-claiming keeps the existing token."},"cbcast__ClassificationBody":{"properties":{"classification":{"type":"string","title":"Classification","description":"Highest content sensitivity this screen may show: one of `public`, `internal`, `confidential`, `restricted`. A channel classified above this is withheld (the screen holds standby) — so financials never land on a `public` Lobby panel.","examples":["public","confidential"]}},"type":"object","required":["classification"],"title":"ClassificationBody","description":"Set a device's display clearance ceiling (DESIGN §14)."},"cbcast__CommandBody":{"properties":{"action":{"type":"string","title":"Action","description":"One of: `reboot`, `restart`, `recalibrate`, `identify`, `release`.","examples":["restart","reboot","identify"]}},"type":"object","required":["action"],"title":"CommandBody","description":"Queue an out-of-band command for a box, delivered on its next poll."},"cbcast__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbcast__CurrentBody":{"properties":{"scene_id":{"type":"string","title":"Scene Id","description":"Id of an existing scene to make the channel's current scene.","examples":["a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"]}},"type":"object","required":["scene_id"],"title":"CurrentBody","description":"Pin a channel to a specific scene (manual push)."},"cbcast__DataBody":{"properties":{"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Arbitrary JSON snapshot relayed verbatim to the channel's web app.","examples":[{"headline":"Polls close at 8pm","races":[]}]},"name":{"type":"string","title":"Name","description":"Optional label for the resulting content-addressed data scene.","default":"","examples":["results-update"]}},"type":"object","required":["data"],"title":"DataBody","description":"A data payload for an Enhanced channel. Served verbatim to the box's web\napp over the loopback websocket (never rendered as SVG)."},"cbcast__DeviceChannelBody":{"properties":{"channel_id":{"type":"string","title":"Channel Id","description":"Channel to pin the box to (detaches it from any program). Empty string sends the box to standby. To freeze a box on what it's currently showing, pin it to its current effective channel.","default":"","examples":["lobby","results-wall",""]}},"type":"object","title":"DeviceChannelBody","description":"Pin a box to a static channel (the remote's 'set content' / 'pause this\none screen' move)."},"cbcast__FrameBody":{"properties":{"image_b64":{"type":"string","title":"Image B64","description":"The panel image, base64-encoded (strict validation; 400 if not valid base64)."},"content_type":{"type":"string","title":"Content Type","description":"MIME type of the decoded image.","default":"image/png","examples":["image/png"]}},"type":"object","required":["image_b64"],"title":"FrameBody","description":"A box's actual rendered panel, uploaded as a base64 image."},"cbcast__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbcast__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbcast__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbcast__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbcast__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcast__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbcast__LockBody":{"properties":{"locked":{"type":"boolean","title":"Locked","description":"When true the BrainCast Remote treats this device as read-only — non-selectable in the DJ console, non-armable in Settings batch ops — so a dedicated controller/kiosk can't be accidentally driven, regardless of its channel. A UI gate only; the box is unaffected.","examples":[true,false]}},"type":"object","required":["locked"],"title":"LockBody","description":"Set a device's remote-side control lock."},"cbcast__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbcast__NameBody":{"properties":{"name":{"type":"string","title":"Name","description":"Operator display label (e.g. \"Dave's Office\"); empty string clears it. The machine id is unchanged.","examples":["Dave's Office","Lobby TV"]}},"type":"object","required":["name"],"title":"NameBody","description":"Set a device's operator display label."},"cbcast__PauseBody":{"properties":{"paused":{"type":"boolean","title":"Paused","description":"`true` freezes the panel on the current slide; `false` resumes rotation.","default":true}},"type":"object","title":"PauseBody","description":"Pause/resume a channel's slideshow rotation."},"cbcast__PdfRequest":{"properties":{"url":{"type":"string","title":"Url","description":"The page to render. MUST be an `http(s)://*.campaignbrain.dev` URL — arbitrary hosts are rejected (SSRF guard).","examples":["https://greenroom.campaignbrain.dev/proposals/abc123"]},"format":{"type":"string","title":"Format","description":"Chromium paper size (e.g. `Letter`, `A4`, `Legal`).","default":"Letter","examples":["Letter","A4"]},"landscape":{"type":"boolean","title":"Landscape","description":"Render in landscape orientation instead of portrait.","default":false}},"type":"object","required":["url"],"title":"PdfRequest","description":"Request to render a public ``*.campaignbrain.dev`` page to a PDF.\n\nThe URL is hard-restricted to the ``campaignbrain.dev`` zone (SSRF guard);\nany other host is rejected with 400 before chromium is touched."},"cbcast__PlaylistBody":{"properties":{"scene_ids":{"items":{"type":"string"},"type":"array","title":"Scene Ids","description":"Ordered scene ids to rotate through. Must be non-empty and all known.","examples":[["scene-a","scene-b","scene-c"]]},"interval_s":{"type":"number","title":"Interval S","description":"Seconds each slide is held before advancing to the next.","default":10.0,"examples":[10.0,30.0]}},"type":"object","required":["scene_ids"],"title":"PlaylistBody","description":"A slideshow playlist for a channel — an ordered list of scenes the\nserver rotates through on a fixed interval."},"cbcast__PrefsBody":{"properties":{"prefs":{"additionalProperties":true,"type":"object","title":"Prefs","description":"Arbitrary JSON the client owns (the remote's last room / favorites / theme). Replaces the stored blob wholesale — disposable by design.","examples":[{"favorites":["results-wall"],"last_room":"daves-office"}]}},"type":"object","title":"PrefsBody","description":"Replace a box's client-owned UI prefs blob (the remote's preferences)."},"cbcast__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbcast__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbcast__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbcast__ProgramBindBody":{"properties":{"program_id":{"type":"string","title":"Program Id","description":"Program to follow; empty string clears it (the box reverts to its static channel binding).","default":"","examples":["election-night",""]}},"type":"object","title":"ProgramBindBody","description":"Bind a box to a program, or clear the binding."},"cbcast__ProgramBody":{"properties":{"id":{"type":"string","title":"Id","description":"Program id (machine key devices bind to).","examples":["election-night"]},"name":{"type":"string","title":"Name","description":"Operator-facing program name. A later create with a new name renames it.","default":"","examples":["Election Night Rotation"]}},"type":"object","required":["id"],"title":"ProgramBody","description":"A program to create or rename (idempotent by ``id``)."},"cbcast__ProgramJumpBody":{"properties":{"index":{"type":"integer","title":"Index","description":"Board index to cut to (wraps modulo the slot count).","examples":[0,2]}},"type":"object","required":["index"],"title":"ProgramJumpBody","description":"Cut a program to a specific board immediately."},"cbcast__ProgramPauseBody":{"properties":{"paused":{"type":"boolean","title":"Paused","description":"`true` freezes the whole room on the current board; `false` resumes the sequence.","default":true}},"type":"object","title":"ProgramPauseBody","description":"Pause/resume a program's sequencer."},"cbcast__ProgramSlotBody":{"properties":{"channel_id":{"type":"string","title":"Channel Id","description":"Id of the channel ('board') to show for this slot.","examples":["results-wall"]},"dwell_s":{"type":"number","title":"Dwell S","description":"Seconds the room holds this board before advancing (≤0 → defaults to 10s).","default":10.0,"examples":[10.0,20.0]},"label":{"type":"string","title":"Label","description":"Optional label; defaults to the channel's display name or id.","default":"","examples":["Results"]}},"type":"object","required":["channel_id"],"title":"ProgramSlotBody","description":"One board in a program: a channel to show and how long to dwell on it."},"cbcast__ProgramSlotsBody":{"properties":{"slots":{"items":{"$ref":"#/components/schemas/cbcast__ProgramSlotBody"},"type":"array","title":"Slots","description":"Ordered boards. Each channel must exist and all must share one kind.","examples":[[{"channel_id":"intro","dwell_s":8},{"channel_id":"results-wall","dwell_s":20}]]}},"type":"object","required":["slots"],"title":"ProgramSlotsBody","description":"The full ordered set of boards for a program (replaces any existing\nslots). All channels must be the same kind (all `base` or all `enhanced`)."},"cbcast__RailBody":{"properties":{"rail":{"type":"number","title":"Rail","description":"Side-rail width as % of the stage for enhanced URL channels (clamped to [10, 70] when >0); `0` inherits the app default.","examples":[25.0,40.0,0]}},"type":"object","required":["rail"],"title":"RailBody","description":"Set a box's side-rail width (enhanced URL channels)."},"cbcast__RoomBeatBody":{"properties":{"beat_s":{"type":"number","title":"Beat S","description":"Synchronized cadence in seconds. `0` clears the beat (members revert to their own independent timers).","default":0.0,"examples":[10.0,30.0,0]},"align_global":{"type":"boolean","title":"Align Global","description":"Anchor the phase to the global grid (so equal-cadence rooms coincide) instead of starting a clean room-local interval from now.","default":false}},"type":"object","title":"RoomBeatBody","description":"Set (or clear) a room's synchronized beat — a shared metronome that flips\nthe room's screens together, even when each shows a different channel."},"cbcast__RoomBeatPauseBody":{"properties":{"paused":{"type":"boolean","title":"Paused","description":"`true` freezes every screen in the room on its current frame; `false` resumes.","default":true}},"type":"object","title":"RoomBeatPauseBody","description":"Pause/resume a room's synchronized beat (freeze the whole wall)."},"cbcast__RoomBody":{"properties":{"name":{"type":"string","title":"Name","description":"Operator-facing room name (e.g. 'Dave's Office'). Seeds the room id (a slug).","examples":["Dave's Office","Lobby Wall"]},"device_ids":{"items":{"type":"string"},"type":"array","title":"Device Ids","description":"Ids of the devices in this room. May be empty; edited later via PATCH.","examples":[["7F9C3C","3A687F"]]}},"type":"object","required":["name"],"title":"RoomBody","description":"Create a room — a named set of devices the BrainCast Remote drives as a\nunit. Rooms group for *control* only; they don't bind content."},"cbcast__RoomMetaBody":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"New room name.","examples":["Dave's Office"]},"device_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Device Ids","description":"Replacement device membership (the full set, not a delta). The ORDER is the physical screen order the remote mirrors — reordering this list rearranges the room's screens.","examples":[["7F9C3C","3A687F","C90CB7"]]},"layout":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Layout","description":"Physical screen arrangement: 'row' (side by side), 'column' (stacked), or 'square' (2x2). Out-of-range values are ignored.","examples":["row","column","square"]}},"type":"object","title":"RoomMetaBody","description":"Partial update of a room. Only fields present in the body are applied, so\nomitting one leaves it unchanged."},"cbcast__ScaleBody":{"properties":{"scale":{"type":"number","title":"Scale","description":"Per-panel render zoom for enhanced URL channels (clamped to [0, 6]); `0` inherits the channel/app default.","examples":[1.0,1.5,0]}},"type":"object","required":["scale"],"title":"ScaleBody","description":"Set a box's per-panel render zoom (enhanced URL channels)."},"cbcast__SceneBody":{"properties":{"markdown":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Markdown","description":"A cbweave markdown spec. Rendered + validated through cbweave on create (502 if it fails to render). Mutually exclusive with `svg`.","examples":["# Lobby Welcome\n\nDoors open at 7pm."]},"svg":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Svg","description":"A pre-rendered SVG document, stored and served verbatim (no cbweave pass). Mutually exclusive with `markdown`.","examples":["<svg xmlns=\"http://www.w3.org/2000/svg\" .../>"]},"surface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Surface","description":"cbweave render surface / aspect profile for a markdown scene (e.g. `desktop` for 16:9). Defaults to the service `default_surface`. Ignored for raw SVG scenes.","examples":["desktop"]},"name":{"type":"string","title":"Name","description":"Optional operator-facing label for the scene library.","default":"","examples":["Lobby Welcome"]}},"type":"object","title":"SceneBody","description":"A scene to create. Provide EITHER ``markdown`` (a cbweave spec, rendered\n+ validated server-side) OR ``svg`` (a pre-rendered SVG, stored as-is) —\nnot both. Scenes are content-addressed, so posting identical content\nreturns the existing scene (idempotent).","examples":[{"markdown":"# Lobby Welcome\n\nDoors open at 7pm.","name":"Lobby Welcome"},{"name":"Logo card","svg":"<svg xmlns=\"http://www.w3.org/2000/svg\" .../>"}]},"cbcast__ScreenshotBody":{"properties":{"channel_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel Id","description":"Snapshot this channel's current scene (rendered to SVG).","examples":["lobby"]},"device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id","description":"Snapshot this device's actual uploaded frame (or, failing that, its bound channel's scene).","examples":["03A8BD"]},"label":{"type":"string","title":"Label","description":"Optional operator label for the screenshot.","default":"","examples":["pre-event check"]},"image_b64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image B64","description":"Store these raw image bytes directly (base64). Wins over channel/device sources."},"content_type":{"type":"string","title":"Content Type","description":"MIME type of the uploaded `image_b64` bytes.","default":"image/png","examples":["image/png","image/svg+xml"]}},"type":"object","title":"ScreenshotBody","description":"Capture a screenshot. Provide ONE source: ``image_b64`` (store raw\nuploaded bytes), ``device_id`` (snapshot the box's actual frame, falling\nback to its channel scene), or ``channel_id`` (render the channel's current\nscene).","examples":[{"channel_id":"lobby","label":"lobby snapshot"},{"device_id":"03A8BD","label":"actual panel"}]},"cbcast__ScreenshotPatch":{"properties":{"label":{"type":"string","title":"Label","description":"New operator label for the screenshot.","default":"","examples":["final lobby board"]}},"type":"object","title":"ScreenshotPatch","description":"Update a screenshot's label."},"cbcast__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbcast__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcast__StoryBody":{"properties":{"markdown":{"type":"string","title":"Markdown","description":"Story Mode markdown (beat directives) — the authoring source.","examples":["---\nsurface: tv-1080p\n---\n::value[IA-03 CALLED]{enter=draw}\n"]},"surface":{"type":"string","title":"Surface","description":"Optional cbweave surface override (e.g. `tv-1080p`); else the frontmatter's.","default":"","examples":["tv-1080p"]},"theme":{"type":"string","title":"Theme","description":"Optional cbweave theme (e.g. `broadcast`).","default":"","examples":["broadcast"]},"name":{"type":"string","title":"Name","description":"Optional label for the resulting scene.","default":"","examples":["ia-03-called"]}},"type":"object","required":["markdown"],"title":"StoryBody","description":"Story Mode markdown to compile + push to an Enhanced ``story`` channel.\n\ncbcast compiles it via cbweave `POST /api/v1/render/story` (deterministic,\ncached by bundle hash) into a v1 scene bundle. The bundle plays on the\nEnhanced panel via the loopback websocket; the static final frame is stored\nalongside as the Base fallback for any `kind=svg` box on the same channel."},"cbcast__StreamStartBody":{"properties":{"youtube":{"type":"boolean","title":"Youtube","description":"Also push the broadcast to YouTube Live via RTMP (requires `YOUTUBE_STREAM_KEY`). HLS is always served regardless.","default":false}},"type":"object","title":"StreamStartBody","description":"Options for starting a virtual device's broadcast."},"cbcast__TelemetryBody":{"properties":{"showing":{"type":"string","title":"Showing","description":"ETag of the scene the box currently believes it is displaying.","default":"","examples":["\"a1b2c3d4\""]},"health":{"additionalProperties":true,"type":"object","title":"Health","description":"Arbitrary box health metrics (temp, uptime, agent version, …); stored as-is.","examples":[{"agent":"0.2.3","uptime_s":86400}]}},"type":"object","title":"TelemetryBody","description":"A box's periodic health/status report."},"cbcast__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbcast__VirtualDeviceBody":{"properties":{"name":{"type":"string","title":"Name","description":"Display name; becomes the hostname and seeds the device id (`vstream-<slug>`).","examples":["Main Broadcast","Lobby Stream"]}},"type":"object","required":["name"],"title":"VirtualDeviceBody","description":"Create a server-side stream worker ('virtual device')."},"cbcast__WifiListBody":{"properties":{"networks":{"items":{"$ref":"#/components/schemas/cbcast__WifiNetworkBody"},"type":"array","title":"Networks"}},"type":"object","title":"WifiListBody","description":"The full desired WiFi set for a box (replace, not a delta)."},"cbcast__WifiNetworkBody":{"properties":{"ssid":{"type":"string","title":"Ssid","description":"Network SSID — the key.","examples":["lunchable"]},"psk":{"type":"string","title":"Psk","description":"WPA-PSK passphrase; empty = open network.","default":"","examples":["11111111"]},"autoconnect":{"type":"boolean","title":"Autoconnect","description":"Auto-join when in range.","default":true},"priority":{"type":"integer","title":"Priority","description":"Autoconnect priority — higher wins.","default":0},"hidden":{"type":"boolean","title":"Hidden","description":"Hidden SSID (the box scans for it).","default":false}},"type":"object","required":["ssid"],"title":"WifiNetworkBody","description":"One WiFi network in a box's list (keyed by ssid). Applied by the agent via\nNetworkManager. Lab/throwaway creds live here as-is."},"cbcast__WorkspaceBody":{"properties":{"name":{"type":"string","title":"Name","description":"Optional workspace name.","default":"","examples":["Election Night boards"]}},"type":"object","title":"WorkspaceBody","description":"A workspace to create — a named collection of scenes."},"cbcast__WorkspaceSceneBody":{"properties":{"markdown":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Markdown","description":"A cbweave markdown spec. Rendered + validated through cbweave on create (502 if it fails to render). Mutually exclusive with `svg`.","examples":["# Lobby Welcome\n\nDoors open at 7pm."]},"svg":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Svg","description":"A pre-rendered SVG document, stored and served verbatim (no cbweave pass). Mutually exclusive with `markdown`.","examples":["<svg xmlns=\"http://www.w3.org/2000/svg\" .../>"]},"surface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Surface","description":"cbweave render surface / aspect profile for a markdown scene (e.g. `desktop` for 16:9). Defaults to the service `default_surface`. Ignored for raw SVG scenes.","examples":["desktop"]},"name":{"type":"string","title":"Name","description":"Optional operator-facing label for the scene library.","default":"","examples":["Lobby Welcome"]},"scene_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scene Id","description":"Id of an existing scene to add. If set, `markdown`/`svg` are ignored.","examples":["a1b2c3d4e5f6"]}},"type":"object","title":"WorkspaceSceneBody","description":"Add a scene to a workspace: EITHER reference an existing scene by\n``scene_id``, OR provide ``markdown`` / ``svg`` to create one (same rules\nas creating a standalone scene).","examples":[{"markdown":"# Lobby Welcome\n\nDoors open at 7pm.","name":"Lobby Welcome"},{"name":"Logo card","svg":"<svg xmlns=\"http://www.w3.org/2000/svg\" .../>"}]},"cbcast__YoutubeUrlBody":{"properties":{"url":{"type":"string","title":"Url","description":"Public YouTube watch URL shown in the Cast UI (operator-set); empty string clears it.","default":"","examples":["https://www.youtube.com/watch?v=dQw4w9WgXcQ",""]}},"type":"object","title":"YoutubeUrlBody","description":"Set (or clear) a virtual device's public YouTube watch URL."},"cbconvo__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbconvo__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbconvo__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbconvo__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbconvo__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbconvo__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbconvo__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbconvo__InboundSMS":{"properties":{"sender":{"type":"string","title":"Sender"},"message":{"type":"string","title":"Message"},"message_id":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Message Id"},"timestamp":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timestamp"}},"type":"object","required":["sender","message"],"title":"InboundSMS","description":"cbsms's forward payload (``webhook_service._forward_to_callbacks``):\nsender, message, message_id, timestamp. ``sender``/``message`` are the\nload-bearing two; the rest is optional so a device-shaped post works too."},"cbconvo__LocationBody":{"properties":{"tz":{"type":"string","title":"Tz","description":"IANA zone the person actually lives in, e.g. America/Detroit"},"note":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Note","description":"Why you know (optional)"}},"type":"object","required":["tz"],"title":"LocationBody"},"cbconvo__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbconvo__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbconvo__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbconvo__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbconvo__RejectBody":{"properties":{"note":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Note","description":"Why (optional) — handed to the model for the redraft"}},"type":"object","title":"RejectBody"},"cbconvo__StartBody":{"properties":{"contact":{"type":"string","title":"Contact","description":"E.164 phone number of the contact"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","required":["contact"],"title":"StartBody"},"cbconvo__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbconvo__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbconvo__TestMessageBody":{"properties":{"text":{"type":"string","maxLength":4000,"minLength":1,"title":"Text","description":"The voter's text"}},"type":"object","required":["text"],"title":"TestMessageBody"},"cbconvo__TestStartBody":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"The rehearsed voter's name"},"contact":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Contact","description":"A contact snapshot (any shape) echoed back on every read; no phone number is used or needed -- the bench mints a fictional one."}},"type":"object","title":"TestStartBody"},"cbconvo__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbcrm__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbcrm__CustomFieldIn":{"properties":{"name":{"type":"string","minLength":1,"title":"Name"},"field_type":{"type":"string","title":"Field Type","description":"text | number | date | bool | ...","default":"text"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["name"],"title":"CustomFieldIn","description":"Definition of a tenant custom field."},"cbcrm__FieldValueIn":{"properties":{"value":{"type":"string","title":"Value"}},"type":"object","required":["value"],"title":"FieldValueIn","description":"A person's value for a custom field."},"cbcrm__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbcrm__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbcrm__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbcrm__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbcrm__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcrm__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbcrm__InteractionIn":{"properties":{"kind":{"type":"string","title":"Kind","description":"call | email | text | door | note | ...","default":"note"},"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction","description":"inbound | outbound"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","title":"InteractionIn","description":"A logged interaction with a person."},"cbcrm__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbcrm__PersonIn":{"properties":{"pid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pid"},"svid":{"anyOf":[{"type":"integer"},{"type":"string"},{"type":"null"}],"title":"Svid"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"home_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Phone"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zip"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"congressional_district":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Congressional District"},"precinct":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct"},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"},"geoid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geoid"},"whip_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Whip Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"source_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Type"}},"additionalProperties":true,"type":"object","title":"PersonIn","description":"A person/contact to create or upsert. Extra keys are preserved in original_data."},"cbcrm__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbcrm__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbcrm__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbcrm__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbcrm__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcrm__UpsertResult":{"properties":{"person_id":{"type":"string","title":"Person Id"},"matched":{"type":"boolean","title":"Matched","description":"True if merged into an existing person."},"match_type":{"type":"string","title":"Match Type","description":"pid | svid | email | new"}},"type":"object","required":["person_id","matched","match_type"],"title":"UpsertResult","description":"Outcome of an upsert: which golden person, and how it matched."},"cbcrm__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbcron__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbcron__ExecutorType":{"type":"string","enum":["http","shell"],"title":"ExecutorType","description":"Execution mode for a job.\n\nHTTP: dispatch an HTTP request to target_url.\nSHELL: spawn a subprocess running a shell command."},"cbcron__FailingJobRow":{"properties":{"id":{"type":"string","title":"Id","description":"Job UUID."},"name":{"type":"string","title":"Name","description":"Human-readable job name."},"streak":{"type":"integer","title":"Streak","description":"Consecutive failed/timed-out executions."},"last_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Status","description":"Status of the most recent run."},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error","description":"Error/output snippet from the most recent run."},"schedule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Schedule","description":"Cron expression, if any."},"next_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Run At","description":"Next scheduled fire time (UTC)."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether the scheduler still fires this job."},"dormant":{"type":"boolean","title":"Dormant","description":"True when the failure is not an active fire — a seasonal job whose next run is far out, or a one-shot that already fired. Kept out of the active-failure count so seasonal jobs don't cry wolf."}},"type":"object","required":["id","name","streak","last_status","enabled","dormant"],"title":"FailingJobRow","description":"One currently-broken job, flattened for a dashboard grid.\n\nA deliberately flat projection of `JobStatus` (no nested `stats` block) so a\ndeclarative table renderer can bind straight to top-level fields."},"cbcron__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbcron__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbcron__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbcron__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbcron__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcron__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbcron__HttpMethod":{"type":"string","enum":["GET","POST","PATCH","DELETE"],"title":"HttpMethod","description":"HTTP methods supported for job target dispatch."},"cbcron__Job":{"properties":{"id":{"type":"string","title":"Id","description":"Unique job identifier (UUID)."},"name":{"type":"string","title":"Name","description":"Human-readable job name."},"description":{"type":"string","title":"Description","description":"Job description."},"type":{"$ref":"#/components/schemas/cbcron__JobType","description":"Trigger type: cron, once, or calendar.","default":"cron"},"schedule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Schedule","description":"5-field cron expression (cron jobs only)."},"fire_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Fire At","description":"When to fire (once jobs only)."},"dates":{"anyOf":[{"items":{"type":"string","format":"date-time"},"type":"array"},{"type":"null"}],"title":"Dates","description":"List of fire datetimes (calendar jobs only)."},"fired_dates":{"anyOf":[{"items":{"type":"string","format":"date-time"},"type":"array"},{"type":"null"}],"title":"Fired Dates","description":"Datetimes already fired (calendar jobs only)."},"timezone":{"type":"string","title":"Timezone","description":"IANA timezone for schedule evaluation."},"executor":{"$ref":"#/components/schemas/cbcron__ExecutorType","description":"Execution mode: http or shell.","default":"http"},"target_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Url","description":"URL dispatched when the job fires (http jobs)."},"target_method":{"$ref":"#/components/schemas/cbcron__HttpMethod","description":"HTTP method for dispatch."},"headers":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Headers","description":"HTTP headers included in dispatch."},"payload":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payload","description":"JSON body for POST/PATCH."},"command":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Command","description":"Shell command to execute (shell jobs)."},"working_dir":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Working Dir","description":"Working directory for the process (shell jobs)."},"sops_service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sops Service","description":"Service name for SOPS secret loading (shell jobs)."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether the scheduler evaluates this job."},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"Tags for filtering."},"retry_count":{"type":"integer","title":"Retry Count","description":"Additional attempts after first failure."},"retry_delay_seconds":{"type":"integer","title":"Retry Delay Seconds","description":"Seconds between retry attempts."},"timeout_seconds":{"type":"integer","title":"Timeout Seconds","description":"Timeout in seconds."},"skip_if_running":{"type":"boolean","title":"Skip If Running","description":"Skip this tick if a previous run is still active.","default":false},"group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group","description":"Serialization group. Jobs sharing a group run one-at-a-time; a job whose group is busy at tick time is skipped (no pileup)."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the job was created (UTC)."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the job was last modified (UTC)."},"next_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Run At","description":"Computed next fire time (UTC). Null if paused or schedule invalid."}},"type":"object","required":["id","name","description","timezone","target_method","enabled","retry_count","retry_delay_seconds","timeout_seconds","created_at","updated_at"],"title":"Job","description":"Stored job with full metadata, schedule, and computed next_run_at.","examples":[{"created_at":"2026-06-01T12:00:00Z","description":"Generates the daily report bundle.","enabled":true,"executor":"http","id":"550e8400-e29b-41d4-a716-446655440000","name":"Nightly report","next_run_at":"2026-06-22T07:00:00Z","payload":{"report_type":"daily"},"retry_count":2,"retry_delay_seconds":60,"schedule":"0 3 * * *","skip_if_running":false,"tags":["reports"],"target_method":"POST","target_url":"https://api.example.com/reports/generate","timeout_seconds":30,"timezone":"America/New_York","type":"cron","updated_at":"2026-06-01T12:00:00Z"}]},"cbcron__JobCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Human-readable job name."},"description":{"type":"string","title":"Description","description":"Optional description of what this job does.","default":""},"type":{"$ref":"#/components/schemas/cbcron__JobType","description":"Trigger type: cron (recurring), once (one-shot), calendar (multi-date).","default":"cron"},"schedule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Schedule","description":"5-field cron expression (e.g. '*/5 * * * *')."},"fire_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Fire At","description":"When to fire (UTC) for once jobs."},"dates":{"anyOf":[{"items":{"type":"string","format":"date-time"},"type":"array"},{"type":"null"}],"title":"Dates","description":"List of datetimes (UTC) for calendar jobs."},"timezone":{"type":"string","title":"Timezone","description":"IANA timezone (e.g. 'America/New_York', 'UTC')."},"executor":{"$ref":"#/components/schemas/cbcron__ExecutorType","description":"Execution mode: http (dispatch request) or shell (run command).","default":"http"},"target_url":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Target Url","description":"URL to dispatch when job fires (http jobs)."},"target_method":{"$ref":"#/components/schemas/cbcron__HttpMethod","description":"HTTP method for dispatch.","default":"POST"},"headers":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Headers","description":"HTTP headers to include."},"payload":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payload","description":"JSON body for POST/PATCH."},"command":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Command","description":"Shell command to execute (shell jobs)."},"working_dir":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Working Dir","description":"Working directory for the process (shell jobs)."},"sops_service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sops Service","description":"Service name for SOPS secret loading (shell jobs, optional)."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether the scheduler evaluates this job.","default":true},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"Tags for filtering jobs."},"retry_count":{"type":"integer","maximum":10.0,"minimum":0.0,"title":"Retry Count","description":"Additional attempts after first failure.","default":2},"retry_delay_seconds":{"type":"integer","maximum":3600.0,"minimum":0.0,"title":"Retry Delay Seconds","description":"Seconds between retries.","default":60},"timeout_seconds":{"type":"integer","maximum":28800.0,"minimum":1.0,"title":"Timeout Seconds","description":"Timeout in seconds (max 300 for http, 28800 for shell).","default":30},"skip_if_running":{"type":"boolean","title":"Skip If Running","description":"Skip this tick if a previous run is still active.","default":false},"group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group","description":"Serialization group. Jobs sharing a group run one-at-a-time; if the group is busy when this job comes due, the tick is skipped (no pileup)."}},"type":"object","required":["name","timezone"],"title":"JobCreate","description":"Request body for creating a new scheduled job.","examples":[{"name":"Nightly report","payload":{"report_type":"daily"},"schedule":"0 3 * * *","tags":["reports"],"target_method":"POST","target_url":"https://api.example.com/reports/generate","timezone":"America/New_York"},{"command":"/home/bisenbek/projects/nominate/cbradio/scripts/cron_airtable_sync.sh","executor":"shell","name":"Airtable Sync","schedule":"0 3 * * *","sops_service":"cbradio","timeout_seconds":1800,"timezone":"UTC","working_dir":"/home/bisenbek/projects/nominate/cbradio"}]},"cbcron__JobHealthSummary":{"properties":{"total":{"type":"integer","title":"Total","description":"Total registered jobs."},"enabled":{"type":"integer","title":"Enabled","description":"Jobs the scheduler evaluates."},"disabled":{"type":"integer","title":"Disabled","description":"Paused jobs."},"failing":{"type":"integer","title":"Failing","description":"Enabled jobs whose last execution failed AND are due within the active window — the number that should raise an eyebrow right now."},"dormant_failing":{"type":"integer","title":"Dormant Failing","description":"Jobs in a failed state but dormant (seasonal/far-future or a spent one-shot). Real, but not an active fire."},"rows":{"items":{"$ref":"#/components/schemas/cbcron__FailingJobRow"},"type":"array","title":"Rows","description":"Every currently-failing job, deepest streak first."}},"type":"object","required":["total","enabled","disabled","failing","dormant_failing","rows"],"title":"JobHealthSummary","description":"Fleet-wide cron health at a glance — the dashboard rollup.\n\nSingle call that answers 'how many jobs, how many broken right now, and\nwhich ones' with dashboard-ready shapes (scalar counts + a flat row list).\n`failing` counts only ACTIVE failures (enabled, due soon); dormant seasonal\njobs are surfaced in `rows` but excluded from the alarm count.","examples":[{"disabled":57,"dormant_failing":3,"enabled":236,"failing":5,"rows":[{"dormant":false,"enabled":true,"id":"550e8400-e29b-41d4-a716-446655440000","last_error":"FCC sync failed with exit code 1","last_status":"failed","name":"FCC LMS bulk import","next_run_at":"2026-07-26T08:00:00Z","schedule":"0 4 * * 0","streak":8}],"total":293}]},"cbcron__JobRunStats":{"properties":{"total_runs":{"type":"integer","title":"Total Runs","description":"Total number of runs recorded."},"successes":{"type":"integer","title":"Successes","description":"Runs with status 'success'."},"failures":{"type":"integer","title":"Failures","description":"Runs with status 'failed'."},"timeouts":{"type":"integer","title":"Timeouts","description":"Runs with status 'timeout'."},"consecutive_failures":{"type":"integer","title":"Consecutive Failures","description":"Failed/timeout EXECUTIONS in a row from newest (retries collapsed). 0 = the last execution succeeded; >0 = the job is currently broken and this is how deep the streak runs. The single best 'is it healthy right now?' signal.","default":0},"avg_duration_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Duration Ms","description":"Average duration across all completed runs."},"last_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Run At","description":"When the most recent run started."},"last_status":{"anyOf":[{"$ref":"#/components/schemas/cbcron__RunStatus"},{"type":"null"}],"description":"Status of the most recent run."},"last_exit_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Exit Code","description":"Exit code of the most recent run (shell jobs)."},"last_duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Duration Ms","description":"Duration of the most recent run."},"last_response_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Response Body","description":"Output/response from the most recent run (truncated 1KB)."},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error","description":"Error message from the most recent run, if any."}},"type":"object","required":["total_runs","successes","failures","timeouts"],"title":"JobRunStats","description":"Aggregated run statistics for a job.\n\nRolls up every recorded run for a single job into counts (total / success /\nfailure / timeout) plus a snapshot of the most-recent run (status, output,\ntiming). Embedded inside `JobStatus` and returned by the status endpoint.","examples":[{"avg_duration_ms":318.5,"failures":2,"last_duration_ms":295,"last_response_body":"{\"ok\": true}","last_run_at":"2026-06-21T07:00:00Z","last_status":"success","successes":40,"timeouts":0,"total_runs":42}]},"cbcron__JobStatus":{"properties":{"id":{"type":"string","title":"Id","description":"Unique job identifier (UUID)."},"name":{"type":"string","title":"Name","description":"Human-readable job name."},"executor":{"$ref":"#/components/schemas/cbcron__ExecutorType","description":"Execution mode: http or shell."},"schedule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Schedule","description":"5-field cron expression (cron jobs only)."},"timezone":{"type":"string","title":"Timezone","description":"IANA timezone for schedule evaluation."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether the scheduler evaluates this job."},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"Tags for filtering."},"next_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Run At","description":"Computed next fire time (UTC). Null if paused or schedule invalid."},"stats":{"$ref":"#/components/schemas/cbcron__JobRunStats","description":"Aggregated run statistics and most-recent-run summary."}},"type":"object","required":["id","name","executor","timezone","enabled","stats"],"title":"JobStatus","description":"Job with its latest run statistics — single source of truth for job health.\n\nReturned by `GET /api/status`, one entry per registered job. Combines the\njob's identifying/scheduling fields with an embedded `JobRunStats` block so a\ndashboard can render health, last outcome, and next fire time in one call.","examples":[{"enabled":true,"executor":"http","id":"550e8400-e29b-41d4-a716-446655440000","name":"Nightly report","next_run_at":"2026-06-22T07:00:00Z","schedule":"0 3 * * *","stats":{"avg_duration_ms":318.5,"failures":2,"last_duration_ms":295,"last_run_at":"2026-06-21T07:00:00Z","last_status":"success","successes":40,"timeouts":0,"total_runs":42},"tags":["reports"],"timezone":"America/New_York"}]},"cbcron__JobType":{"type":"string","enum":["cron","once","calendar"],"title":"JobType","description":"Type of job trigger.\n\nCRON: recurring schedule via 5-field cron expression.\nONCE: one-shot timer that fires at a single datetime.\nCALENDAR: fires at each datetime in a provided list."},"cbcron__JobUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbcron__JobType"},{"type":"null"}]},"schedule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Schedule"},"fire_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Fire At"},"dates":{"anyOf":[{"items":{"type":"string","format":"date-time"},"type":"array"},{"type":"null"}],"title":"Dates"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"executor":{"anyOf":[{"$ref":"#/components/schemas/cbcron__ExecutorType"},{"type":"null"}]},"target_url":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Target Url"},"target_method":{"anyOf":[{"$ref":"#/components/schemas/cbcron__HttpMethod"},{"type":"null"}]},"headers":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Headers"},"payload":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payload"},"command":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Command"},"working_dir":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Working Dir"},"sops_service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sops Service"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"retry_count":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":0.0},{"type":"null"}],"title":"Retry Count"},"retry_delay_seconds":{"anyOf":[{"type":"integer","maximum":3600.0,"minimum":0.0},{"type":"null"}],"title":"Retry Delay Seconds"},"timeout_seconds":{"anyOf":[{"type":"integer","maximum":28800.0,"minimum":1.0},{"type":"null"}],"title":"Timeout Seconds"},"skip_if_running":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Skip If Running"},"group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group"}},"type":"object","title":"JobUpdate","description":"Request body for partially updating an existing job."},"cbcron__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbcron__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbcron__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbcron__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbcron__QuiesceRequest":{"properties":{"reason":{"type":"string","title":"Reason","description":"Why cbcron is paused (shown in logs/status)."},"ttl_seconds":{"type":"integer","maximum":43200.0,"minimum":1.0,"title":"Ttl Seconds","description":"Auto-resume after this many seconds (crash-safety).","default":1800}},"type":"object","required":["reason"],"title":"QuiesceRequest"},"cbcron__Run":{"properties":{"id":{"type":"string","title":"Id","description":"Unique run identifier (UUID)."},"job_id":{"type":"string","title":"Job Id","description":"Parent job ID."},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"When execution began (UTC)."},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At","description":"When execution completed (UTC). Null if still running."},"status":{"$ref":"#/components/schemas/cbcron__RunStatus","description":"Outcome of this attempt."},"http_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Http Status","description":"HTTP response status code from target. Null on network error or timeout."},"response_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Body","description":"Truncated response body (max 1KB)."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message on failure or timeout."},"attempt":{"type":"integer","title":"Attempt","description":"Attempt number (1 = first try, 2+ = retry).","default":1},"exit_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Exit Code","description":"Process exit code (shell jobs only). Null for HTTP runs."},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms","description":"Wall-clock milliseconds from request to response/error."}},"type":"object","required":["id","job_id","started_at","status"],"title":"Run","description":"Record of a single job execution attempt.","examples":[{"attempt":1,"duration_ms":295,"finished_at":"2026-06-21T07:00:00.295Z","http_status":200,"id":"7b9c1f2e-3a4d-4c5b-9e8f-1a2b3c4d5e6f","job_id":"550e8400-e29b-41d4-a716-446655440000","response_body":"{\"ok\": true}","started_at":"2026-06-21T07:00:00Z","status":"success"}]},"cbcron__RunStatus":{"type":"string","enum":["pending","success","failed","timeout","skipped"],"title":"RunStatus","description":"Outcome status for a job execution run.\n\nLifecycle: PENDING -> SUCCESS | FAILED | TIMEOUT.\nSKIPPED is set when overlap protection prevents execution."},"cbcron__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbcron__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbcron__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbdatabase__BackupListItem":{"properties":{"file":{"type":"string","title":"File","description":"Path to the backup file."},"size_bytes":{"type":"integer","title":"Size Bytes","description":"Size of the backup on disk."},"created_at":{"type":"string","title":"Created At","description":"ISO timestamp from the file mtime."}},"type":"object","required":["file","size_bytes","created_at"],"title":"BackupListItem","description":"One backup file on disk."},"cbdatabase__BackupResponse":{"properties":{"file":{"type":"string","title":"File","description":"Path to the newly created backup file."}},"type":"object","required":["file"],"title":"BackupResponse","description":"Result of triggering a backup."},"cbdatabase__ColumnSpec":{"properties":{"name":{"type":"string","title":"Name","description":"Column name. Bare SQL identifier, validated and quoted in DDL.","examples":["voter_id","created_at","propensity_score"]},"type":{"$ref":"#/components/schemas/cbdatabase__ColumnType","description":"Column data type. See ColumnType for the supported set."},"nullable":{"type":"boolean","title":"Nullable","description":"Whether NULL is allowed. False adds NOT NULL.","default":true},"primary_key":{"type":"boolean","title":"Primary Key","description":"Mark this single column as the primary key.","default":false},"unique":{"type":"boolean","title":"Unique","description":"Add a UNIQUE constraint on this column.","default":false},"default":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default","description":"Raw SQL default expression, inserted verbatim after DEFAULT. Examples: 'now()', '0', \"'pending'\". You own the quoting here.","examples":["now()","0","'unverified'"]}},"type":"object","required":["name","type"],"title":"ColumnSpec","description":"A single column in a table.\n\nUse ``primary_key`` for single-column keys. For composite keys, leave each\ncolumn's ``primary_key`` false and set the table-level ``primary_key`` list."},"cbdatabase__ColumnType":{"type":"string","enum":["INTEGER","BIGINT","DOUBLE","DECIMAL","VARCHAR","BOOLEAN","DATE","TIMESTAMP","TIME","BLOB","UUID","JSON"],"title":"ColumnType","description":"Supported declarative column types.\n\nThe enum value is the token a caller sends on the wire. It maps to a DuckDB\nDDL type via :data:`_DDL_TYPE`. For anything outside this set (nested structs,\nenums, custom-precision decimals) use a table's ``raw_sql`` field or the\nschema-level ``raw_sql`` escape hatch."},"cbdatabase__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbdatabase__DatabaseInfoResponse":{"properties":{"name":{"type":"string","title":"Name","description":"Logical database name and registry key."},"path":{"type":"string","title":"Path","description":"Filesystem path to the live file."},"status":{"type":"string","title":"Status","description":"'online' if a worker is running, 'offline' if registered but not loaded.","examples":["online","offline"]},"lock_held":{"type":"boolean","title":"Lock Held","description":"True if the file lock is held right now. False means the file is currently attachable READ_ONLY by federation readers without contention. Expect brief true windows while writes drain."},"created_at":{"type":"string","title":"Created At","description":"ISO timestamp the database was minted."},"last_backup_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Backup At","description":"ISO timestamp of the most recent backup, or null if never."},"table_count":{"type":"integer","title":"Table Count","description":"Number of tables defined at mint time."},"queue_depth":{"type":"integer","title":"Queue Depth","description":"Pending jobs in this database's worker queue right now. A sustained high value means I/O is the bottleneck for this database."},"size_bytes":{"type":"integer","title":"Size Bytes","description":"On-disk size of the live file."},"owner_tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Tenant","description":"Owning tenant id, or null if unowned."},"visibility":{"type":"string","title":"Visibility","description":"Federation visibility class: 'unclaimed', 'tenant', or 'shared'.","default":"unclaimed"},"offline_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Offline Reason","description":"Why this catalog has no running worker, when status is 'offline'; null while online. Usually another process holds the file's read-write lock. The catalog is registered and will come back on its own — the next request retries it — so treat this as 'temporarily unavailable', not 'gone'."}},"type":"object","required":["name","path","status","lock_held","created_at","table_count","queue_depth","size_bytes"],"title":"DatabaseInfoResponse","description":"Registry record describing one managed database."},"cbdatabase__DatabaseSchema":{"properties":{"name":{"type":"string","title":"Name","description":"Logical database name. Becomes the filename and the registry key. Bare SQL identifier; safe for use as a path segment.","examples":["ia03_field","donor_crm","turnout_model_2026"]},"tables":{"items":{"$ref":"#/components/schemas/cbdatabase__TableSpec"},"type":"array","title":"Tables","description":"Tables to create. May be empty if using only raw_sql."},"raw_sql":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Sql","description":"Optional trailing SQL run after all table DDL. Use for views, macros, or seed data."}},"type":"object","required":["name"],"title":"DatabaseSchema","description":"A full database definition: a name plus one or more tables.\n\nPass this to the manager's mint endpoint to create a new database with all\nstructure in place in a single queued script.","example":{"name":"ia03_field","tables":[{"columns":[{"name":"voter_id","primary_key":true,"type":"VARCHAR"},{"name":"last_name","nullable":false,"type":"VARCHAR"},{"name":"district_id","type":"VARCHAR"},{"name":"propensity","type":"INTEGER"},{"default":"now()","name":"created_at","type":"TIMESTAMP"}],"indexes":[{"columns":["district_id"],"name":"idx_voter_district"}],"name":"voters"}]}},"cbdatabase__DropResponse":{"properties":{"name":{"type":"string","title":"Name","description":"Name of the dropped database."},"file_deleted":{"type":"boolean","title":"File Deleted","description":"True if the file was deleted, false if it was archived to backups."}},"type":"object","required":["name","file_deleted"],"title":"DropResponse","description":"Confirmation that a database was dropped."},"cbdatabase__ExecuteRequest":{"properties":{"sql":{"type":"string","minLength":1,"title":"Sql","description":"An INSERT, UPDATE, DELETE, or DDL statement. Use ? placeholders for user-supplied values and pass them in params.","examples":["INSERT INTO voters (voter_id, last_name) VALUES (?, ?)"]},"params":{"items":{},"type":"array","title":"Params","description":"Positional bind values for the ? placeholders, in order.","examples":[["v0001","Okafor"]]}},"type":"object","required":["sql"],"title":"ExecuteRequest","description":"A write or DDL statement against a managed database.","example":{"params":[82,"v0001"],"sql":"UPDATE voters SET propensity = ? WHERE voter_id = ?"}},"cbdatabase__ExecuteResponse":{"properties":{"rows_changed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows Changed","description":"Best-effort affected row count. DuckDB does not report this for every statement type, so null means the statement ran but the count is unavailable, not that it failed."}},"type":"object","title":"ExecuteResponse","description":"Outcome of a write or DDL statement."},"cbdatabase__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbdatabase__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbdatabase__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbdatabase__IndexSpec":{"properties":{"name":{"type":"string","title":"Name","description":"Index name. Must be unique within the database.","examples":["idx_voter_district","idx_contact_phone"]},"columns":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Columns","description":"Columns covered by the index, in order.","examples":[["district_id"],["last_name","first_name"]]},"unique":{"type":"boolean","title":"Unique","description":"Create a UNIQUE index.","default":false}},"type":"object","required":["name","columns"],"title":"IndexSpec","description":"A secondary index on one or more columns."},"cbdatabase__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbdatabase__MintRequest":{"properties":{"schema":{"$ref":"#/components/schemas/cbdatabase__DatabaseSchema","description":"The declarative database schema to materialize."},"owner_tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Tenant","description":"Owning tenant id. Required for tenant-scoped catalogs.","examples":["tenant_ia_dem",null]},"visibility":{"type":"string","title":"Visibility","description":"Federation visibility class: 'unclaimed', 'tenant', 'shared'.","default":"unclaimed","examples":["tenant","shared","unclaimed"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Human-readable summary stored with the catalog.","examples":["IA-03 field operations voter file"]},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Free-form labels for discovery.","examples":[["field","ia-03"]]}},"type":"object","required":["schema"],"title":"MintRequest","description":"Request to mint a new database and announce it as a federation catalog.\n\nThe schema defines structure. The remaining fields are the catalog metadata\nthe federation visibility gate needs; a directory scan cannot infer them, so\nsupply them here for anything that must respect a tenant boundary."},"cbdatabase__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbdatabase__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbdatabase__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbdatabase__ProbeResponse":{"properties":{"name":{"type":"string","title":"Name","description":"Store name ('_control' is the registry DB)."},"ok":{"type":"boolean","title":"Ok","description":"True if the store answered a real query. Unlike 'online' (worker thread alive) this executes SQL, so a poisoned-but-alive store — the failure mode that hid a 3-day outage — reads false here."},"poisoned":{"type":"boolean","title":"Poisoned","description":"True if the store is in a persistent poisoned state: a DuckDB InternalException survived a connection reset, so on-disk corruption is likely and a restore/reload is required."},"poison_events":{"type":"integer","title":"Poison Events","description":"Lifetime count of poison faults this worker has caught (recovered or not). A rising value across probes flags a store that keeps re-poisoning even though individual resets clear it."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Probe error detail when ok is false, else null."}},"type":"object","required":["name","ok","poisoned","poison_events"],"title":"ProbeResponse","description":"Response to an on-demand probe of a single store.\n\nA probe self-heals transient poison (reset + retry) before answering, so\n``ok: true, poison_events > 0`` means the store hit poison and recovered.\n``ok: false, poisoned: true`` means the fault survived the reset — restore\nfrom a backup."},"cbdatabase__QueryRequest":{"properties":{"sql":{"type":"string","minLength":1,"title":"Sql","description":"A result-producing SQL statement, usually a SELECT. Use ? placeholders for any user-supplied value and pass them in params. Never interpolate values into this string.","examples":["SELECT count(*) AS n FROM voters WHERE district_id = ?"]},"params":{"items":{},"type":"array","title":"Params","description":"Positional bind values for the ? placeholders, in order.","examples":[["ia-03"]]}},"type":"object","required":["sql"],"title":"QueryRequest","description":"A read query against a managed database.","example":{"params":[70],"sql":"SELECT last_name, propensity FROM voters WHERE propensity >= ? LIMIT 10"}},"cbdatabase__QueryResponse":{"properties":{"columns":{"items":{"type":"string"},"type":"array","title":"Columns","description":"Column names in the order DuckDB returned them."},"rows":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Rows","description":"One dict per row, keyed by column name. All values are JSON-safe: timestamps are ISO strings, decimals are strings, blobs are base64."},"row_count":{"type":"integer","title":"Row Count","description":"Number of rows returned."},"column_types":{"items":{"type":"string"},"type":"array","title":"Column Types","description":"DuckDB type name per column, aligned with `columns` (e.g. 'DECIMAL(5,2)', 'TIMESTAMP', 'VARCHAR'). Lets clients reconstruct native types that JSON flattens to strings. Empty if unavailable."}},"type":"object","required":["columns","rows","row_count"],"title":"QueryResponse","description":"Rows returned by a read query."},"cbdatabase__RestoreRequest":{"properties":{"backup_file":{"type":"string","title":"Backup File","description":"Path to a backup produced by the backup endpoint. Get valid paths from GET /api/v1/databases/{name}/backups."}},"type":"object","required":["backup_file"],"title":"RestoreRequest","description":"Request to restore a database from a backup file."},"cbdatabase__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbdatabase__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbdatabase__StoreProbe":{"properties":{"name":{"type":"string","title":"Name","description":"Store name ('_control' is the registry DB)."},"ok":{"type":"boolean","title":"Ok","description":"True if the store answered a real query. Unlike 'online' (worker thread alive) this executes SQL, so a poisoned-but-alive store — the failure mode that hid a 3-day outage — reads false here."},"poisoned":{"type":"boolean","title":"Poisoned","description":"True if the store is in a persistent poisoned state: a DuckDB InternalException survived a connection reset, so on-disk corruption is likely and a restore/reload is required."},"poison_events":{"type":"integer","title":"Poison Events","description":"Lifetime count of poison faults this worker has caught (recovered or not). A rising value across probes flags a store that keeps re-poisoning even though individual resets clear it."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Probe error detail when ok is false, else null."}},"type":"object","required":["name","ok","poisoned","poison_events"],"title":"StoreProbe","description":"Result of a real liveness probe (``SELECT 1``) against one store."},"cbdatabase__TableSpec":{"properties":{"name":{"type":"string","title":"Name","description":"Table name. Bare SQL identifier, validated and quoted in DDL.","examples":["voters","contact_log","turf_assignments"]},"columns":{"items":{"$ref":"#/components/schemas/cbdatabase__ColumnSpec"},"type":"array","minItems":1,"title":"Columns","description":"Ordered list of columns."},"primary_key":{"items":{"type":"string"},"type":"array","title":"Primary Key","description":"Composite primary key columns. Leave empty for single-column keys.","examples":[[],["tenant_id","voter_id"]]},"indexes":{"items":{"$ref":"#/components/schemas/cbdatabase__IndexSpec"},"type":"array","title":"Indexes","description":"Secondary indexes to create after the table."},"raw_sql":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Sql","description":"Escape hatch. If set, this exact CREATE statement is used instead of the generated one. Use for types or constraints the spec does not model."}},"type":"object","required":["name","columns"],"title":"TableSpec","description":"A table definition.\n\nSet either per-column ``primary_key`` flags or the table-level\n``primary_key`` list for composite keys, not both."},"cbdatabase__TransactionRequest":{"properties":{"statements":{"items":{"$ref":"#/components/schemas/cbdatabase__TransactionStatement"},"type":"array","minItems":1,"title":"Statements","description":"Ordered statements to run atomically. Must be non-empty."}},"type":"object","required":["statements"],"title":"TransactionRequest","description":"Several statements to run as one all-or-nothing transaction.\n\nThe statements run in order inside a single transaction on one connection.\nThey all commit together, or — if any one fails — the whole batch is rolled\nback and nothing is applied. Use this for multi-table updates that must be\natomic. Keep all the tables in the same database: atomicity is a per-database\nproperty.","example":{"statements":[{"params":["v0001","Okafor"],"sql":"INSERT INTO voters (voter_id, last_name) VALUES (?, ?)"},{"params":["t-12"],"sql":"UPDATE turf SET assigned = assigned + 1 WHERE turf_id = ?"}]}},"cbdatabase__TransactionResponse":{"properties":{"committed":{"type":"boolean","title":"Committed","description":"True when every statement committed atomically.","default":true},"results":{"items":{"$ref":"#/components/schemas/cbdatabase__ExecuteResponse"},"type":"array","title":"Results","description":"Per-statement outcome, in order. Present only on a commit."}},"type":"object","required":["results"],"title":"TransactionResponse","description":"Outcome of a committed transaction."},"cbdatabase__TransactionStatement":{"properties":{"sql":{"type":"string","minLength":1,"title":"Sql","description":"A single SQL statement. Use ? placeholders for user-supplied values and pass them in params; never interpolate.","examples":["INSERT INTO voters (voter_id, last_name) VALUES (?, ?)"]},"params":{"items":{},"type":"array","title":"Params","description":"Positional bind values for this statement's ? placeholders.","examples":[["v0001","Okafor"]]}},"type":"object","required":["sql"],"title":"TransactionStatement","description":"One statement in a transactional batch."},"cbdatabase__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbdatabase__VerifyResponse":{"properties":{"ok":{"type":"boolean","title":"Ok","description":"True when nothing reported corruption. This is a strong NEGATIVE signal: false means do not trust this file. True is not a proof of health — it means everything reachable through SQL read cleanly."},"tables_checked":{"type":"integer","title":"Tables Checked","description":"User tables whose every column was read."},"indexes_checked":{"type":"integer","title":"Indexes Checked","description":"Index structures probed with an equality lookup, including the ART indexes behind PRIMARY KEY / UNIQUE constraints (which duckdb_indexes() does not list). Index corruption is invisible to a full scan and is the fleet's most common corruption failure, so this count being 0 on an indexed catalog means the check was shallower than it looks."},"keys_probed":{"type":"integer","title":"Keys Probed","description":"Total index lookups performed. Should be far larger than `indexes_checked` — one lookup per index walks a single path through the ART and would clear an index whose other branches are corrupt, so this is the depth of the check rather than a statistic.","default":0},"findings":{"items":{"type":"string"},"type":"array","title":"Findings","description":"One line per failure, naming the table or index. Empty when ok."},"unprobed":{"items":{"type":"string"},"type":"array","title":"Unprobed","description":"Index expressions that could not be turned into an equality lookup (expression indexes such as `lower(name)`). These are NOT failures and do not affect `ok` — they are gaps in coverage, reported because a silent gap reads as 'verified' when nothing was checked."}},"type":"object","required":["ok","tables_checked","indexes_checked"],"title":"VerifyResponse","description":"Outcome of a deep integrity check on a managed catalog."},"cbdatabase__cbdatabase__models__HealthResponse":{"properties":{"status":{"type":"string","title":"Status","description":"'ok' when the control plane is responsive. 'degraded' (deep probe only) when one or more stores failed their liveness probe or are poisoned.","examples":["ok","degraded"]},"database_count":{"type":"integer","title":"Database Count","description":"Number of registered databases."},"online_count":{"type":"integer","title":"Online Count","description":"Number currently online with a worker."},"poisoned":{"items":{"type":"string"},"type":"array","title":"Poisoned","description":"Names of stores in a persistent poisoned state (deep probe only). Empty on a healthy control plane. A non-empty list should page."},"probes":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbdatabase__StoreProbe"},"type":"array"},{"type":"null"}],"title":"Probes","description":"Per-store probe detail. Present only when deep=1 is passed."}},"type":"object","required":["status","database_count","online_count"],"title":"HealthResponse","description":"Service liveness summary.\n\nThe shallow form (``/healthz``) reports registry counts only. The deep form\n(``/healthz?deep=1``) additionally runs a real ``SELECT 1`` against every\nonline store plus the control DB, so a poisoned-but-alive store degrades the\nreported status instead of masquerading as healthy."},"cbdatabase__sysforge__core__health__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbdatabase__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbdatabase__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbdistricts__CacheWarmJobResponse":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Unique job identifier"},"status":{"$ref":"#/components/schemas/cbdistricts__JobStatus","description":"Current job status"},"message":{"type":"string","title":"Message","description":"Human-readable status message"},"state_fips":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State Fips","description":"State filter if applied"},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"Job start time (UTC)"}},"type":"object","required":["job_id","status","message","started_at"],"title":"CacheWarmJobResponse","description":"Response for cache warm job start.","example":{"job_id":"wiki-warm-1706234567","message":"Wikipedia cache warming started for 441 districts","started_at":"2024-01-25T10:30:00Z","status":"running"}},"cbdistricts__CacheWarmProgress":{"properties":{"total":{"type":"integer","title":"Total","description":"Total districts to warm"},"completed":{"type":"integer","title":"Completed","description":"Districts completed successfully"},"failed":{"type":"integer","title":"Failed","description":"Districts that failed to fetch","default":0},"skipped":{"type":"integer","title":"Skipped","description":"Districts skipped (already cached)","default":0},"current_geoid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Geoid","description":"Currently processing GEOID"}},"type":"object","required":["total","completed"],"title":"CacheWarmProgress","description":"Progress tracking for cache warming job."},"cbdistricts__CacheWarmStatusResponse":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Unique job identifier"},"status":{"$ref":"#/components/schemas/cbdistricts__JobStatus","description":"Current job status"},"progress":{"anyOf":[{"$ref":"#/components/schemas/cbdistricts__CacheWarmProgress"},{"type":"null"}],"description":"Progress details"},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"Job start time"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"Job completion time"},"duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration Seconds","description":"Job duration if completed"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if failed"}},"type":"object","required":["job_id","status","started_at"],"title":"CacheWarmStatusResponse","description":"Response for cache warm job status query.","example":{"job_id":"wiki-warm-1706234567","progress":{"completed":156,"current_geoid":"1903","failed":2,"skipped":43,"total":441},"started_at":"2024-01-25T10:30:00Z","status":"running"}},"cbdistricts__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbdistricts__ComputeIntersectionsResponse":{"properties":{"layer_a":{"type":"string","title":"Layer A","description":"First layer computed"},"layer_b":{"type":"string","title":"Layer B","description":"Second layer computed"},"intersections_computed":{"type":"integer","minimum":0.0,"title":"Intersections Computed","description":"Number of intersections computed"},"duration_ms":{"type":"number","minimum":0.0,"title":"Duration Ms","description":"Computation duration in milliseconds"},"state_filter":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State Filter","description":"State filter applied (if any)"}},"type":"object","required":["layer_a","layer_b","intersections_computed","duration_ms"],"title":"ComputeIntersectionsResponse","description":"Response from intersection computation request.\n\nReports results of batch intersection computation.","example":{"duration_ms":450.5,"intersections_computed":825,"layer_a":"radio-coverage","layer_b":"michigan-state-house","state_filter":"MI"}},"cbdistricts__CoverageItem":{"properties":{"layer":{"type":"string","title":"Layer","description":"Layer containing the covering feature"},"feature_id":{"type":"string","title":"Feature Id","description":"ID of the covering feature"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Name of the covering feature"},"pct_coverage":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Pct Coverage","description":"Percentage of the target feature covered (0-100). Alias for pct_of_b.","x-unit":"percent"},"pct_of_a":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Pct Of A","description":"Percentage of the covering feature that lies inside the target (0-100)","x-unit":"percent"},"pct_of_b":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Pct Of B","description":"Percentage of the target feature covered by the covering feature (0-100). Same as pct_coverage.","x-unit":"percent"},"overlap_area_sq_deg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overlap Area Sq Deg","description":"Overlap area in square degrees"}},"type":"object","required":["layer","feature_id","pct_coverage"],"title":"CoverageItem","description":"Single coverage entry for a feature.\n\nRepresents how much one feature overlaps with another. Exposes both\ndirections of the percentage:\n\n- `pct_coverage` / `pct_of_b` — % of the *target* feature covered by\n  the other. Driven most use cases historically (e.g. \"how much of MI-3\n  is covered by WOOD-AM's signal?\").\n- `pct_of_a` — % of the *covering* feature that lies inside the target.\n  Needed when the target feature is conceptually larger than the\n  covering one (e.g. \"how much of WOOD-AM's signal is inside MI?\").","example":{"feature_id":"12345","layer":"radio-coverage","name":"WDET-FM","overlap_area_sq_deg":0.0025,"pct_coverage":85.2,"pct_of_a":97.3,"pct_of_b":85.2}},"cbdistricts__DemographicVariable":{"properties":{"code":{"type":"string","pattern":"^DP\\d{2}_\\d{4}[EP]?E?$","title":"Code","description":"ACS variable code (e.g., DP05_0001E for total population)","examples":["DP05_0001E","DP03_0062E","DP02_0068E"]},"name":{"type":"string","title":"Name","description":"Human-readable variable name","examples":["Total Population","Median Household Income","Bachelor's Degree or Higher"]},"category":{"type":"string","enum":["social","economic","housing","demographic"],"title":"Category","description":"Data profile category","examples":["social","economic","housing","demographic"],"x-mapping":{"demographic":"DP05 - ACS Demographic and Housing Estimates","economic":"DP03 - Economic Characteristics","housing":"DP04 - Housing Characteristics","social":"DP02 - Social Characteristics"}},"unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit","description":"Unit of measurement (null for counts)","examples":["dollars","percent","years",null]}},"type":"object","required":["code","name","category"],"title":"DemographicVariable","description":"Metadata about an ACS demographic variable.\n\nDescribes a single Census variable including its code, human-readable name,\ncategory, and unit of measurement.\n\n**ACS Variable Naming Convention:**\n- Format: `DP{profile}_{sequence}E` (Estimate) or `DP{profile}_{sequence}PE` (Percent)\n- Profiles: DP02 (Social), DP03 (Economic), DP04 (Housing), DP05 (Demographic)","example":{"category":"demographic","code":"DP05_0001E","name":"Total Population"}},"cbdistricts__DemographicsAggregation":{"properties":{"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Number of districts included in aggregation","examples":[52,441,10]},"population_total":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Population Total","description":"Total population across all districts","examples":[39538223]},"population_avg":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Population Avg","description":"Average population per district","examples":[761620]},"population_min":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Population Min","description":"Minimum district population","examples":[550000]},"population_max":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Population Max","description":"Maximum district population","examples":[980000]},"income_avg":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Income Avg","description":"Average median household income","examples":[75000]},"unemployment_avg":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Unemployment Avg","description":"Average unemployment rate","examples":[4.5]}},"type":"object","required":["count"],"title":"DemographicsAggregation","description":"Aggregated demographic statistics across multiple districts.\n\nUsed for state-level summaries or filtered result aggregations.\nProvides min, max, and average values for key metrics.","example":{"count":52,"income_avg":75000,"population_avg":761620,"population_max":980000,"population_min":550000,"population_total":39538223,"unemployment_avg":4.5}},"cbdistricts__DemographicsDetail":{"properties":{"geoid":{"type":"string","title":"Geoid","description":"Census GEOID for the congressional district","examples":["0601","3614"]},"year":{"type":"integer","maximum":2030.0,"minimum":2010.0,"title":"Year","description":"ACS data year","default":2023,"examples":[2023,2022]},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"District name"},"dp02_0001e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp02 0001E","description":"Total households","x-category":"Social","x-profile":"DP02"},"dp02_0067e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp02 0067E","description":"Estimate (count) of people 25+ with a high school diploma or higher. ACS '_E' = estimate; divide by dp02_0059e (pop 25+) for a percent. See cbdistricts#17.","x-profile":"DP02","x-unit":"people"},"dp02_0068e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp02 0068E","description":"Estimate (count) of people 25+ with a bachelor's degree or higher. ACS '_E' = estimate; divide by dp02_0059e (pop 25+) for a percent. See cbdistricts#17.","x-profile":"DP02","x-unit":"people"},"dp03_0062e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp03 0062E","description":"Median household income in the past 12 months (dollars)","x-profile":"DP03","x-unit":"dollars"},"dp03_0009pe":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp03 0009Pe","description":"Unemployment rate (percent of civilian labor force 16+)","x-profile":"DP03","x-unit":"percent"},"dp03_0088e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp03 0088E","description":"Per capita income in the past 12 months (dollars)","x-profile":"DP03","x-unit":"dollars"},"dp04_0001e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp04 0001E","description":"Total housing units","x-profile":"DP04"},"dp04_0089e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp04 0089E","description":"Median value of owner-occupied housing units (dollars)","x-profile":"DP04","x-unit":"dollars"},"dp05_0001e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp05 0001E","description":"Total population","x-profile":"DP05"},"dp05_0018e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp05 0018E","description":"Median age (years)","x-profile":"DP05","x-unit":"years"},"dp05_0002e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp05 0002E","description":"Male population","x-profile":"DP05"},"dp05_0003e":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dp05 0003E","description":"Female population","x-profile":"DP05"}},"type":"object","required":["geoid"],"title":"DemographicsDetail","description":"Comprehensive demographics with all available ACS variables.\n\nIncludes data from all four ACS Data Profiles (DP02-DP05) for complete\ndemographic analysis.\n\n**Data Profile Reference:**\n| Profile | Description | Variables |\n|---------|-------------|-----------|\n| DP02 | Social Characteristics | Education, language, ancestry |\n| DP03 | Economic Characteristics | Income, employment, occupation |\n| DP04 | Housing Characteristics | Units, tenure, value, costs |\n| DP05 | Demographic Characteristics | Age, sex, race, population |","example":{"dp02_0001e":285000,"dp02_0067e":88.5,"dp02_0068e":35.2,"dp03_0009pe":4.2,"dp03_0062e":75000,"dp03_0088e":42000,"dp04_0001e":310000,"dp04_0089e":350000,"dp05_0001e":761620,"dp05_0002e":375000,"dp05_0003e":386620,"dp05_0018e":42.1,"geoid":"0601","name":"California's 1st Congressional District","year":2023}},"cbdistricts__DemographicsSummary":{"properties":{"geoid":{"type":"string","title":"Geoid","description":"Census GEOID for the congressional district","examples":["0601","3614"]},"year":{"type":"integer","maximum":2030.0,"minimum":2010.0,"title":"Year","description":"ACS data year","default":2023,"examples":[2023,2022]},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"District name (e.g., 'California's 1st Congressional District')"},"dp05_0001e":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Dp05 0001E","description":"Total population (ACS estimate)","examples":[761620]},"dp05_0018e":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Dp05 0018E","description":"Median age in years","examples":[42.1]},"dp03_0062e":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Dp03 0062E","description":"Median household income in dollars","examples":[75000]},"dp02_0067e":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Dp02 0067E","description":"Percent of population 25+ with high school diploma or higher","examples":[88.5]},"dp02_0068e":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Dp02 0068E","description":"Percent of population 25+ with bachelor's degree or higher","examples":[35.2]},"dp03_0009pe":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Dp03 0009Pe","description":"Unemployment rate as percent of civilian labor force","examples":[4.2]},"dp04_0089e":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Dp04 0089E","description":"Median value of owner-occupied housing units in dollars","examples":[350000]}},"type":"object","required":["geoid"],"title":"DemographicsSummary","description":"Key demographics summary for quick analysis.\n\nContains the most commonly used demographic indicators suitable for\nlist views, comparisons, and basic analysis.","example":{"bachelors_plus":35.2,"geoid":"0601","high_school_plus":88.5,"median_age":42.1,"median_home_value":350000,"median_income":75000,"name":"California's 1st Congressional District","population":761620,"unemployment_rate":4.2,"year":2023}},"cbdistricts__DistrictDetail":{"properties":{"geoid":{"type":"string","maxLength":5,"minLength":4,"pattern":"^\\d{4,5}$","title":"Census GEOID","description":"Unique 4-5 character identifier combining state FIPS and district number","examples":["0601","3614","5000"],"x-format":"state_fips (2 digits) + district_num (2 digits)"},"namelsad":{"type":"string","title":"Namelsad","description":"Legal/Statistical Area Description - full district name","examples":["Congressional District 1","Congressional District 14","Congressional District (at Large)"]},"state_fips":{"type":"string","maxLength":2,"minLength":2,"pattern":"^\\d{2}$","title":"State FIPS Code","description":"2-digit FIPS code identifying the state","examples":["06","36","48"],"x-reference":"https://www.census.gov/library/reference/code-lists/ansi/ansi-codes-for-states.html"},"district_num":{"type":"string","maxLength":2,"minLength":2,"pattern":"^\\d{2}$","title":"District Num","description":"2-digit district number (00 for at-large districts)","examples":["01","14","00"]},"aland":{"type":"integer","minimum":0.0,"title":"Aland","description":"Land area in square meters","examples":[15285123456,8765432100],"x-conversion":"divide by 2,589,988 for square miles","x-unit":"square meters"},"awater":{"type":"integer","minimum":0.0,"title":"Awater","description":"Water area in square meters","examples":[123456789,987654],"x-conversion":"divide by 2,589,988 for square miles","x-unit":"square meters"},"population":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Population","description":"Total population (ACS 1-year estimate)","examples":[761620]},"median_age":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Median Age","description":"Median age of residents in years","examples":[42.1]},"median_income":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Median Income","description":"Median household income in USD","examples":[75000]},"bachelors_plus":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Bachelors Plus","description":"Percentage of population 25+ with bachelor's degree or higher","examples":[35.2,28.5,42.8],"x-unit":"percent"},"unemployment_rate":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Unemployment Rate","description":"Unemployment rate as percentage of labor force","examples":[4.2,5.8,3.1],"x-unit":"percent"}},"type":"object","required":["geoid","namelsad","state_fips","district_num","aland","awater"],"title":"DistrictDetail","description":"Complete district details including land/water area and full demographics.\n\nReturns all available data for a single district, suitable for detail views\nand comprehensive analysis.","example":{"aland":15285123456,"awater":123456789,"bachelors_plus":35.2,"district_num":"01","geoid":"0601","median_age":42.1,"median_income":75000,"namelsad":"Congressional District 1","population":761620,"state_fips":"06","unemployment_rate":4.2}},"cbdistricts__DistrictRadioCoverage":{"properties":{"geoid":{"type":"string","title":"Geoid","description":"Congressional district GEOID","examples":["2614","0601"]},"district_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"District Name","description":"District display name"},"stations":{"items":{"$ref":"#/components/schemas/cbdistricts__RadioCoverage"},"type":"array","title":"Stations","description":"List of stations covering this district"},"total_stations":{"type":"integer","minimum":0.0,"title":"Total Stations","description":"Total number of stations with coverage"},"by_service_type":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Service Type","description":"Station count by service type {am: N, fm: N}"}},"type":"object","required":["geoid","stations","total_stations"],"title":"DistrictRadioCoverage","description":"All radio stations covering a congressional district.\n\nAggregates coverage data for all stations whose contours\nintersect with the district boundary.","example":{"by_service_type":{"am":10,"fm":15},"district_name":"Michigan's 14th Congressional District","geoid":"2614","stations":[{"callsign":"WDET-FM","coverage_pct":85.5,"facility_id":"12345"}],"total_stations":25}},"cbdistricts__DistrictSummary":{"properties":{"geoid":{"type":"string","maxLength":5,"minLength":4,"pattern":"^\\d{4,5}$","title":"Census GEOID","description":"Unique 4-5 character identifier combining state FIPS and district number","examples":["0601","3614","5000"],"x-format":"state_fips (2 digits) + district_num (2 digits)"},"namelsad":{"type":"string","title":"Namelsad","description":"Legal/Statistical Area Description - full district name","examples":["Congressional District 1","Congressional District 14","Congressional District (at Large)"]},"state_fips":{"type":"string","maxLength":2,"minLength":2,"pattern":"^\\d{2}$","title":"State FIPS Code","description":"2-digit FIPS code identifying the state","examples":["06","36","48"],"x-reference":"https://www.census.gov/library/reference/code-lists/ansi/ansi-codes-for-states.html"},"district_num":{"type":"string","maxLength":2,"minLength":2,"pattern":"^\\d{2}$","title":"District Num","description":"2-digit district number (00 for at-large districts)","examples":["01","14","00"]},"population":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Population","description":"Total population (ACS estimate)","examples":[761620,818737,650000]},"median_age":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Median Age","description":"Median age of residents in years","examples":[38.5,42.1,35.2]},"median_income":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Median Income","description":"Median household income in USD","examples":[75000,92500,55000]}},"type":"object","required":["geoid","namelsad","state_fips","district_num"],"title":"DistrictSummary","description":"District summary with key demographic metrics.\n\nUsed in list views where full details are not needed. Includes population,\nmedian age, and median household income for quick comparison.","example":{"district_num":"01","geoid":"0601","median_age":42.1,"median_income":75000,"namelsad":"Congressional District 1","population":761620,"state_fips":"06"}},"cbdistricts__ErrorResponse":{"properties":{"error":{"type":"string","title":"Error","description":"Machine-readable error code for programmatic handling","examples":["not_found","invalid_request","validation_error","internal_error"]},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail","description":"Human-readable error description with context","examples":["District 9999 not found","State FIPS must be 2 digits"]},"status_code":{"type":"integer","maximum":599.0,"minimum":400.0,"title":"Status Code","description":"HTTP status code (matches response status)","examples":[400,404,422,500]}},"type":"object","required":["error","status_code"],"title":"ErrorResponse","description":"Standard error response format for all API errors.\n\nAll non-2xx responses follow this format for consistent error handling.\n\n**Common Error Codes:**\n| Code | Description |\n|------|-------------|\n| `not_found` | Resource does not exist |\n| `invalid_request` | Malformed request parameters |\n| `validation_error` | Field validation failed |\n| `internal_error` | Unexpected server error |","example":{"detail":"District 9999 not found","error":"not_found","status_code":404}},"cbdistricts__FeatureCoverageResponse":{"properties":{"target_layer":{"type":"string","title":"Target Layer","description":"Layer containing the target feature"},"target_id":{"type":"string","title":"Target Id","description":"Target feature ID"},"target_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Name","description":"Target feature name"},"covering_layer":{"type":"string","title":"Covering Layer","description":"Layer containing the covering features"},"coverage":{"items":{"$ref":"#/components/schemas/cbdistricts__CoverageItem"},"type":"array","title":"Coverage","description":"List of features covering the target"},"total_features":{"type":"integer","minimum":0.0,"title":"Total Features","description":"Total number of covering features"}},"type":"object","required":["target_layer","target_id","covering_layer","coverage","total_features"],"title":"FeatureCoverageResponse","description":"All features covering a target feature.\n\nAggregates coverage data for all features from a specific layer\nthat intersect with the target feature.","example":{"coverage":[{"feature_id":"12345","layer":"radio-coverage","name":"WDET-FM","pct_coverage":85.2}],"covering_layer":"radio-coverage","target_id":"26020","target_layer":"michigan-state-house","target_name":"District 20","total_features":15}},"cbdistricts__GeoJSONFeature":{"properties":{"type":{"type":"string","const":"Feature","title":"Type","description":"GeoJSON object type (always 'Feature')","default":"Feature"},"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Unique feature identifier (GEOID for districts)","examples":["0601","3614"]},"properties":{"additionalProperties":true,"type":"object","title":"Properties","description":"Key-value pairs of feature attributes","example":{"geoid":"0601","namelsad":"Congressional District 1","population":761620}},"geometry":{"anyOf":[{"$ref":"#/components/schemas/cbdistricts__GeoJSONGeometry"},{"type":"null"}],"description":"Geometry object (null if geometry excluded)"}},"type":"object","required":["properties"],"title":"GeoJSONFeature","description":"GeoJSON Feature object per RFC 7946.\n\nCombines a geometry with associated properties. Each feature represents\na single geographic entity (e.g., one congressional district).","example":{"geometry":{"coordinates":[[[-122.42,37.78],[-122.41,37.77]]],"type":"Polygon"},"id":"0601","properties":{"geoid":"0601","namelsad":"Congressional District 1","state_fips":"06","district_num":"01","population":761620},"type":"Feature"}},"cbdistricts__GeoJSONFeatureCollection":{"properties":{"type":{"type":"string","const":"FeatureCollection","title":"Type","description":"GeoJSON object type (always 'FeatureCollection')","default":"FeatureCollection"},"features":{"items":{"$ref":"#/components/schemas/cbdistricts__GeoJSONFeature"},"type":"array","title":"Features","description":"Array of Feature objects"},"bbox":{"anyOf":[{"items":{"type":"number"},"type":"array","maxItems":4,"minItems":4},{"type":"null"}],"title":"Bbox","description":"Bounding box [minLon, minLat, maxLon, maxLat]","example":[-124.48,32.53,-114.13,42.01],"x-format":"[minLon, minLat, maxLon, maxLat]"}},"type":"object","required":["features"],"title":"GeoJSONFeatureCollection","description":"GeoJSON FeatureCollection per RFC 7946.\n\nContainer for multiple features. This is the standard format returned\nby list endpoints when `format=geojson` is specified.\n\n**Usage with MapLibre GL JS:**\n```javascript\nmap.addSource('districts', {\n  type: 'geojson',\n  data: '/api/v1/districts?format=geojson&state_fips=06'\n});\nmap.addLayer({\n  id: 'districts-fill',\n  type: 'fill',\n  source: 'districts',\n  paint: { 'fill-color': '#088' }\n});\n```","example":{"bbox":[-124.48,32.53,-114.13,42.01],"features":[{"geometry":{"coordinates":[[]],"type":"Polygon"},"id":"0601","properties":{"geoid":"0601","population":761620},"type":"Feature"}],"type":"FeatureCollection"}},"cbdistricts__GeoJSONGeometry":{"properties":{"type":{"type":"string","enum":["Point","LineString","Polygon","MultiPoint","MultiLineString","MultiPolygon"],"title":"Type","description":"Geometry type (Polygon, MultiPolygon, Point, etc.)","examples":["Polygon","MultiPolygon","Point"]},"coordinates":{"title":"Coordinates","description":"Coordinate array in [longitude, latitude] format (GeoJSON order)","example":[[[-122.42,37.78],[-122.41,37.78],[-122.41,37.77],[-122.42,37.77],[-122.42,37.78]]],"x-note":"Coordinates are [lon, lat] not [lat, lon]"}},"type":"object","required":["type","coordinates"],"title":"GeoJSONGeometry","description":"GeoJSON geometry object per RFC 7946.\n\nRepresents the spatial extent of a feature. For congressional districts,\nthis is typically a Polygon or MultiPolygon.\n\n**Geometry Types:**\n| Type | Description | Use Case |\n|------|-------------|----------|\n| `Polygon` | Single contiguous area | Most districts |\n| `MultiPolygon` | Multiple disconnected areas | Island districts |","example":{"coordinates":[[[-122.42,37.78],[-122.41,37.78],[-122.41,37.77],[-122.42,37.77],[-122.42,37.78]]],"type":"Polygon"}},"cbdistricts__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbdistricts__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbdistricts__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbdistricts__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbdistricts__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbdistricts__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbdistricts__IntersectionStatsResponse":{"properties":{"total_intersections":{"type":"integer","minimum":0.0,"title":"Total Intersections","description":"Total number of cached intersection records"},"layer_pairs":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Layer Pairs","description":"List of computed layer pairs with counts","example":[{"count":825,"layer_a":"radio-coverage","layer_b":"michigan-state-house"}]},"unique_features_a":{"type":"integer","minimum":0.0,"title":"Unique Features A","description":"Count of unique features from layer A side"},"unique_features_b":{"type":"integer","minimum":0.0,"title":"Unique Features B","description":"Count of unique features from layer B side"},"db_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Db Size Bytes","description":"Database file size in bytes"},"last_computed":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Computed","description":"Timestamp of most recent computation"}},"type":"object","required":["total_intersections","layer_pairs","unique_features_a","unique_features_b"],"title":"IntersectionStatsResponse","description":"Aggregate statistics for the intersection cache.\n\nProvides high-level metrics about cached intersection data.","example":{"db_size_bytes":4500000,"last_computed":"2025-01-15T10:30:00Z","layer_pairs":[{"count":825,"layer_a":"radio-coverage","layer_b":"michigan-state-house"}],"total_intersections":825,"unique_features_a":150,"unique_features_b":110}},"cbdistricts__JobStatus":{"type":"string","enum":["pending","running","completed","failed","cancelled"],"title":"JobStatus","description":"Job execution states."},"cbdistricts__LayerFeature":{"properties":{"id":{"type":"string","title":"Id","description":"Unique feature identifier within the layer","examples":["26001","26003"]},"name":{"type":"string","title":"Name","description":"Display name of the feature","examples":["Alcona County","Alger County"]},"properties":{"additionalProperties":true,"type":"object","title":"Properties","description":"All property values for the feature"},"geometry":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Geometry","description":"GeoJSON geometry object (if requested)"}},"type":"object","required":["id","name","properties"],"title":"LayerFeature","description":"A single feature from a geographic layer.\n\nRepresents one entity (county, district, etc.) with its properties\nand optionally its geometry.","example":{"geometry":{"coordinates":[[]],"type":"Polygon"},"id":"26001","name":"Alcona County","properties":{"geoid":"26001","namelsad":"Alcona County","aland":1742590000}}},"cbdistricts__LayerFeatureListResponse":{"properties":{"layer_id":{"type":"string","title":"Layer Id","description":"Layer identifier"},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Number of features in this response"},"features":{"items":{"$ref":"#/components/schemas/cbdistricts__LayerFeature"},"type":"array","title":"Features","description":"Array of feature objects"}},"type":"object","required":["layer_id","count","features"],"title":"LayerFeatureListResponse","description":"Response model for listing features in a layer.\n\nContains the layer identifier, count, and array of feature objects.","example":{"count":83,"features":[{"id":"26001","name":"Alcona County","properties":{"geoid":"26001"}}],"layer_id":"mi_counties"}},"cbdistricts__LayerListResponse":{"properties":{"version":{"type":"string","title":"Version","description":"Layer registry version for cache validation","examples":["1.0.0","2024.01.15"]},"updated":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated","description":"Last registry update date (YYYY-MM-DD)","examples":["2024-01-15"]},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Total number of available layers"},"layers":{"items":{"$ref":"#/components/schemas/cbdistricts__LayerSummary"},"type":"array","title":"Layers","description":"List of available layers"}},"type":"object","required":["version","count","layers"],"title":"LayerListResponse","description":"Response model for listing all available layers.\n\nIncludes registry version for cache invalidation and the complete\nlist of available layers with summary information.","example":{"count":5,"layers":[{"feature_count":83,"id":"mi_counties","name":"Michigan Counties","scope":"state","state":"MI"}],"updated":"2024-01-15","version":"1.0.0"}},"cbdistricts__LayerMetadata":{"properties":{"id":{"type":"string","pattern":"^[a-z0-9_-]+$","title":"Id","description":"Unique layer identifier (lowercase, dashes/underscores)","examples":["federal-congressional","tx-state-house","tx-congressional-2025"]},"name":{"type":"string","title":"Name","description":"Human-readable layer name","examples":["Michigan Counties","Ohio State House Districts"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Detailed description of the layer"},"scope":{"type":"string","enum":["federal","state","local"],"title":"Scope","description":"Geographic scope of the layer","examples":["federal","state","local"]},"state":{"anyOf":[{"type":"string","maxLength":2,"minLength":2},{"type":"null"}],"title":"State","description":"2-letter state code for state/local layers","examples":["MI","OH","CA"]},"database":{"type":"string","title":"Database","description":"Path to the DuckDB database file containing this layer"},"feature_count":{"type":"integer","minimum":0.0,"title":"Feature Count","description":"Total number of features in the layer","examples":[83,441,100]},"geometry_type":{"type":"string","title":"Geometry Type","description":"Type of geometry (Polygon, MultiPolygon, Point)","examples":["Polygon","MultiPolygon","Point"]},"id_field":{"type":"string","title":"Id Field","description":"Property field used as unique identifier","examples":["geoid","fips","id"]},"name_field":{"type":"string","title":"Name Field","description":"Property field used for display names","examples":["namelsad","name","title"]},"properties":{"additionalProperties":{"$ref":"#/components/schemas/cbdistricts__PropertySchema"},"type":"object","title":"Properties","description":"Schema for available property fields"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Data source name","examples":["Census Bureau TIGER/Line","Michigan Open Data Portal"]},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url","description":"URL to the original data source"}},"type":"object","required":["id","name","scope","database","feature_count","geometry_type","id_field","name_field","properties"],"title":"LayerMetadata","description":"Complete metadata for a geographic layer.\n\nIncludes layer identification, schema definition, data source information,\nand configuration for rendering and querying.\n\n**Layer Scopes:**\n| Scope | Description | Examples |\n|-------|-------------|----------|\n| `federal` | National boundaries | Congressional districts |\n| `state` | State-specific boundaries | State legislative districts |\n| `local` | Local boundaries | Counties, cities |","example":{"database":"data/layers/mi_counties.duckdb","description":"County boundaries for the state of Michigan","feature_count":83,"geometry_type":"MultiPolygon","id":"mi_counties","id_field":"geoid","name":"Michigan Counties","name_field":"namelsad","properties":{"geoid":{"description":"Census GEOID","type":"string"},"namelsad":{"description":"County name","type":"string"}},"scope":"state","source":"Census Bureau TIGER/Line","source_url":"https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html","state":"MI"}},"cbdistricts__LayerSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Unique layer identifier","examples":["mi_counties","cd119"]},"name":{"type":"string","title":"Name","description":"Human-readable layer name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Brief description of the layer"},"scope":{"type":"string","title":"Scope","description":"Geographic scope (federal/state/local)"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State code for state/local layers"},"feature_count":{"type":"integer","minimum":0.0,"title":"Feature Count","description":"Number of features in the layer"}},"type":"object","required":["id","name","scope","feature_count"],"title":"LayerSummary","description":"Summary information for layer listings.\n\nLightweight model for list views containing only essential metadata.","example":{"description":"County boundaries for Michigan","feature_count":83,"id":"mi_counties","name":"Michigan Counties","scope":"state","state":"MI"}},"cbdistricts__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbdistricts__PaginatedResponse_DemographicsSummary_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/cbdistricts__DemographicsSummary"},"type":"array","title":"Data","description":"Array of items for the current page"},"meta":{"$ref":"#/components/schemas/cbdistricts__PaginationMeta","description":"Pagination metadata"}},"type":"object","required":["data","meta"],"title":"PaginatedResponse[DemographicsSummary]"},"cbdistricts__PaginatedResponse_DistrictSummary_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/cbdistricts__DistrictSummary"},"type":"array","title":"Data","description":"Array of items for the current page"},"meta":{"$ref":"#/components/schemas/cbdistricts__PaginationMeta","description":"Pagination metadata"}},"type":"object","required":["data","meta"],"title":"PaginatedResponse[DistrictSummary]"},"cbdistricts__PaginationMeta":{"properties":{"total":{"type":"integer","minimum":0.0,"title":"Total","description":"Total number of items matching the query across all pages","examples":[441,52,100]},"limit":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Limit","description":"Maximum number of items returned per page (requested limit)","examples":[50,100,25]},"offset":{"type":"integer","minimum":0.0,"title":"Offset","description":"Number of items skipped (for pagination)","examples":[0,50,100]},"has_more":{"type":"boolean","title":"Has More","description":"True if more items exist beyond the current page","examples":[true,false]}},"type":"object","required":["total","limit","offset","has_more"],"title":"PaginationMeta","description":"Pagination metadata included in all list responses.\n\nProvides information about the current page position and whether\nadditional results are available.","example":{"has_more":true,"limit":50,"offset":0,"total":441}},"cbdistricts__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbdistricts__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbdistricts__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbdistricts__PropertySchema":{"properties":{"type":{"type":"string","enum":["string","integer","float","boolean","object"],"title":"Type","description":"Data type of the property","examples":["string","integer","float","boolean"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Human-readable description of the property","examples":["County name","Population count","Area in square meters"]}},"type":"object","required":["type"],"title":"PropertySchema","description":"Schema definition for a layer property.\n\nDescribes the data type and meaning of a property field available\nin the layer's features."},"cbdistricts__RadioCoverage":{"properties":{"facility_id":{"type":"string","title":"Facility Id","description":"FCC facility identifier"},"callsign":{"type":"string","title":"Callsign","description":"Station callsign"},"service_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service Type","description":"am or fm"},"frequency":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Frequency","description":"Broadcast frequency"},"power_kw":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Power Kw","description":"Transmit power in kilowatts"},"coverage_pct":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Coverage Pct","description":"Percentage of district area covered by this station (0-100)","examples":[85.5,42.3,100.0],"x-unit":"percent"}},"type":"object","required":["facility_id","callsign","coverage_pct"],"title":"RadioCoverage","description":"Radio coverage information for a single station-district pair.\n\nRepresents how much of a congressional district falls within\na station's coverage contour.","example":{"callsign":"WDET-FM","coverage_pct":85.5,"facility_id":"12345","frequency":101.9,"power_kw":48,"service_type":"fm"}},"cbdistricts__RadioStation":{"properties":{"facility_id":{"type":"string","title":"Facility Id","description":"Unique FCC facility identifier","examples":["12345","67890"]},"callsign":{"type":"string","pattern":"^[A-Z]{3,4}(-[AF]M)?$","title":"Callsign","description":"Station callsign (e.g., WBCH-AM, WXYZ-FM)","examples":["WBCH-AM","WXYZ-FM","WDET-FM"]},"service_type":{"anyOf":[{"type":"string"},{"type":"null"}],"enum":["am","fm"],"title":"Service Type","description":"Broadcast band (am or fm)","examples":["am","fm"]},"frequency":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Frequency","description":"Broadcast frequency (kHz for AM, MHz for FM)","examples":[1340,101.9],"x-am-range":"530-1700 kHz","x-fm-range":"88.0-108.0 MHz","x-unit":"kHz (AM) or MHz (FM)"},"power_kw":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Power Kw","description":"Transmit power in kilowatts","examples":[50,100,0.25],"x-unit":"kilowatts"},"station_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Class","description":"FCC station class (affects coverage and power limits)","examples":["A","B","C","D"],"x-classes":{"A":"Local coverage (up to 6 kW)","B":"Regional coverage (up to 50 kW)","C":"Wide coverage (up to 100 kW)","D":"Secondary/low power"},"x-reference":"https://www.fcc.gov/media/radio/fm-station-classes"},"hours_operation":{"anyOf":[{"type":"string"},{"type":"null"}],"enum":["U","D","N"],"title":"Hours Operation","description":"Hours of operation code","examples":["U","D","N"],"x-codes":{"D":"Daytime only","N":"Nighttime only","U":"Unlimited"}},"state":{"anyOf":[{"type":"string","maxLength":2,"minLength":2},{"type":"null"}],"title":"State","description":"Two-letter state code of transmitter location","examples":["MI","OH","CA"]},"antenna_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Antenna Mode","description":"Antenna configuration mode","examples":["ND","DA-1","DA-2","DA-N"],"x-codes":{"DA-1":"Directional (same day/night)","DA-2":"Directional (different day/night)","DA-N":"Directional (night only)","ND":"Non-directional"}},"tower_count":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Tower Count","description":"Number of antenna towers in the array","examples":[1,3,6]},"nielsen_dma":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Dma","description":"Nielsen Designated Market Area name","examples":["Detroit","Los Angeles","New York"]},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner","description":"Station owner/licensee name","examples":["iHeartMedia","Cumulus Media","NPR"]},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format","description":"Programming format","examples":["News/Talk","Country","Classic Rock","NPR"]},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website","description":"Station website URL"},"has_contour":{"type":"boolean","title":"Has Contour","description":"Whether coverage contour polygon is available","default":false},"location":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Location","description":"Transmitter coordinates {lat, lon}","example":{"lat":42.3314,"lon":-83.0458}}},"type":"object","required":["facility_id","callsign"],"title":"RadioStation","description":"Complete radio station model with all available fields.\n\nIncludes antenna configuration, ownership, format, and coverage data\nfor detailed station analysis.","example":{"antenna_mode":"ND","callsign":"WDET-FM","facility_id":"12345","format":"NPR/News","frequency":101.9,"has_contour":true,"hours_operation":"U","location":{"lat":42.3578,"lon":-83.0706},"nielsen_dma":"Detroit","owner":"Wayne State University","power_kw":48,"service_type":"fm","state":"MI","station_class":"B","tower_count":1,"website":"https://wdet.org"}},"cbdistricts__RadioStatsResponse":{"properties":{"total_stations":{"type":"integer","minimum":0.0,"title":"Total Stations","description":"Total number of radio stations in database","examples":[15000]},"stations_with_contours":{"type":"integer","minimum":0.0,"title":"Stations With Contours","description":"Number of stations with coverage contour data","examples":[12500]},"total_coverage_relationships":{"type":"integer","minimum":0.0,"title":"Total Coverage Relationships","description":"Total station-district coverage pairs","examples":[45000]},"districts_with_coverage":{"type":"integer","minimum":0.0,"title":"Districts With Coverage","description":"Number of districts with at least one station","examples":[441]},"by_service_type":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Service Type","description":"Station counts by service type","example":{"am":5000,"fm":10000}},"by_state":{"additionalProperties":{"type":"integer"},"type":"object","title":"By State","description":"Station counts by state code"},"power_stats":{"additionalProperties":{"type":"number"},"type":"object","title":"Power Stats","description":"Power statistics (min, max, avg in kW)","example":{"avg":25.5,"max":100,"min":0.01}}},"type":"object","required":["total_stations","stations_with_contours","total_coverage_relationships","districts_with_coverage","by_service_type","by_state","power_stats"],"title":"RadioStatsResponse","description":"Aggregate statistics for the radio coverage dataset.\n\nProvides high-level metrics about station counts, coverage,\nand distribution by service type and state.","example":{"by_service_type":{"am":5000,"fm":10000},"by_state":{"CA":800,"MI":350,"OH":425},"districts_with_coverage":441,"power_stats":{"avg":25.5,"max":100,"min":0.01},"stations_with_contours":12500,"total_coverage_relationships":45000,"total_stations":15000}},"cbdistricts__StateDetail":{"properties":{"state_fips":{"type":"string","maxLength":2,"minLength":2,"pattern":"^\\d{2}$","title":"State Fips","description":"2-digit FIPS code uniquely identifying the state","examples":["06","36","48"],"x-reference":"https://www.census.gov/library/reference/code-lists/ansi/ansi-codes-for-states.html"},"state_name":{"type":"string","title":"State Name","description":"Full state name derived from district names","examples":["California","New York","Texas"]},"district_count":{"type":"integer","maximum":52.0,"minimum":1.0,"title":"District Count","description":"Number of congressional districts in the state (1-52)","examples":[52,38,26],"x-note":"States with 1 district are 'at-large' (e.g., Wyoming, Vermont)"},"total_population":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Total Population","description":"Total state population (sum of all districts)","examples":[39538223,20201249]},"avg_median_income":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Avg Median Income","description":"Average median household income across all districts","examples":[78672,71117]},"avg_median_age":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Avg Median Age","description":"Average median age across all districts","examples":[36.8,39.2]},"avg_unemployment_rate":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Avg Unemployment Rate","description":"Average unemployment rate across all districts","examples":[4.2,5.1],"x-unit":"percent"},"total_land_area":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Total Land Area","description":"Total land area in square meters","examples":[403466610000,268580800000],"x-conversion":"divide by 2,589,988 for square miles","x-unit":"square meters"},"total_water_area":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Total Water Area","description":"Total water area in square meters","examples":[20047120000,52094100000],"x-conversion":"divide by 2,589,988 for square miles","x-unit":"square meters"}},"type":"object","required":["state_fips","state_name","district_count"],"title":"StateDetail","description":"Detailed state information with additional demographics and area.\n\nExtends StateInfo with average age, unemployment, and geographic area\nfor comprehensive state-level analysis.","example":{"avg_median_age":36.8,"avg_median_income":78672,"avg_unemployment_rate":4.2,"district_count":52,"state_fips":"06","state_name":"California","total_land_area":403466610000,"total_population":39538223,"total_water_area":20047120000}},"cbdistricts__StateInfo":{"properties":{"state_fips":{"type":"string","maxLength":2,"minLength":2,"pattern":"^\\d{2}$","title":"State Fips","description":"2-digit FIPS code uniquely identifying the state","examples":["06","36","48"],"x-reference":"https://www.census.gov/library/reference/code-lists/ansi/ansi-codes-for-states.html"},"state_name":{"type":"string","title":"State Name","description":"Full state name derived from district names","examples":["California","New York","Texas"]},"district_count":{"type":"integer","maximum":52.0,"minimum":1.0,"title":"District Count","description":"Number of congressional districts in the state (1-52)","examples":[52,38,26],"x-note":"States with 1 district are 'at-large' (e.g., Wyoming, Vermont)"},"total_population":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Total Population","description":"Total state population (sum of all districts)","examples":[39538223,20201249]},"avg_median_income":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Avg Median Income","description":"Average median household income across all districts","examples":[78672,71117]}},"type":"object","required":["state_fips","state_name","district_count"],"title":"StateInfo","description":"State summary with district count and aggregate demographics.\n\nProvides a high-level overview of congressional representation and\nkey demographic metrics for each state.","example":{"avg_median_income":78672,"district_count":52,"state_fips":"06","state_name":"California","total_population":39538223}},"cbdistricts__StationDistrictCoverage":{"properties":{"facility_id":{"type":"string","title":"Facility Id","description":"FCC facility identifier"},"callsign":{"type":"string","title":"Callsign","description":"Station callsign"},"districts":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Districts","description":"List of covered districts with coverage percentages","example":[{"coverage_pct":85.5,"geoid":"2614","name":"Michigan's 14th"}]},"total_districts":{"type":"integer","minimum":0.0,"title":"Total Districts","description":"Total number of districts covered"}},"type":"object","required":["facility_id","callsign","districts","total_districts"],"title":"StationDistrictCoverage","description":"All congressional districts covered by a radio station.\n\nShows which districts fall within a station's coverage contour\nand what percentage of each district is covered.","example":{"callsign":"WDET-FM","districts":[{"coverage_pct":85.5,"geoid":"2614","name":"Michigan's 14th"},{"coverage_pct":45.2,"geoid":"2613","name":"Michigan's 13th"}],"facility_id":"12345","total_districts":3}},"cbdistricts__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbdistricts__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbdistricts__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbdistricts__WikipediaDistrictData":{"properties":{"geoid":{"type":"string","title":"Geoid","description":"Census GEOID (4 digits: state FIPS + district)"},"state_code":{"type":"string","title":"State Code","description":"Two-letter state abbreviation (lowercase)"},"state_name":{"type":"string","title":"State Name","description":"Full state name"},"district_number":{"type":"integer","title":"District Number","description":"District number (0 for at-large)"},"is_at_large":{"type":"boolean","title":"Is At Large","description":"Whether this is an at-large district"},"title":{"type":"string","title":"Title","description":"Formal district title"},"summary":{"type":"string","title":"Summary","description":"Wikipedia summary (2-3 sentences)"},"population":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Population","description":"Population from Wikipedia"},"representative":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Representative","description":"Current representative name"},"party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party","description":"Representative's party (Republican/Democrat)"},"cook_pvi":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cook Pvi","description":"Cook Partisan Voting Index (e.g., R+5, D+10, EVEN)"},"counties":{"items":{"type":"string"},"type":"array","title":"Counties","description":"Counties in the district"},"wikipedia_url":{"type":"string","title":"Wikipedia Url","description":"Full Wikipedia page URL"},"cached_at":{"type":"string","format":"date-time","title":"Cached At","description":"When this data was cached"},"cache_expires_at":{"type":"string","format":"date-time","title":"Cache Expires At","description":"When the cache expires"}},"type":"object","required":["geoid","state_code","state_name","district_number","is_at_large","title","summary","wikipedia_url","cached_at","cache_expires_at"],"title":"WikipediaDistrictData","description":"Wikipedia-sourced data for a congressional district.","example":{"cache_expires_at":"2024-01-17T10:30:00Z","cached_at":"2024-01-15T10:30:00Z","cook_pvi":"R+3","counties":["Adair","Adams","Cass","Dallas","Guthrie"],"district_number":3,"geoid":"1903","is_at_large":false,"party":"Republican","population":"818,737","representative":"Zach Nunn","state_code":"ia","state_name":"Iowa","summary":"Iowa's 3rd congressional district is a congressional district in the U.S. state of Iowa...","title":"Iowa's 3rd Congressional District","wikipedia_url":"https://en.wikipedia.org/wiki/Iowa%27s_3rd_congressional_district"}},"cbea__ChatRequest":{"properties":{"sender":{"type":"string","title":"Sender","description":"Stable identifier for the contact sending the message. Keys identity resolution, conversation history, and per-contact memory. For MCP/web this is typically a user id or handle; for SMS/WhatsApp it is the phone number."},"message":{"type":"string","title":"Message","description":"The user's message text — the inbound turn for this request."},"transport":{"type":"string","title":"Transport","description":"Logical transport the message arrived on. Recorded on the inbound message and used for routing/observability. One of `mcp`, `web`, `sms`, `whatsapp`.","default":"mcp"}},"type":"object","required":["sender","message"],"title":"ChatRequest","description":"A single chat turn submitted to the agent loop.\n\nUsed by both the synchronous `/api/v1/chat` endpoint and the SSE\n`/api/v1/chat/stream` variant. `sender` keys the per-contact identity\nand conversation history; `message` is the user's text turn.","examples":[{"message":"What shipped on cbpulse this week?","sender":"brad","transport":"mcp"}]},"cbea__ChatResponse":{"properties":{"recipient":{"type":"string","title":"Recipient","description":"Identifier the response is addressed to (echoes the request `sender`)."},"message":{"type":"string","title":"Message","description":"The agent's user-facing reply text."},"tools_used":{"items":{"type":"string"},"type":"array","title":"Tools Used","description":"Names of the tools the agent loop invoked while producing this reply.","default":[]},"latency_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latency Ms","description":"End-to-end processing latency in milliseconds, if measured."},"tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tokens","description":"Total tokens consumed (input + output) for this turn, if reported."}},"type":"object","required":["recipient","message"],"title":"ChatResponse","description":"The agent's reply to a single chat turn.\n\nCarries the user-facing text plus observability metadata (which tools the\nloop invoked, end-to-end latency, and token usage) so callers can surface\nor log it.","examples":[{"latency_ms":2840,"message":"Three PRs merged on cbpulse this week: ...","recipient":"brad","tokens":1620,"tools_used":["pulse_query"]}]},"cbea__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbea__DigestResponse":{"properties":{"total_events":{"type":"integer","title":"Total Events","description":"Total GitHub events collected across all repos in the window."},"section_count":{"type":"integer","title":"Section Count","description":"Number of per-repo sections in the report (repos with at least one event)."},"body_length":{"type":"integer","title":"Body Length","description":"Character length of the formatted markdown body that was (or would be) delivered."},"truncated":{"type":"boolean","title":"Truncated","description":"True if the formatted body was clipped to fit the MMS size budget."},"delivered_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivered To","description":"Recipient phone number the digest was delivered to, or `null` when `dry_run=true` (built but not sent)."},"dry_run":{"type":"boolean","title":"Dry Run","description":"Echoes whether this run skipped delivery (preflight/test mode)."}},"type":"object","required":["total_events","section_count","body_length","truncated","delivered_to","dry_run"],"title":"DigestResponse","description":"Slim summary of a daily-digest run.\n\nA compact, log-friendly view of the workflow outcome — the full\n`DigestReport` (with every event and the formatted body) is too heavy for\ncbcron run logs, so the trigger endpoint returns only counts and delivery\nstatus.","examples":[{"body_length":1180,"delivered_to":"+15551234567","dry_run":false,"section_count":4,"total_events":17,"truncated":false}]},"cbea__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbea__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbea__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbea__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbea__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbea__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbea__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbea__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbea__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbea__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbea__SpanRecord":{"properties":{"trace_id":{"type":"string","title":"Trace Id","description":"32-char hex trace ID this span belongs to."},"span_id":{"type":"string","title":"Span Id","description":"16-char hex ID of this span."},"parent_span_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Span Id","description":"16-char hex ID of the parent span, or null for the root span."},"name":{"type":"string","title":"Name","description":"Span name, e.g. `cbea.handle_message` or `cbea.tool.pulse_query`."},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind","description":"OTel SpanKind name (INTERNAL, SERVER, CLIENT, ...)."},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"UTC span start time."},"ended_at":{"type":"string","format":"date-time","title":"Ended At","description":"UTC span end time."},"duration_ms":{"type":"number","title":"Duration Ms","description":"Span duration in milliseconds."},"status_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Code","description":"OTel StatusCode name (OK / ERROR / UNSET)."},"status_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Message","description":"Status detail message, typically set on ERROR spans."},"attributes":{"additionalProperties":true,"type":"object","title":"Attributes","description":"Captured span attributes (e.g. `cbea.transport`, token counts)."},"events":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Events","description":"Span events (timestamped annotations) recorded during the span."}},"type":"object","required":["trace_id","span_id","name","started_at","ended_at","duration_ms"],"title":"SpanRecord","description":"One span from the spans table, normalized for API consumption.\n\nMirrors an OpenTelemetry span: identity (`trace_id` / `span_id` /\n`parent_span_id`), name and kind, start/end timing and derived duration,\nstatus, and the captured attribute/event bags.","examples":[{"attributes":{"cbea.transport":"sms"},"duration_ms":2840.5,"ended_at":"2026-06-20T18:04:13.960Z","events":[],"kind":"SERVER","name":"cbea.handle_message","span_id":"00f067aa0ba902b7","started_at":"2026-06-20T18:04:11.120Z","status_code":"OK","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}]},"cbea__SpanTree":{"properties":{"span":{"$ref":"#/components/schemas/cbea__SpanRecord","description":"The span at this node of the tree."},"children":{"items":{"$ref":"#/components/schemas/cbea__SpanTree"},"type":"array","title":"Children","description":"Child spans nested under this span, recursively."}},"type":"object","required":["span"],"title":"SpanTree","description":"A span and its (recursive) children — the shape returned by\n/api/v1/traces/{trace_id}.\n\nDefining ``children`` as a list of ``SpanTree`` requires the model\nto refer to itself; pydantic v2 handles forward refs after the\nexplicit ``model_rebuild()`` below.","examples":[{"children":[],"span":{"attributes":{},"duration_ms":2840.5,"ended_at":"2026-06-20T18:04:13.960Z","events":[],"kind":"SERVER","name":"cbea.handle_message","span_id":"00f067aa0ba902b7","started_at":"2026-06-20T18:04:11.120Z","status_code":"OK","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}]},"cbea__StanddownRequest":{"properties":{"minutes":{"type":"integer","maximum":10080.0,"minimum":1.0,"title":"Minutes","default":240},"reason":{"type":"string","title":"Reason","default":""}},"type":"object","title":"StanddownRequest","description":"Mute the agent for this contact for ``minutes``."},"cbea__StartFlowRequest":{"properties":{"contact_id":{"type":"string","title":"Contact Id"},"context":{"additionalProperties":true,"type":"object","title":"Context","default":{}},"supersede":{"type":"boolean","title":"Supersede","default":false}},"type":"object","required":["contact_id"],"title":"StartFlowRequest","description":"Target contact for a flow start, plus optional seed context.\n\n``context`` seeds the claim so a flow can open already knowing its\nsubject — e.g. confirm's ``question``/``subject``/``notify`` keys. The\nanswering contact's id is seeded as ``contact`` automatically.\n\n``supersede`` opts into \"abandon the contact's live flow, ask this\ninstead\" — the live journey is cancelled, never run alongside\n(issue #30). Without it, a start against a claimed contact is a 409."},"cbea__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbea__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbea__TraceListResponse":{"properties":{"records":{"items":{"$ref":"#/components/schemas/cbea__TraceSummary"},"type":"array","title":"Records","description":"The trace summaries on this page."},"total":{"type":"integer","title":"Total","description":"Total number of traces matching the query (across all pages)."},"limit":{"type":"integer","title":"Limit","description":"Maximum number of records requested for this page."},"offset":{"type":"integer","title":"Offset","description":"Number of records skipped before this page."}},"type":"object","required":["records","total","limit","offset"],"title":"TraceListResponse","description":"A page of trace summaries, ordered by most-recent root span start time.","examples":[{"limit":100,"offset":0,"records":[],"total":128}]},"cbea__TraceSummary":{"properties":{"trace_id":{"type":"string","title":"Trace Id","description":"32-char hex trace ID"},"root_span_id":{"type":"string","title":"Root Span Id","description":"16-char hex root span ID"},"root_span_name":{"type":"string","title":"Root Span Name","description":"Name of the root span (e.g. 'cbea.handle_message')"},"root_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Root Kind","description":"OTel SpanKind name (INTERNAL, SERVER, CLIENT, ...)"},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"Root span start time"},"duration_ms":{"type":"number","title":"Duration Ms","description":"Root span duration in ms"},"status_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Code","description":"OTel StatusCode name (OK / ERROR / UNSET)"},"span_count":{"type":"integer","title":"Span Count","description":"Total number of spans in this trace tree"}},"type":"object","required":["trace_id","root_span_id","root_span_name","started_at","duration_ms","span_count"],"title":"TraceSummary","description":"One row per `trace_id` for the paginated `/api/v1/traces` list.\n\nA flattened summary of a whole trace tree — the root span's name, timing,\nand status plus the total span count — so the list view can render without\nloading every span. Drill into a single trace via\n`GET /api/v1/traces/{trace_id}`.","examples":[{"duration_ms":2840.5,"root_kind":"SERVER","root_span_id":"00f067aa0ba902b7","root_span_name":"cbea.handle_message","span_count":6,"started_at":"2026-06-20T18:04:11.120Z","status_code":"OK","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}]},"cbea__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbelections__CalendarLookupResponse":{"properties":{"state":{"type":"string","title":"State"},"cycle":{"type":"integer","title":"Cycle"},"kind":{"type":"string","title":"Kind"},"election":{"anyOf":[{"$ref":"#/components/schemas/cbelections__ElectionDate"},{"type":"null"}],"description":"the one row — null when none match OR when ambiguous"},"ambiguous":{"type":"boolean","title":"Ambiguous","description":"the matching rows disagree on the date; a consumer deriving a legal window must not be handed a coin flip"},"candidates":{"items":{"$ref":"#/components/schemas/cbelections__ElectionDate"},"type":"array","title":"Candidates","description":"the disagreeing rows, only when ambiguous"}},"additionalProperties":true,"type":"object","required":["state","cycle","kind","ambiguous"],"title":"CalendarLookupResponse"},"cbelections__CalendarResponse":{"properties":{"count":{"type":"integer","title":"Count"},"elections":{"items":{"$ref":"#/components/schemas/cbelections__ElectionDate"},"type":"array","title":"Elections"},"election_types":{"additionalProperties":{"type":"string"},"type":"object","title":"Election Types","description":"FEC type id -> human name, the decode ring for election_type"}},"additionalProperties":true,"type":"object","required":["count","elections","election_types"],"title":"CalendarResponse"},"cbelections__CallEntry":{"properties":{"office_code":{"type":"string","title":"Office Code"},"party":{"type":"string","title":"Party"},"winner_name":{"type":"string","title":"Winner Name","description":"candidate display name being called"},"winner_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Winner Key","description":"candidate key when the client knows it"},"basis":{"type":"string","pattern":"^(clinch|model|desk)$","title":"Basis","description":"what the call rests on: clinch | model | desk"},"called_by":{"type":"string","title":"Called By","description":"who made the call (name or email)"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"retract":{"type":"boolean","title":"Retract","description":"true supersedes the standing call for this contest — the journal keeps both; screens show neither","default":false},"id":{"type":"string","title":"Id"},"at":{"type":"string","title":"At","description":"ISO-8601 UTC server receive time"}},"type":"object","required":["office_code","party","winner_name","basis","called_by","id","at"],"title":"CallEntry"},"cbelections__CallEntryIn":{"properties":{"office_code":{"type":"string","title":"Office Code"},"party":{"type":"string","title":"Party"},"winner_name":{"type":"string","title":"Winner Name","description":"candidate display name being called"},"winner_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Winner Key","description":"candidate key when the client knows it"},"basis":{"type":"string","pattern":"^(clinch|model|desk)$","title":"Basis","description":"what the call rests on: clinch | model | desk"},"called_by":{"type":"string","title":"Called By","description":"who made the call (name or email)"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"retract":{"type":"boolean","title":"Retract","description":"true supersedes the standing call for this contest — the journal keeps both; screens show neither","default":false}},"type":"object","required":["office_code","party","winner_name","basis","called_by"],"title":"CallEntryIn","description":"One race call, as made by the desk."},"cbelections__CallbackRow":{"properties":{"outcome_id":{"type":"string","title":"Outcome Id"},"candidate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Id"},"channel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel"},"disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disposition"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"callback_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback At","description":"set iff disposition = callback — the due-queue key"},"crm_person_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crm Person Id"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Level"}},"additionalProperties":true,"type":"object","required":["outcome_id"],"title":"CallbackRow","description":"An outcome row joined to the roster so it is workable without a\nsecond fetch."},"cbelections__CallbacksResponse":{"properties":{"callbacks":{"items":{"$ref":"#/components/schemas/cbelections__CallbackRow"},"type":"array","title":"Callbacks"},"count":{"type":"integer","title":"Count"}},"additionalProperties":true,"type":"object","required":["callbacks","count"],"title":"CallbacksResponse"},"cbelections__CallsResponse":{"properties":{"channel":{"type":"string","title":"Channel"},"count":{"type":"integer","title":"Count"},"calls":{"items":{"$ref":"#/components/schemas/cbelections__CallEntry"},"type":"array","title":"Calls"}},"type":"object","required":["channel","count","calls"],"title":"CallsResponse"},"cbelections__Candidate":{"properties":{"key":{"type":"string","title":"Key","description":"Stable column key (candidate_id or name slug)"},"display_name":{"type":"string","title":"Display Name","description":"Candidate name as published by the SoS"},"party":{"type":"string","title":"Party","description":"Canonical party short code: R|D|L|G|I|WI|N"},"candidate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Id","description":"Roster candidate_id when this option matched the roster"},"is_writein":{"type":"boolean","title":"Is Writein","default":false}},"type":"object","required":["key","display_name","party"],"title":"Candidate","description":"One ballot option in a contest. ``key`` is stable for the matrix\ncolumn join — the roster ``candidate_id`` when matched, else a name\nslug."},"cbelections__CandidateRow":{"properties":{"candidate_id":{"type":"string","title":"Candidate Id"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"cycle":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cycle"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office"},"office_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office Code"},"office_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office Kind"},"district":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"District"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Level"},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage"},"stage_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage Date"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party"},"party_group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Group"},"incumbent":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Incumbent","description":"TRUE is asserted by every source that writes it. FALSE is asserted only by sources that publish the fact (MA OCPF, MN CFB); the mi2026 catalog and the MI ballot list write FALSE as a default, sitting members included. Do not build an incumbency signal on FALSE."},"on_ballot":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"On Ballot","description":"ballot ACCESS. FALSE carries three meanings that only ballot_asserted separates: with ballot_asserted=true the source says OUT; with ballot_asserted=false nobody ever established it (every finance-portal harvest writes this)."},"ballot_asserted":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ballot Asserted"},"advanced":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Advanced","description":"TRUE came out of the primary, FALSE was defeated, NULL means no published result has been joined to this row — it is NOT a loss; a district the results loader could not key reads NULL for every candidate, winner included."},"seat_ambiguous":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Seat Ambiguous"},"status_desc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Desc"},"contact_suppressed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Contact Suppressed","description":"the CANDIDATE asked for withholding — distinct from a contact's use_restriction, which is the source's licence"},"level_inferred":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Level Inferred"},"field_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Field Size","description":"who is actually running in this row's race — never recomputed against the caller's filter"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"source_catalog":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Catalog"},"found_by_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Found By Unit"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"pid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pid"},"svid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Svid"},"person_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Person Key"},"identity_band":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Identity Band"},"race_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race Key"},"contacts":{"items":{"$ref":"#/components/schemas/cbelections__ContactPoint"},"type":"array","title":"Contacts"},"has_contact":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Contact"},"last_outcome":{"anyOf":[{"$ref":"#/components/schemas/cbelections__Outcome"},{"type":"null"}]}},"additionalProperties":true,"type":"object","required":["candidate_id"],"title":"CandidateRow","description":"One roster row. The three-valued columns are the contract's hard part."},"cbelections__CandidatesPage":{"properties":{"total":{"type":"integer","title":"Total"},"count":{"type":"integer","title":"Count"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"candidates":{"items":{"$ref":"#/components/schemas/cbelections__CandidateRow"},"type":"array","title":"Candidates"}},"additionalProperties":true,"type":"object","required":["total","count","limit","offset","candidates"],"title":"CandidatesPage"},"cbelections__ChannelJob":{"properties":{"role":{"type":"string","title":"Role","description":"Pipeline role, e.g. results_baseline | results_burst | markets_baseline | status_file | bigboard | ev_roster_autofill"},"job_id":{"type":"string","title":"Job Id","description":"cbcron job UUID"}},"type":"object","required":["role","job_id"],"title":"ChannelJob","description":"One cbcron job that feeds this channel, by its role in the pipeline."},"cbelections__ChannelJobsResponse":{"properties":{"channel":{"type":"string","title":"Channel"},"count":{"type":"integer","title":"Count"},"jobs":{"items":{"$ref":"#/components/schemas/cbelections__ChannelJob"},"type":"array","title":"Jobs"}},"type":"object","required":["channel","count"],"title":"ChannelJobsResponse","description":"The channel→cbcron mapping, straight from the channel YAML.\n\nDeliberately just the mapping: this service knows WHICH jobs feed a channel\n(its own config records them at registration time) but not how they are\ndoing. Schedules, enablement and run history belong to cbcron, and the\nconsumer composes the two — no service reaches into another's lane."},"cbelections__ChannelSummary":{"properties":{"id":{"type":"string","title":"Id","description":"The addressable channel id — pass THIS as {channel} in every other elections endpoint. It is the config file's stem, which is what channel lookup resolves."},"slug":{"type":"string","title":"Slug","description":"Derived state-cycle-kind label, which the other endpoints echo back as their 'channel' field. NOT unique and NOT addressable: 'ga-2026-runoff-brief' and 'ga-2026-runoff' both derive 'ga-2026-runoff', and 'dc-2026' derives 'dc-2026-general', which matches no config. Use `id` to address a channel; treat `slug` as a display label only."},"state":{"type":"string","title":"State"},"state_name":{"type":"string","title":"State Name"},"cycle":{"type":"integer","title":"Cycle"},"election_kind":{"type":"string","title":"Election Kind","description":"primary | general | runoff | special"},"election_date":{"type":"string","title":"Election Date","description":"Local election date, YYYY-MM-DD"},"polls_close_utc":{"type":"string","title":"Polls Close Utc"},"phase":{"type":"string","title":"Phase","description":"Where the channel sits in its own clock right now: 'pre' (before polls close) · 'burst' (close-window, results arriving fast) · 'canvass' (slow certified updates) · 'closed' (past canvass)"},"vendor":{"type":"string","title":"Vendor","description":"Results-feed vendor, e.g. enhanced_voting"},"results_mode":{"type":"string","title":"Results Mode","description":"statewide | county_fanout"},"has_county_grain":{"type":"boolean","title":"Has County Grain","description":"Does this election report at county grain? THE field a board reads to decide on a county map/tracker/denominator before any county rows arrive (cbelections#92). False = no county grain, ever (a convention); True + empty counties = none reported YET. Neither results_mode (feed transport — a statewide TSV can be at county grain) nor counties_total (fanout-only ops number) means this."},"headline_races":{"type":"integer","title":"Headline Races","description":"Count of configured headline races"},"counties_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Counties Total","description":"County-fanout denominator (None for statewide feeds)"},"counties_sourced":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Counties Sourced","description":"Counties registered to a real vehicle (ops number)"},"has_markets":{"type":"boolean","title":"Has Markets","description":"Channel has a prediction-markets layer"},"has_datasources":{"type":"boolean","title":"Has Datasources","description":"A per-county datasource-health file exists for this channel"}},"type":"object","required":["id","slug","state","state_name","cycle","election_kind","election_date","polls_close_utc","phase","vendor","results_mode","has_county_grain","headline_races","has_markets","has_datasources"],"title":"ChannelSummary","description":"One election channel, as an operator needs to see it in a list.\n\nIdentity + feed shape + where it sits in its own clock. Deliberately does\nNOT open the workspace DB: this has to stay cheap enough to render a\ndirectory of every channel, and a dormant channel's DB may not exist yet."},"cbelections__ChannelsResponse":{"properties":{"count":{"type":"integer","title":"Count"},"channels":{"items":{"$ref":"#/components/schemas/cbelections__ChannelSummary"},"type":"array","title":"Channels"}},"type":"object","required":["count"],"title":"ChannelsResponse","description":"The channel directory — every election this engine knows about.\n\nThe YAML files are the registry, so this is a directory scan; a file that\nis not a channel config is filtered out by failing to load, not by name.\nEntries are unique and sorted by ``id`` — see ``ChannelSummary.slug`` for\nwhy the id, not the slug, is the key."},"cbelections__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbelections__ContactEnvelope":{"properties":{"contact":{"$ref":"#/components/schemas/cbelections__ContactPoint"},"newly_callable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Newly Callable","description":"true when this write made the candidate callable: no phone or email a rep may use before it, one after. The same test /states counts with_contact by. False for a row on someone already callable, and for a mailing address or a website, which reach nobody by phone or email"}},"additionalProperties":true,"type":"object","required":["contact"],"title":"ContactEnvelope"},"cbelections__ContactPoint":{"properties":{"contact_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Id"},"candidate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Id"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"},"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value"},"source_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Class"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"found_by_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Found By Unit","description":"NULL when a human supplied it (operator)"},"found_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Found At"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"},"verified_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verified At"},"superseded_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Superseded By"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"use_restriction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Use Restriction","description":"what the SOURCE'S licence permits — NULL means unrestricted; a value means publishable but not callable"},"contact_grain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Grain","description":"person | organisation — whether the value reaches a human or a switchboard. NULL = unclassified"},"deleted":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Deleted"}},"additionalProperties":true,"type":"object","title":"ContactPoint"},"cbelections__ContactsResponse":{"properties":{"channel":{"type":"string","title":"Channel"},"state":{"type":"string","title":"State"},"count":{"type":"integer","title":"Count"},"counties":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Counties"}},"type":"object","required":["channel","state","count","counties"],"title":"ContactsResponse"},"cbelections__ContestSummary":{"properties":{"office_code":{"type":"string","title":"Office Code"},"party":{"type":"string","title":"Party"},"label":{"type":"string","title":"Label"},"candidate_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Candidate Count"},"elects":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Elects","description":"How many people this seat FILLS -- distinct from `candidate_count`, how many are RUNNING. NULL where the source never said; never guessed, because a default of 1 renders a two-seat board as a solved race."},"uncontested":{"type":"boolean","title":"Uncontested","description":"No more are running than the seat fills, so nobody can be beaten and NO VOTE WILL EVER BE REPORTED for this contest. A consumer must render this as a walkover, not as a race awaiting numbers: on 2026-08-22 five of seven convention seats sat at 'no count reported yet' all night, promising numbers that did not exist. False when `elects` is unknown -- absence of proof, not proof of a contest.","default":false}},"type":"object","required":["office_code","party","label"],"title":"ContestSummary","description":"Headline contest descriptor for the picker."},"cbelections__ContestUnit":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"County map key (mapFeatureId); null for the statewide unit"},"precincts_reporting":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Precincts Reporting"},"precincts_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Precincts Total"},"ballots_cast":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ballots Cast","description":"Contest vote total for this unit (sum of candidate votes)"},"votes":{"additionalProperties":{"type":"integer"},"type":"object","title":"Votes","description":"candidate key → vote count"},"shares":{"additionalProperties":{"type":"number"},"type":"object","title":"Shares","description":"candidate key → REPORTED vote share, for sources that publish a share and not a count: a convention floor, a caucus, a ranked-choice round. EMPTY when the source publishes counts — consumers derive from `votes` exactly as before. NEVER BOTH: a share here means the count is genuinely unknown, not zero."},"votes_by_method":{"additionalProperties":{"additionalProperties":{"type":"integer"},"type":"object"},"type":"object","title":"Votes By Method","description":"candidate key → {vote method → vote count} (Election Day / Advance Voting / Absentee by Mail / Provisional). Empty when the snapshot predates vote-method capture. Method votes sum to the candidate's total."}},"type":"object","title":"ContestUnit","description":"A statewide rollup or one county's tally for the selected contest."},"cbelections__ContestsResponse":{"properties":{"channel":{"type":"string","title":"Channel"},"version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Version","description":"Current snapshot version, if any"},"contests":{"items":{"$ref":"#/components/schemas/cbelections__ContestSummary"},"type":"array","title":"Contests"}},"type":"object","required":["channel","contests"],"title":"ContestsResponse"},"cbelections__CountiesResponse":{"properties":{"cycle":{"type":"integer","title":"Cycle"},"count":{"type":"integer","title":"Count"},"counties":{"items":{"$ref":"#/components/schemas/cbelections__CountyRow"},"type":"array","title":"Counties"}},"additionalProperties":true,"type":"object","required":["cycle","count","counties"],"title":"CountiesResponse"},"cbelections__CountyDatasource":{"properties":{"county":{"type":"string","title":"County"},"fips":{"type":"string","title":"Fips"},"vehicle":{"type":"string","title":"Vehicle","description":"How results are obtained: enhanced_voting | clarity | pdf | election_archive | pending"},"status":{"type":"string","title":"Status","description":"live (machine-readable feed resolved) · no_eid (vendor known, election id not published yet) · manual_pdf (human-readable only) · unresolved (no vehicle identified)"},"eid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Eid","description":"Vendor election id, once published"},"items":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Items","description":"Ballot items seen in the payload"},"state":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"State","description":"State-level contests seen"},"delegates":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Delegates"},"local_contests":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Local Contests"},"judicial":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Judicial"},"proposals":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Proposals"},"unparsed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Unparsed","description":"Items the parser could not classify"},"candidates_loaded":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Candidates Loaded"}},"type":"object","required":["county","fips","vehicle","status"],"title":"CountyDatasource","description":"One county's ingest vehicle and how far it has actually got.\n\nThe count fields are only populated once a county's payload has been\nparsed — a county at ``no_eid`` or ``unresolved`` carries identity and\nnothing else, and those stay None rather than 0 so \"not reached yet\" is\ndistinguishable from \"reached, found nothing\"."},"cbelections__CountyFeedCandidate":{"properties":{"name":{"type":"string","title":"Name"},"votes":{"type":"integer","title":"Votes","default":0}},"additionalProperties":true,"type":"object","required":["name"],"title":"CountyFeedCandidate"},"cbelections__CountyFeedContest":{"properties":{"contest_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contest Name"},"office_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office Code"},"party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party"},"precincts_reporting":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Precincts Reporting"},"precincts_total":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Precincts Total"},"candidates":{"items":{"$ref":"#/components/schemas/cbelections__CountyFeedCandidate"},"type":"array","title":"Candidates"}},"additionalProperties":true,"type":"object","title":"CountyFeedContest"},"cbelections__CountyFeedCounty":{"properties":{"county":{"type":"string","title":"County"},"eid":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Eid"},"clarity_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Clarity Version"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"'ap-reference' on the reference feed — consumers must keep the AP label on the glass"},"fetched_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fetched At"},"contests":{"items":{"$ref":"#/components/schemas/cbelections__CountyFeedContest"},"type":"array","title":"Contests"}},"additionalProperties":true,"type":"object","required":["county"],"title":"CountyFeedCounty","description":"One county in the shared county-major feed shape — served identically\nby /clarity (official Clarity rows) and /reference (UNOFFICIAL AP rows,\ntagged source: ap-reference), so one adapter reads both."},"cbelections__CountyFeedResponse":{"properties":{"channel":{"type":"string","title":"Channel"},"count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Count"},"snapshot":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snapshot","description":"reference feed only: the archived file served"},"counties":{"items":{"$ref":"#/components/schemas/cbelections__CountyFeedCounty"},"type":"array","title":"Counties"}},"additionalProperties":true,"type":"object","required":["channel"],"title":"CountyFeedResponse"},"cbelections__CountyRow":{"properties":{"state":{"type":"string","title":"State"},"county":{"type":"string","title":"County"},"offices":{"type":"integer","title":"Offices","default":0},"candidates":{"type":"integer","title":"Candidates","default":0}},"additionalProperties":true,"type":"object","required":["state","county"],"title":"CountyRow"},"cbelections__DatasourceStatusBucket":{"properties":{"counties":{"type":"integer","title":"Counties"},"reg_voters":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reg Voters"},"share":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Share","description":"Fraction of statewide registered voters, 0..1"}},"type":"object","required":["counties"],"title":"DatasourceStatusBucket","description":"Counties in one status, with their registered-voter weight.\n\nVoter weight is the number that matters on the night: 19 unresolved rural\ncounties and one unresolved Wayne County are not the same risk."},"cbelections__DatasourcesResponse":{"properties":{"channel":{"type":"string","title":"Channel"},"available":{"type":"boolean","title":"Available","description":"False when this channel has no per-county datasource layer"},"generated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated At","description":"When the roster-autofill cron last wrote the file"},"election_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Date"},"counties_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Counties Total"},"live_share":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Live Share","description":"Share of REGISTERED VOTERS on a live machine-readable feed"},"candidates_loaded":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Candidates Loaded"},"by_status":{"additionalProperties":{"$ref":"#/components/schemas/cbelections__DatasourceStatusBucket"},"type":"object","title":"By Status"},"counties":{"items":{"$ref":"#/components/schemas/cbelections__CountyDatasource"},"type":"array","title":"Counties"}},"type":"object","required":["channel","available"],"title":"DatasourcesResponse","description":"Per-county datasource health for a county-fanout channel.\n\nWhy this exists as its own endpoint: for a statewide feed \"is ingest up?\"\nis one boolean, but Michigan assembles results from 83 independent county\nvehicles, so readiness is a distribution — and the honest headline is\n``live_share`` by REGISTERED VOTERS, not by county count.\n\nReturns 200 with ``available: false`` and empty counties when the channel\nhas no fanout layer or the file has not been generated, so a dashboard can\nrender \"no datasource layer\" without special-casing a 404."},"cbelections__DispatchInput":{"properties":{"action":{"type":"string","title":"Action"},"office_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office Code"},"party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party"},"channel":{"type":"string","title":"Channel","default":"mi-2026-primary"}},"type":"object","required":["action"],"title":"DispatchInput","description":"The model's tool input, forwarded verbatim by cbai."},"cbelections__ElectionDate":{"properties":{"state":{"type":"string","title":"State"},"cycle":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cycle"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type","description":"FEC type id — see election_types in /calendar"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office"},"election_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Date"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"lur_window_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lur Window Days"},"lur_window_opens":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lur Window Opens","description":"when the lowest-unit-charge window opens (47 U.S.C. 315(b))"},"is_special":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Special"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"first_seen_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Seen At"},"last_synced_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Synced At"},"revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Revision"},"previous_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous Date","description":"set when a synced row moved its date"}},"additionalProperties":true,"type":"object","required":["state"],"title":"ElectionDate","description":"One calendar row, as synced from the FEC's published dates."},"cbelections__EtaResponse":{"properties":{"channel":{"type":"string","title":"Channel"},"close_utc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Close Utc"},"model":{"additionalProperties":true,"type":"object","title":"Model"}},"type":"object","required":["channel","close_utc","model"],"title":"EtaResponse"},"cbelections__FanOutResponse":{"properties":{"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"NULL when the pass selected across the whole store"},"level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Level"},"dry_run":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Dry Run"},"skipped":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skipped","description":"'excluded' when the state is stood down"},"targets":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Targets"},"by_state":{"additionalProperties":{"type":"integer"},"type":"object","title":"By State","description":"where the batch actually went"},"dispatched":{"type":"integer","title":"Dispatched","default":0},"failed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Failed"},"units":{"items":{"$ref":"#/components/schemas/cbelections__FanOutUnit"},"type":"array","title":"Units"}},"additionalProperties":true,"type":"object","title":"FanOutResponse"},"cbelections__FanOutUnit":{"properties":{"unit_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit Id"},"candidate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"additionalProperties":true,"type":"object","title":"FanOutUnit"},"cbelections__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbelections__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbelections__HeadlineRace":{"properties":{"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office"},"party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"}},"additionalProperties":true,"type":"object","title":"HeadlineRace"},"cbelections__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbelections__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbelections__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbelections__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbelections__LadderTier":{"properties":{"tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tier"},"level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Level"},"expected_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Count"},"approximate":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Approximate"},"minimum":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Minimum"},"raw_count":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Count"},"offices":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Offices"},"compound_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Compound With"},"offices_covered":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Offices Covered"},"candidates":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Candidates"},"gap":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Gap","description":"expected minus actual for the tier. NULL means the skeleton never carried an expected count — a priming gap, which must read as unknown rather than as zero."},"over_expected":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Over Expected"},"level_tier_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Level Tier Count"}},"additionalProperties":true,"type":"object","title":"LadderTier"},"cbelections__LevelCount":{"properties":{"candidates":{"type":"integer","title":"Candidates","default":0},"offices":{"type":"integer","title":"Offices","default":0}},"additionalProperties":true,"type":"object","title":"LevelCount"},"cbelections__ManualEntry":{"properties":{"office_code":{"type":"string","title":"Office Code"},"party":{"type":"string","title":"Party"},"county":{"type":"string","title":"County"},"votes":{"additionalProperties":{"type":"integer"},"type":"object","title":"Votes","description":"candidate display name → vote count"},"precincts_reporting":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Precincts Reporting"},"precincts_total":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Precincts Total"},"entered_by":{"type":"string","title":"Entered By"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"retract":{"type":"boolean","title":"Retract","description":"True hides this (office_code, party, county) again — a mis-entry is corrected by superseding, never by editing history","default":false},"id":{"type":"string","title":"Id"},"at":{"type":"string","title":"At","description":"ISO-8601 UTC server receive time"}},"type":"object","required":["office_code","party","county","entered_by","id","at"],"title":"ManualEntry"},"cbelections__ManualEntryIn":{"properties":{"office_code":{"type":"string","title":"Office Code"},"party":{"type":"string","title":"Party"},"county":{"type":"string","title":"County"},"votes":{"additionalProperties":{"type":"integer"},"type":"object","title":"Votes","description":"candidate display name → vote count"},"precincts_reporting":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Precincts Reporting"},"precincts_total":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Precincts Total"},"entered_by":{"type":"string","title":"Entered By"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"retract":{"type":"boolean","title":"Retract","description":"True hides this (office_code, party, county) again — a mis-entry is corrected by superseding, never by editing history","default":false}},"type":"object","required":["office_code","party","county","entered_by"],"title":"ManualEntryIn","description":"One called-in result, as typed by office staff (cbelections#24)."},"cbelections__ManualResponse":{"properties":{"channel":{"type":"string","title":"Channel"},"count":{"type":"integer","title":"Count"},"entries":{"items":{"$ref":"#/components/schemas/cbelections__ManualEntry"},"type":"array","title":"Entries"}},"type":"object","required":["channel","count","entries"],"title":"ManualResponse"},"cbelections__MarketOutcome":{"properties":{"candidate":{"type":"string","title":"Candidate","description":"Outcome label (candidate or party)"},"party":{"type":"string","title":"Party","description":"Party short code derived from the outcome: R|D|I"},"implied_prob":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Implied Prob","description":"Implied win probability, 0..1 (last trade or bid/ask midpoint)"},"volume":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Volume","description":"Contract volume traded"}},"type":"object","required":["candidate","party"],"title":"MarketOutcome","description":"One tradeable outcome in a prediction market — a candidate or party\nwinning the race — with its current implied probability."},"cbelections__MarketRace":{"properties":{"series_ticker":{"type":"string","title":"Series Ticker"},"race":{"type":"string","title":"Race","description":"Human race label, e.g. 'U.S. SENATE (Nov)'"},"office_code":{"type":"string","title":"Office Code","description":"Win-market office code, e.g. USS-WIN"},"outcomes":{"items":{"$ref":"#/components/schemas/cbelections__MarketOutcome"},"type":"array","title":"Outcomes"}},"type":"object","required":["series_ticker","race","office_code","outcomes"],"title":"MarketRace","description":"All outcomes for one race's prediction market (one Kalshi series)."},"cbelections__MarketsResponse":{"properties":{"channel":{"type":"string","title":"Channel"},"vendor":{"type":"string","title":"Vendor","description":"Prediction-market source","default":"kalshi"},"as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of","description":"ISO-8601 UTC time of the latest fetch"},"races":{"items":{"$ref":"#/components/schemas/cbelections__MarketRace"},"type":"array","title":"Races"}},"type":"object","required":["channel"],"title":"MarketsResponse","description":"Prediction-market implied odds for a channel's configured races.\n\nA **distinct race-outcome lens** (typically the general-election winner),\nseparate from the per-county actuals — surfaced by the Election Night Loom\nkiosk as its own 'Prediction Markets' layer. Empty ``races`` when the\nchannel has no markets configured or none ingested yet (200, not 404, so\nconsumers degrade gracefully)."},"cbelections__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbelections__Outcome":{"properties":{"outcome_id":{"type":"string","title":"Outcome Id"},"candidate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Id"},"channel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel"},"disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disposition"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"callback_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callback At","description":"set iff disposition = callback — the due-queue key"},"crm_person_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crm Person Id"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"additionalProperties":true,"type":"object","required":["outcome_id"],"title":"Outcome"},"cbelections__OutcomeEnvelope":{"properties":{"outcome":{"$ref":"#/components/schemas/cbelections__Outcome"}},"additionalProperties":true,"type":"object","required":["outcome"],"title":"OutcomeEnvelope"},"cbelections__OutcomesResponse":{"properties":{"outcomes":{"items":{"$ref":"#/components/schemas/cbelections__Outcome"},"type":"array","title":"Outcomes","description":"newest first — the history"}},"additionalProperties":true,"type":"object","required":["outcomes"],"title":"OutcomesResponse"},"cbelections__PipelineJob":{"properties":{"role":{"type":"string","title":"Role","description":"Pipeline role from the channel YAML's cbcron_jobs"},"job_id":{"type":"string","title":"Job Id"},"missing":{"type":"boolean","title":"Missing","description":"The channel config names a job id cbcron does not have","default":false},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"schedule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Schedule","description":"Cron expression; None for one-shots"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group","description":"cbcron serialization group (overlap guard)"},"next_run_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Run At"},"last_run":{"anyOf":[{"$ref":"#/components/schemas/cbelections__PipelineLastRun"},{"type":"null"}]}},"type":"object","required":["role","job_id"],"title":"PipelineJob"},"cbelections__PipelineLastRun":{"properties":{"status":{"type":"string","title":"Status"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["status"],"title":"PipelineLastRun","description":"The newest *finished* run — an in-flight one is deliberately skipped\n(an every-5-minute job is mid-run at most instants, and 'pending' would\nread as a failure in the Control app's health filter)."},"cbelections__PipelineResponse":{"properties":{"channel":{"type":"string","title":"Channel"},"count":{"type":"integer","title":"Count"},"jobs":{"items":{"$ref":"#/components/schemas/cbelections__PipelineJob"},"type":"array","title":"Jobs"}},"type":"object","required":["channel","count","jobs"],"title":"PipelineResponse"},"cbelections__PolledUnit":{"properties":{"unit_id":{"type":"string","title":"Unit Id"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"additionalProperties":true,"type":"object","required":["unit_id"],"title":"PolledUnit"},"cbelections__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbelections__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbelections__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbelections__ProfileEntry":{"properties":{"raw":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw"},"value":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Value"},"approximate":{"type":"boolean","title":"Approximate","default":false}},"additionalProperties":true,"type":"object","title":"ProfileEntry"},"cbelections__RaceConfig":{"properties":{"code":{"type":"string","title":"Code","description":"2-letter state code, the registry key"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"cycle":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cycle"},"fips":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fips"},"catalog":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Catalog","description":"the federation catalog the boards read"},"channel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel","description":"the cbcast channel boards are cast to"},"bellwether_county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bellwether County"},"source_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Label"},"headline_races":{"items":{"$ref":"#/components/schemas/cbelections__HeadlineRace"},"type":"array","title":"Headline Races"},"deck":{"anyOf":[{"additionalProperties":true,"type":"object"},{"items":{},"type":"array"},{"type":"null"}],"title":"Deck"}},"additionalProperties":true,"type":"object","required":["code"],"title":"RaceConfig","description":"One registered race — a StateConfig, projected flat (cbelections#109)."},"cbelections__RaceRow":{"properties":{"race_key":{"type":"string","title":"Race Key"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"office_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office Code"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office"},"district":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"District"},"level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Level"},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage"},"stage_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage Date"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"seat_ambiguous":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Seat Ambiguous"},"jurisdiction_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jurisdiction Id"},"field_size":{"type":"integer","title":"Field Size","description":"who is actually running — candidates their source has established are off the ballot do not count; a three-way general reads 3 even when six historical rows come back"},"matching":{"type":"integer","title":"Matching","description":"rows this response carries for the race"},"concluded":{"type":"boolean","title":"Concluded","description":"field_size == 0 — a decided race OR a seat not up this cycle; the flag cannot tell those apart"},"with_contact":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"With Contact"},"candidates":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbelections__CandidateRow"},"type":"array"},{"type":"null"}],"title":"Candidates","description":"the whole historical roster, not the current field — absent when expand=false"}},"additionalProperties":true,"type":"object","required":["race_key","field_size","matching","concluded"],"title":"RaceRow"},"cbelections__RacesPage":{"properties":{"total":{"type":"integer","title":"Total"},"count":{"type":"integer","title":"Count"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"races":{"items":{"$ref":"#/components/schemas/cbelections__RaceRow"},"type":"array","title":"Races"}},"additionalProperties":true,"type":"object","required":["total","count","limit","offset","races"],"title":"RacesPage"},"cbelections__RacesResponse":{"properties":{"count":{"type":"integer","title":"Count"},"races":{"items":{"$ref":"#/components/schemas/cbelections__RaceConfig"},"type":"array","title":"Races"}},"additionalProperties":true,"type":"object","required":["count","races"],"title":"RacesResponse"},"cbelections__ReadAck":{"properties":{"unit_id":{"type":"string","title":"Unit Id"},"hits":{"type":"integer","title":"Hits","description":"contact points written from this unit"}},"additionalProperties":true,"type":"object","required":["unit_id","hits"],"title":"ReadAck"},"cbelections__ReconcileResponse":{"properties":{"open":{"type":"integer","title":"Open","description":"open units with a job behind them, store-wide"},"polled":{"type":"integer","title":"Polled"},"offset":{"type":"integer","title":"Offset"},"remaining":{"type":"integer","title":"Remaining","description":"open units past this chunk — pass back as offset to walk on"},"outcome":{"additionalProperties":{"type":"integer"},"type":"object","title":"Outcome","description":"status -> count for the polled batch"},"units":{"items":{"$ref":"#/components/schemas/cbelections__PolledUnit"},"type":"array","title":"Units"}},"additionalProperties":true,"type":"object","required":["open","polled","offset","remaining"],"title":"ReconcileResponse"},"cbelections__ResultsMatrix":{"properties":{"channel":{"type":"string","title":"Channel"},"office_code":{"type":"string","title":"Office Code"},"party":{"type":"string","title":"Party"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"elects":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Elects","description":"How many this seat FILLS. See ContestSummary.elects."},"uncontested":{"type":"boolean","title":"Uncontested","description":"No vote will ever be reported. See ContestSummary.","default":false},"level":{"type":"string","title":"Level","description":"'county' (default) or 'statewide'"},"version":{"type":"integer","title":"Version","description":"Snapshot version these actuals come from"},"as_of":{"type":"string","title":"As Of","description":"ISO-8601 UTC fetch time of the version"},"candidates":{"items":{"$ref":"#/components/schemas/cbelections__Candidate"},"type":"array","title":"Candidates"},"methods":{"items":{"type":"string"},"type":"array","title":"Methods","description":"Vote methods present in this snapshot, in canonical order. Empty when vote-method data hasn't been captured for the version."},"counties_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Counties Total","description":"Every county in the state — the honest completeness denominator for a county-fanout channel (Michigan: 83). None for a statewide-feed channel, where the feed returns all counties at once and len(counties) is already correct. Consumers must NOT derive this from the number of county rows present: for a federated ingest the numerator and denominator then grow together and the board reads 100% from the first county in (cbelections#16)."},"counties_sourced":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Counties Sourced","description":"How many counties are registered to a real results vehicle. An OPS number, not a board denominator — it moves as reconciliation lands, so using it would make resolving a county look like the board going backwards."},"counties_reporting":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Counties Reporting","description":"Counties with any votes or precincts in for THIS contest — the county-progress numerator, explicitly named (cbelections#22). Pair it with counties_in_scope, never with counties_total directly."},"counties_in_scope":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Counties In Scope","description":"The county-progress denominator for THIS contest: the district's county count for a district office (scope='district'), the registry's for a statewide one (scope='statewide'). None for a statewide-feed channel. This is what fixes MSH-101 reading 1/83 — a five-county district must be counted against 5 (cbelections#22)."},"scope":{"type":"string","title":"Scope","description":"Which denominator applies: 'district' when counties_in_scope is the district's own county count, else 'statewide'.","default":"statewide"},"methods_total":{"additionalProperties":{"type":"integer"},"type":"object","title":"Methods Total","description":"Statewide votes by method, summed across ALL candidates — the 'whole breakdown' for an aggregate donut/pie. Keyed by method name; empty when no vote-method data. Per-candidate splits are on statewide.votes_by_method."},"statewide":{"$ref":"#/components/schemas/cbelections__ContestUnit"},"counties":{"items":{"$ref":"#/components/schemas/cbelections__ContestUnit"},"type":"array","title":"Counties"}},"type":"object","required":["channel","office_code","party","level","version","as_of","candidates","statewide"],"title":"ResultsMatrix","description":"The candidates×counties matrix for one contest at one version."},"cbelections__ScenarioDetail":{"properties":{"id":{"type":"string","title":"Id","description":"Scenario id (filename stem)"},"races":{"additionalProperties":{"type":"string"},"type":"object","title":"Races","description":"race key → finish margin, e.g. {'GOV-R': '+3.0'}"},"holds":{"additionalProperties":{"type":"number"},"type":"object","title":"Holds","description":"county → late-dump reporting fraction"},"valid":{"type":"boolean","title":"Valid","description":"parses against the channel's races","default":true},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"parse error when valid=false"},"yaml":{"type":"string","title":"Yaml","description":"raw scenario YAML"}},"type":"object","required":["id","yaml"],"title":"ScenarioDetail","description":"A scenario plus its raw YAML body."},"cbelections__ScenarioSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Scenario id (filename stem)"},"races":{"additionalProperties":{"type":"string"},"type":"object","title":"Races","description":"race key → finish margin, e.g. {'GOV-R': '+3.0'}"},"holds":{"additionalProperties":{"type":"number"},"type":"object","title":"Holds","description":"county → late-dump reporting fraction"},"valid":{"type":"boolean","title":"Valid","description":"parses against the channel's races","default":true},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"parse error when valid=false"}},"type":"object","required":["id"],"title":"ScenarioSummary","description":"A scenario at a glance — enough for a list/picker."},"cbelections__ScenarioWrite":{"properties":{"id":{"type":"string","title":"Id","description":"lowercase slug; the filename stem"},"yaml":{"type":"string","title":"Yaml","description":"scenario YAML body"}},"type":"object","required":["id","yaml"],"title":"ScenarioWrite","description":"Create/replace a scenario."},"cbelections__SimResult":{"properties":{"ok":{"type":"boolean","title":"Ok"},"action":{"type":"string","title":"Action"},"scenario_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scenario Id"},"report_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Report Pct"},"returncode":{"type":"integer","title":"Returncode"},"output":{"type":"string","title":"Output","description":"tail of the runner's stdout/stderr"}},"type":"object","required":["ok","action","returncode","output"],"title":"SimResult","description":"Outcome of a sim action."},"cbelections__SimRunRequest":{"properties":{"scenario_id":{"type":"string","title":"Scenario Id","description":"which stored scenario to run"},"report_pct":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Report Pct","description":"reporting level to scrub to — a fraction [0..1] OR a percent (1..100]. Values >1 are treated as a percent and divided by 100 (Loom's Sim app sends a 0-100 slider).","default":1.0},"force":{"type":"boolean","title":"Force","description":"override the close-window time-gate (apply/reset only)","default":false}},"type":"object","required":["scenario_id"],"title":"SimRunRequest","description":"Run one of the sim actions against a scenario."},"cbelections__StateDelta":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity"},"breaks":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Breaks"},"resolved":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved"},"read_on":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Read On"}},"additionalProperties":true,"type":"object","title":"StateDelta"},"cbelections__StateDetail":{"properties":{"state":{"type":"string","title":"State"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"fips":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fips"},"cycle":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cycle"},"primed":{"type":"boolean","title":"Primed","default":false},"primed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primed At"},"primed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primed By"},"primed_from":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primed From"},"ladder":{"items":{"$ref":"#/components/schemas/cbelections__LadderTier"},"type":"array","title":"Ladder"},"profile":{"additionalProperties":{"$ref":"#/components/schemas/cbelections__ProfileEntry"},"type":"object","title":"Profile"},"deltas":{"items":{"$ref":"#/components/schemas/cbelections__StateDelta"},"type":"array","title":"Deltas"},"sources":{"items":{"$ref":"#/components/schemas/cbelections__StateSource"},"type":"array","title":"Sources"},"candidates":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Candidates"},"offices":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Offices"},"by_level":{"additionalProperties":{"$ref":"#/components/schemas/cbelections__LevelCount"},"type":"object","title":"By Level"}},"additionalProperties":true,"type":"object","required":["state"],"title":"StateDetail"},"cbelections__StateRow":{"properties":{"state":{"type":"string","title":"State"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"fips":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fips"},"primed":{"type":"boolean","title":"Primed","default":false},"primed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primed At"},"tiers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tiers"},"expected_offices":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Offices"},"offices":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Offices"},"office_coverage":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Office Coverage"},"candidates":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Candidates"},"with_contact":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"With Contact"},"contact_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Contact Rate"},"party_posts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Party Posts"},"contact_rate_ex_party_posts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Contact Rate Ex Party Posts"},"by_level":{"additionalProperties":{"$ref":"#/components/schemas/cbelections__LevelCount"},"type":"object","title":"By Level"},"profile":{"additionalProperties":{"$ref":"#/components/schemas/cbelections__ProfileEntry"},"type":"object","title":"Profile"},"general_election":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"General Election"}},"additionalProperties":true,"type":"object","required":["state"],"title":"StateRow"},"cbelections__StateSource":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"read_on":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Read On"}},"additionalProperties":true,"type":"object","title":"StateSource"},"cbelections__StatesResponse":{"properties":{"cycle":{"type":"integer","title":"Cycle"},"count":{"type":"integer","title":"Count"},"primed":{"type":"integer","title":"Primed"},"states":{"items":{"$ref":"#/components/schemas/cbelections__StateRow"},"type":"array","title":"States"}},"additionalProperties":true,"type":"object","required":["cycle","count","primed","states"],"title":"StatesResponse"},"cbelections__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbelections__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbelections__SweepResponse":{"properties":{"cutoff":{"type":"string","title":"Cutoff"},"older_than_hours":{"type":"integer","title":"Older Than Hours"},"dry_run":{"type":"boolean","title":"Dry Run"},"closed":{"type":"integer","title":"Closed"},"units":{"items":{"$ref":"#/components/schemas/cbelections__SweptUnit"},"type":"array","title":"Units"}},"additionalProperties":true,"type":"object","required":["cutoff","older_than_hours","dry_run","closed"],"title":"SweepResponse"},"cbelections__SweptUnit":{"properties":{"unit_id":{"type":"string","title":"Unit Id"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"},"dispatched_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dispatched At"},"rows_attributed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows Attributed","description":"roster rows that landed before the process died — any write this unit made is PARTIAL"}},"additionalProperties":true,"type":"object","required":["unit_id"],"title":"SweptUnit"},"cbelections__ToolDescriptor":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"input_schema":{"additionalProperties":true,"type":"object","title":"Input Schema","description":"JSON Schema for the tool's input — actions and their params"}},"additionalProperties":true,"type":"object","required":["name","description","input_schema"],"title":"ToolDescriptor","description":"An Anthropic tool descriptor, as cbai consumes it at catalog resolution."},"cbelections__UnitEnvelope":{"properties":{"unit":{"$ref":"#/components/schemas/cbelections__WorkUnit"}},"additionalProperties":true,"type":"object","required":["unit"],"title":"UnitEnvelope"},"cbelections__UnitsPage":{"properties":{"total":{"type":"integer","title":"Total"},"count":{"type":"integer","title":"Count"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"units":{"items":{"$ref":"#/components/schemas/cbelections__WorkUnit"},"type":"array","title":"Units"}},"additionalProperties":true,"type":"object","required":["total","count","limit","offset","units"],"title":"UnitsPage"},"cbelections__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbelections__VocabularyResponse":{"properties":{"levels":{"items":{"type":"string"},"type":"array","title":"Levels"},"stages":{"items":{"type":"string"},"type":"array","title":"Stages"},"party_groups":{"items":{"type":"string"},"type":"array","title":"Party Groups"},"party_group_codes":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Party Group Codes"},"contact_kinds":{"items":{"type":"string"},"type":"array","title":"Contact Kinds"},"source_classes":{"items":{"type":"string"},"type":"array","title":"Source Classes"},"unit_kinds":{"items":{"type":"string"},"type":"array","title":"Unit Kinds"},"unit_statuses":{"items":{"type":"string"},"type":"array","title":"Unit Statuses"},"callable_kinds":{"items":{"type":"string"},"type":"array","title":"Callable Kinds"},"use_restrictions":{"items":{"type":"string"},"type":"array","title":"Use Restrictions","description":"What a source's licence permits. NULL on a contact means unrestricted; a value means publishable but not callable."},"outcome_channels":{"items":{"type":"string"},"type":"array","title":"Outcome Channels"},"outcome_dispositions":{"items":{"type":"string"},"type":"array","title":"Outcome Dispositions"}},"additionalProperties":true,"type":"object","required":["levels","stages","party_groups","party_group_codes","contact_kinds","source_classes","unit_kinds","unit_statuses","callable_kinds","use_restrictions","outcome_channels","outcome_dispositions"],"title":"VocabularyResponse","description":"Every enumerated value in the contract, in one place."},"cbelections__WorkUnit":{"properties":{"unit_id":{"type":"string","title":"Unit Id"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"},"scope_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope State"},"scope_county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope County"},"scope_jurisdiction_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope Jurisdiction Id"},"scope_candidate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope Candidate Id"},"cycle":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cycle"},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage"},"source_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Class"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Id"},"request":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Request"},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result","description":"the adapter's own stats block — shape varies per source"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"attempts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Attempts"},"hits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hits"},"dispatched_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dispatched At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"},"dispatched_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dispatched By"},"retry_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Retry Of"},"superseded_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Superseded By","description":"the retry that replaced this unit; NULL = never retried. Follow it rather than retrying this unit again: a second retry is refused with 409"},"deleted":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Deleted"},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning","description":"a unit that closed done, wrote its rows, and still went wrong — a separate axis from failure; the two never overlap"},"read_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Read At","description":"when the enrich extract read this unit's crawl; NULL = never read (the extract's resume ledger, #110)"}},"additionalProperties":true,"type":"object","required":["unit_id"],"title":"WorkUnit","description":"One dispatched unit — the cbelections/cbintel seam."},"cbelections__YieldResponse":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/cbelections__YieldRow"},"type":"array","title":"Rows"},"attempts":{"type":"integer","title":"Attempts","default":0},"hits":{"type":"integer","title":"Hits","default":0},"rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate"},"barren":{"type":"integer","title":"Barren","default":0},"unqueued":{"items":{"type":"string"},"type":"array","title":"Unqueued","description":"states holding roster rows with no unit ever dispatched — invisible to any grouping over units (#63)"}},"additionalProperties":true,"type":"object","required":["rows"],"title":"YieldResponse"},"cbelections__YieldRow":{"properties":{"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"},"source_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Class"},"units":{"type":"integer","title":"Units","default":0},"attempts":{"type":"integer","title":"Attempts","default":0},"hits":{"type":"integer","title":"Hits","default":0},"failed":{"type":"integer","title":"Failed","description":"the unit errored","default":0},"barren":{"type":"integer","title":"Barren","description":"completed, looked >= 25 times, found nothing — reads clean in failed alone (#63)","default":0},"rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate"}},"additionalProperties":true,"type":"object","title":"YieldRow"},"cbemail__Attachment":{"properties":{"filename":{"type":"string","title":"Filename","description":"Attachment filename"},"content_type":{"type":"string","title":"Content Type","description":"MIME content type"},"size":{"type":"integer","title":"Size","description":"Size in bytes"},"content_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Id","description":"Content-ID for inline images"}},"type":"object","required":["filename","content_type","size"],"title":"Attachment","description":"Email attachment information.","examples":[{"content_type":"application/pdf","filename":"report.pdf","size":245678},{"content_id":"logo001","content_type":"image/png","filename":"logo.png","size":12340}]},"cbemail__BatchEmailResult":{"properties":{"total":{"type":"integer","title":"Total","description":"Total number of emails"},"successful":{"type":"integer","title":"Successful","description":"Number of successfully sent emails"},"failed":{"type":"integer","title":"Failed","description":"Number of failed emails"},"results":{"items":{"$ref":"#/components/schemas/cbemail__EmailResult"},"type":"array","title":"Results","description":"Individual results"}},"type":"object","required":["total","successful","failed","results"],"title":"BatchEmailResult","description":"Result of sending multiple emails.","examples":[{"failed":1,"results":[{"message_id":"<MSG001@smtp.gmail.com>","success":true},{"message_id":"<MSG002@smtp.gmail.com>","success":true},{"error":"Mailbox quota exceeded","success":false}],"successful":2,"total":3}]},"cbemail__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbemail__EmailAttachment":{"properties":{"filename":{"type":"string","maxLength":255,"minLength":1,"title":"Filename","description":"Attachment filename"},"content":{"type":"string","title":"Content","description":"Base64-encoded file content"},"mime_type":{"type":"string","title":"Mime Type","description":"MIME type (e.g. application/pdf, audio/mpeg)","default":"application/octet-stream"}},"type":"object","required":["filename","content"],"title":"EmailAttachment","description":"File attachment for an email.","examples":[{"content":"JVBERi0xLjQK...","filename":"order_sheet.pdf","mime_type":"application/pdf"}]},"cbemail__EmailResult":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the email was sent successfully"},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Id","description":"Message-ID of sent email"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if sending failed"}},"type":"object","required":["success"],"title":"EmailResult","description":"Result of sending an email.","examples":[{"message_id":"<20240115093000.ABC123@smtp.gmail.com>","success":true},{"error":"Recipient address rejected: User unknown","success":false}]},"cbemail__Folder":{"properties":{"name":{"type":"string","title":"Name","description":"Folder name"},"delimiter":{"type":"string","title":"Delimiter","description":"Folder hierarchy delimiter"},"flags":{"items":{"type":"string"},"type":"array","title":"Flags","description":"Folder flags"},"message_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Message Count","description":"Number of messages"},"unread_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Unread Count","description":"Number of unread messages"}},"type":"object","required":["name","delimiter"],"title":"Folder","description":"Email folder information.","examples":[{"delimiter":"/","flags":["\\HasNoChildren"],"message_count":1542,"name":"INBOX","unread_count":23},{"delimiter":"/","flags":["\\HasNoChildren"],"message_count":856,"name":"Archive/2024","unread_count":0}]},"cbemail__FolderCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Folder name to create"}},"type":"object","required":["name"],"title":"FolderCreate","description":"Request model for creating a folder.","examples":[{"name":"Projects"},{"name":"Archive/2024"}]},"cbemail__FolderList":{"properties":{"folders":{"items":{"$ref":"#/components/schemas/cbemail__Folder"},"type":"array","title":"Folders","description":"List of folders"}},"type":"object","title":"FolderList","description":"List of folders response.","examples":[{"folders":[{"delimiter":"/","flags":["\\HasNoChildren"],"message_count":1542,"name":"INBOX","unread_count":23},{"delimiter":"/","flags":["\\HasNoChildren","\\Sent"],"message_count":3421,"name":"[Gmail]/Sent Mail","unread_count":0},{"delimiter":"/","flags":["\\HasNoChildren","\\Drafts"],"message_count":5,"name":"[Gmail]/Drafts","unread_count":0}]}]},"cbemail__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbemail__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbemail__HealthCheckResponse":{"properties":{"session_id":{"type":"string","title":"Session Id","description":"Session identifier"},"imap_connected":{"type":"boolean","title":"Imap Connected","description":"IMAP connection status"},"smtp_connected":{"type":"boolean","title":"Smtp Connected","description":"SMTP connection status"},"imap_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Imap Error","description":"IMAP error message if any"},"smtp_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Smtp Error","description":"SMTP error message if any"}},"type":"object","required":["session_id","imap_connected","smtp_connected"],"title":"HealthCheckResponse","description":"Health check response for a session.","examples":[{"imap_connected":true,"session_id":"550e8400-e29b-41d4-a716-446655440000","smtp_connected":true},{"imap_connected":true,"session_id":"550e8400-e29b-41d4-a716-446655440000","smtp_connected":false,"smtp_error":"Connection refused: SMTP server unreachable"}]},"cbemail__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbemail__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbemail__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbemail__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbemail__MarkAction":{"type":"string","enum":["read","unread","flagged","unflagged"],"title":"MarkAction","description":"Message mark actions."},"cbemail__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbemail__Message":{"properties":{"uid":{"type":"string","title":"Uid","description":"Message UID"},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Id","description":"Message-ID header"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Email subject"},"from_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Address","description":"From address"},"to_address":{"items":{"type":"string"},"type":"array","title":"To Address","description":"To addresses"},"cc_address":{"items":{"type":"string"},"type":"array","title":"Cc Address","description":"CC addresses"},"bcc_address":{"items":{"type":"string"},"type":"array","title":"Bcc Address","description":"BCC addresses"},"reply_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reply To","description":"Reply-To address"},"date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date","description":"Message date"},"flags":{"items":{"type":"string"},"type":"array","title":"Flags","description":"Message flags"},"body_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Text","description":"Plain text body"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html","description":"HTML body"},"attachments":{"items":{"$ref":"#/components/schemas/cbemail__Attachment"},"type":"array","title":"Attachments","description":"List of attachments"},"headers":{"additionalProperties":{"type":"string"},"type":"object","title":"Headers","description":"Selected headers"}},"type":"object","required":["uid"],"title":"Message","description":"Full email message.","examples":[{"attachments":[{"content_type":"application/pdf","filename":"report.pdf","size":245678}],"bcc_address":[],"body_html":"<html><body><p>Hi Bob,</p><p>Here's the weekly update...</p><p>Best,<br>Alice</p></body></html>","body_text":"Hi Bob,\n\nHere's the weekly update...\n\nBest,\nAlice","cc_address":["team@example.com"],"date":"2024-01-15T09:30:00Z","flags":["\\Seen"],"from_address":"alice@example.com","headers":{"X-Mailer":"Gmail","X-Priority":"3"},"message_id":"<CAH1rXxJ8hFn3qKE@mail.gmail.com>","reply_to":"alice@example.com","subject":"Weekly Project Update","to_address":["bob@example.com"],"uid":"12345"}]},"cbemail__MessageList":{"properties":{"folder":{"type":"string","title":"Folder","description":"Folder name"},"messages":{"items":{"$ref":"#/components/schemas/cbemail__MessageSummary"},"type":"array","title":"Messages","description":"List of messages"},"total":{"type":"integer","title":"Total","description":"Total number of messages in folder"},"offset":{"type":"integer","title":"Offset","description":"Current offset","default":0},"limit":{"type":"integer","title":"Limit","description":"Number of messages returned"}},"type":"object","required":["folder","total","limit"],"title":"MessageList","description":"List of messages response.","examples":[{"folder":"INBOX","limit":50,"messages":[{"date":"2024-01-15T09:30:00Z","flags":["\\Seen"],"from_address":"alice@example.com","message_id":"<CAH1rXxJ8hFn3qKE@mail.gmail.com>","size":15234,"subject":"Weekly Project Update","to_address":"bob@example.com","uid":"12345"},{"date":"2024-01-14T16:45:00Z","flags":["\\Seen","\\Flagged"],"from_address":"finance@company.com","message_id":"<MSG002@mail.example.com>","size":8921,"subject":"Re: Budget Review","to_address":"bob@example.com","uid":"12344"}],"offset":0,"total":1542}]},"cbemail__MessageMark":{"properties":{"action":{"$ref":"#/components/schemas/cbemail__MarkAction","description":"Mark action to perform"}},"type":"object","required":["action"],"title":"MessageMark","description":"Request model for marking a message.","examples":[{"action":"read"},{"action":"flagged"}]},"cbemail__MessageMove":{"properties":{"destination_folder":{"type":"string","title":"Destination Folder","description":"Destination folder name"}},"type":"object","required":["destination_folder"],"title":"MessageMove","description":"Request model for moving a message.","examples":[{"destination_folder":"Archive"},{"destination_folder":"[Gmail]/Trash"}]},"cbemail__MessageSummary":{"properties":{"uid":{"type":"string","title":"Uid","description":"Message UID"},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Id","description":"Message-ID header"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Email subject"},"from_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Address","description":"From address"},"to_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To Address","description":"To address"},"date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date","description":"Message date"},"flags":{"items":{"type":"string"},"type":"array","title":"Flags","description":"Message flags"},"size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size","description":"Message size in bytes"}},"type":"object","required":["uid"],"title":"MessageSummary","description":"Summary of an email message.","examples":[{"date":"2024-01-15T09:30:00Z","flags":["\\Seen"],"from_address":"alice@example.com","message_id":"<CAH1rXxJ8hFn3qKE@mail.gmail.com>","size":15234,"subject":"Weekly Project Update","to_address":"bob@example.com","uid":"12345"}]},"cbemail__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbemail__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbemail__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbemail__Provider":{"type":"string","enum":["gmail","outlook","yahoo","icloud","custom"],"title":"Provider","description":"Email provider types."},"cbemail__PruneResult":{"properties":{"cutoff":{"type":"string","format":"date-time","title":"Cutoff","description":"Sessions untouched since this moment were eligible"},"eligible":{"type":"integer","title":"Eligible","description":"Active sessions older than the cutoff"},"revoked":{"type":"integer","title":"Revoked","description":"Sessions soft-revoked (0 on dry runs)"},"dry_run":{"type":"boolean","title":"Dry Run"},"by_email":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Email","description":"Eligible-session count per email address"}},"type":"object","required":["cutoff","eligible","revoked","dry_run"],"title":"PruneResult","description":"Result of a stale-session prune."},"cbemail__SendEmail":{"properties":{"to":{"anyOf":[{"items":{"type":"string","format":"email"},"type":"array"},{"type":"string","format":"email"}],"title":"To","description":"Recipient email address(es)"},"subject":{"type":"string","title":"Subject","description":"Email subject"},"body":{"type":"string","title":"Body","description":"Email body (plain text)"},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html","description":"Email body (HTML)"},"cc":{"anyOf":[{"items":{"type":"string","format":"email"},"type":"array"},{"type":"null"}],"title":"Cc","description":"CC recipients"},"bcc":{"anyOf":[{"items":{"type":"string","format":"email"},"type":"array"},{"type":"null"}],"title":"Bcc","description":"BCC recipients"},"reply_to":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Reply To","description":"Reply-To address"},"in_reply_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"In Reply To","description":"Message-ID this email replies to (sets the In-Reply-To header, e.g. '<abc@example.com>'). Real clients thread on this, not on the subject line."},"references":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"References","description":"Space-separated Message-ID chain for the References header. Defaults to in_reply_to when omitted, which threads correctly for a first reply."},"attachments":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbemail__EmailAttachment"},"type":"array"},{"type":"null"}],"title":"Attachments","description":"File attachments (base64-encoded)"},"from":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"From","description":"Sender mailbox to send as. Only honored for cbauth `cbemail.send.create` callers (fleet agents); it selects a provisioned active session and must match one, so it is deterministic and cannot spoof an address cbemail has no credentials for. Ignored for session-token / API-key+X-Session-ID auth, which always sends as the session's own address."}},"type":"object","required":["to","subject","body"],"title":"SendEmail","description":"Request model for sending an email.","examples":[{"body":"This is a test email sent via the cbemail API.","subject":"Hello from cbemail","to":"recipient@example.com"},{"attachments":[{"content":"JVBERi0xLjQK...","filename":"report.pdf","mime_type":"application/pdf"}],"bcc":["archive@example.com"],"body":"Please see the attached report.","body_html":"<html><body><h1>Team Update</h1><p>Please see the attached report.</p></body></html>","cc":["manager@example.com"],"reply_to":"noreply@example.com","subject":"Team Update","to":["alice@example.com","bob@example.com"]}]},"cbemail__SendEmailBatch":{"properties":{"emails":{"items":{"$ref":"#/components/schemas/cbemail__SendEmail"},"type":"array","title":"Emails","description":"List of emails to send"},"from":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"From","description":"Sender mailbox for the whole batch (cbauth `cbemail.send.create` mode only; must match a provisioned active session). Ignored for session auth."}},"type":"object","required":["emails"],"title":"SendEmailBatch","description":"Request model for sending multiple emails.","examples":[{"emails":[{"body":"Hi Alice, here's your personalized update...","subject":"Personal Update for Alice","to":"alice@example.com"},{"body":"Hi Bob, here's your personalized update...","subject":"Personal Update for Bob","to":"bob@example.com"}]}]},"cbemail__SendToList":{"properties":{"list_id":{"type":"string","title":"List Id","description":"The cblist list id to resolve recipients from."},"subject":{"type":"string","title":"Subject","description":"Subject. Supports {first_name}/{merge_var} placeholders."},"body":{"type":"string","title":"Body","description":"Plain-text body. Supports {placeholder} substitution."},"body_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Html","description":"Optional HTML body (also templated)."},"reply_to":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Reply To","description":"Reply-To address."},"tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant","description":"Tenant that owns the list (scopes the cblist lookup)."},"dry_run":{"type":"boolean","title":"Dry Run","description":"Resolve + preview recipients without sending. No SMTP session required.","default":false}},"type":"object","required":["list_id","subject","body"],"title":"SendToList","description":"Send a templated email to every email member of a cblist."},"cbemail__SendToListResult":{"properties":{"list_id":{"type":"string","title":"List Id","description":"The cblist id the send was resolved from."},"recipient_count":{"type":"integer","title":"Recipient Count","description":"Email members resolved from the list."},"sent":{"type":"integer","title":"Sent","description":"Emails successfully handed to SMTP.","default":0},"failed":{"type":"integer","title":"Failed","description":"Emails that failed to send.","default":0},"dry_run":{"type":"boolean","title":"Dry Run","description":"True when nothing was sent (preview only).","default":false},"sample":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sample","description":"First few resolved recipient emails."},"preview":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Preview","description":"The first rendered email (dry-run only)."}},"type":"object","required":["list_id","recipient_count"],"title":"SendToListResult","description":"Outcome of a send-to-list request."},"cbemail__SessionCreate":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Email address"},"password":{"type":"string","title":"Password","description":"Email password or app-specific password"},"provider":{"anyOf":[{"$ref":"#/components/schemas/cbemail__Provider"},{"type":"null"}],"description":"Email provider (auto-detected if not specified)"},"imap_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Imap Server","description":"IMAP server hostname"},"imap_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Imap Port","description":"IMAP server port"},"smtp_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Smtp Server","description":"SMTP server hostname"},"smtp_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Smtp Port","description":"SMTP server port"}},"type":"object","required":["email","password"],"title":"SessionCreate","description":"Request model for creating a new session.","examples":[{"email":"user@gmail.com","password":"abcd-efgh-ijkl-mnop"},{"email":"user@company.com","imap_port":993,"imap_server":"mail.company.com","password":"secure-password","provider":"custom","smtp_port":587,"smtp_server":"mail.company.com"}]},"cbemail__SessionInfo":{"properties":{"session_id":{"type":"string","title":"Session Id","description":"Unique session identifier"},"email":{"type":"string","format":"email","title":"Email","description":"Email address"},"provider":{"$ref":"#/components/schemas/cbemail__Provider","description":"Email provider"},"imap_server":{"type":"string","title":"Imap Server","description":"IMAP server hostname"},"smtp_server":{"type":"string","title":"Smtp Server","description":"SMTP server hostname"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Session creation timestamp"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"Last activity timestamp"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the session is active"}},"type":"object","required":["session_id","email","provider","imap_server","smtp_server","created_at","is_active"],"title":"SessionInfo","description":"Session information response.","examples":[{"created_at":"2024-01-15T10:30:00Z","email":"user@gmail.com","imap_server":"imap.gmail.com","is_active":true,"last_used_at":"2024-01-15T14:22:00Z","provider":"gmail","session_id":"550e8400-e29b-41d4-a716-446655440000","smtp_server":"smtp.gmail.com"}]},"cbemail__SessionResponse":{"properties":{"session_id":{"type":"string","title":"Session Id","description":"Unique session identifier"},"token":{"type":"string","title":"Token","description":"JWT token for authentication"},"email":{"type":"string","format":"email","title":"Email","description":"Email address"},"provider":{"$ref":"#/components/schemas/cbemail__Provider","description":"Detected or specified provider"},"expires_in":{"type":"integer","title":"Expires In","description":"Token expiration time in seconds"}},"type":"object","required":["session_id","token","email","provider","expires_in"],"title":"SessionResponse","description":"Response model after session creation.","examples":[{"email":"user@gmail.com","expires_in":1800,"provider":"gmail","session_id":"550e8400-e29b-41d4-a716-446655440000","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uX2lkIjoiNTUwZTg0MDAtZTI5Yi00MWQ0LWE3MTYtNDQ2NjU1NDQwMDAwIiwiZXhwIjoxNzA0MDY3MjAwfQ.signature"}]},"cbemail__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbemail__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbemail__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbenrichment__Body_create_job_api_v1_jobs_post":{"properties":{"file":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"File"},"cbfiles_bucket":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbfiles Bucket"},"cbfiles_object":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbfiles Object"},"mapping":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mapping"},"use_ai":{"type":"boolean","title":"Use Ai","default":true},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner","description":"owner string to check list-matrix attribution against"},"tenant":{"type":"string","title":"Tenant","default":"cc"},"precise_geocode":{"type":"boolean","title":"Precise Geocode","description":"precise per-address geocoding (cbetl normalize + cbgeo free-text + cbdistricts), hard-capped at 500 rows/job until cbgeo#3 lands; the default geo path is the offline zip centroid","default":false},"geocode_limit":{"type":"integer","title":"Geocode Limit","description":"cap precise geocoding at this many address rows (0 = the hard cap, 500)","default":0}},"type":"object","title":"Body_create_job_api_v1_jobs_post"},"cbenrichment__Body_probe_job_api_v1_jobs_probe_post":{"properties":{"file":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"File"},"cbfiles_bucket":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbfiles Bucket"},"cbfiles_object":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbfiles Object"},"mapping":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mapping","description":"JSON object {\"<header>\": \"<canonical field or null>\"} overriding column detection"},"use_ai":{"type":"boolean","title":"Use Ai","description":"let cbai suggest fields for headers the heuristic cannot name (best-effort; never overrides a heuristic hit)","default":true}},"type":"object","title":"Body_probe_job_api_v1_jobs_probe_post"},"cbenrichment__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbenrichment__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbenrichment__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbenrichment__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbenrichment__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbenrichment__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbenrichment__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbenrichment__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbenrichment__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbenrichment__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbenrichment__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbenrichment__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbenrichment__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbenrichment__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbesig__ArchiveStats":{"properties":{"total":{"type":"integer","title":"Total","description":"Total archived envelopes across all sources."},"pdfs_archived":{"type":"integer","title":"Pdfs Archived","description":"How many envelopes have a signed PDF archived to cbfiles."},"by_source":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Source","description":"Envelope counts keyed by source provider (boldsign/docusign)."},"by_status":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Status","description":"Envelope counts keyed by normalized status."}},"type":"object","required":["total","pdfs_archived","by_source","by_status"],"title":"ArchiveStats","description":"Aggregate archive counts for dashboard widgets.","example":{"by_source":{"boldsign":92,"docusign":45},"by_status":{"completed":118,"expired":7,"in_progress":12},"pdfs_archived":121,"total":137}},"cbesig__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbesig__Document":{"properties":{"name":{"type":"string","title":"Name","description":"Document/file name.","default":""},"page_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page Count","description":"Number of pages in the document, if known."}},"type":"object","title":"Document","description":"A single file attached to an archived envelope.","example":{"name":"Offer Letter.pdf","page_count":3}},"cbesig__DocumentCreated":{"properties":{"document_id":{"type":"string","title":"Document Id","description":"Identifier of the newly created document."}},"type":"object","required":["document_id"],"title":"DocumentCreated","description":"Response after creating a signing request.","example":{"document_id":"doc_abc123"}},"cbesig__DocumentListResponse":{"properties":{"page":{"type":"integer","title":"Page","description":"Current page number (1-based)."},"page_size":{"type":"integer","title":"Page Size","description":"Number of documents per page."},"total":{"type":"integer","title":"Total","description":"Total documents matching the filters."},"documents":{"items":{"$ref":"#/components/schemas/cbesig__DocumentSummary"},"type":"array","title":"Documents","description":"Documents on this page."}},"type":"object","required":["page","page_size","total","documents"],"title":"DocumentListResponse","description":"Paginated list of documents.","example":{"documents":[],"page":1,"page_size":20,"total":92}},"cbesig__DocumentSigner":{"properties":{"name":{"type":"string","title":"Name","description":"Signer's full name."},"emailAddress":{"type":"string","title":"Emailaddress","description":"Signer's email address."},"signerType":{"type":"string","title":"Signertype","description":"Recipient type, e.g. 'Signer' or 'CC'.","default":"Signer"},"signerOrder":{"type":"integer","title":"Signerorder","description":"Signing order position when a sequential order applies.","default":1}},"type":"object","required":["name","emailAddress"],"title":"DocumentSigner","description":"Signer for a raw document send.\n\nUnlike :class:`TemplateRole` (which maps to a predefined template slot),\nthis describes a signer for an ad-hoc PDF send.","example":{"emailAddress":"jane@example.com","name":"Jane Doe","signerOrder":1,"signerType":"Signer"}},"cbesig__DocumentStatus":{"properties":{"document_id":{"type":"string","title":"Document Id","description":"Document identifier."},"title":{"type":"string","title":"Title","description":"Document title.","default":""},"status":{"type":"string","title":"Status","description":"Overall document status, e.g. 'Completed' or 'InProgress'.","default":""},"created_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created Date","description":"Creation time as a Unix epoch timestamp (seconds)."},"expiry_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expiry Date","description":"Expiry time as a Unix epoch timestamp (seconds)."},"signer_details":{"items":{"$ref":"#/components/schemas/cbesig__SignerDetail"},"type":"array","title":"Signer Details","description":"Per-signer status entries."},"labels":{"items":{"type":"string"},"type":"array","title":"Labels","description":"Labels/tags on the document."},"metadata":{"additionalProperties":{"type":"string"},"type":"object","title":"Metadata","description":"Key/value metadata on the document."}},"type":"object","required":["document_id"],"title":"DocumentStatus","description":"Document properties and signing status.\n\nReturned when polling a single document; ``status`` is the overall\ndocument lifecycle state and ``signer_details`` carries per-recipient\nprogress. Date fields are Unix epoch seconds (BoldSign's native format).","example":{"created_date":1748419200,"document_id":"doc_abc123","expiry_date":1751011200,"labels":["onboarding"],"metadata":{"employee_id":"E-1024"},"signer_details":[{"signer_email":"jane@example.com","signer_name":"Jane Doe","signer_type":"Signer","status":"InProgress"}],"status":"InProgress","title":"2026 Onboarding — Jane Doe"}},"cbesig__DocumentSummary":{"properties":{"document_id":{"type":"string","title":"Document Id","description":"Document identifier."},"title":{"type":"string","title":"Title","description":"Document title.","default":""},"status":{"type":"string","title":"Status","description":"Overall document status.","default":""},"created_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created Date","description":"Creation time as a Unix epoch timestamp (seconds)."},"activity_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Activity Date","description":"Most-recent activity time as a Unix epoch timestamp (seconds)."},"signer_details":{"items":{"$ref":"#/components/schemas/cbesig__SignerDetail"},"type":"array","title":"Signer Details","description":"Per-signer status entries."},"labels":{"items":{"type":"string"},"type":"array","title":"Labels","description":"Labels/tags on the document."}},"type":"object","required":["document_id"],"title":"DocumentSummary","description":"Document in a list response.\n\nA lighter projection of :class:`DocumentStatus` used for paginated\nlistings. Date fields are Unix epoch seconds.","example":{"activity_date":1748592000,"created_date":1748419200,"document_id":"doc_abc123","labels":["onboarding"],"signer_details":[],"status":"Completed","title":"2026 Onboarding — Jane Doe"}},"cbesig__Envelope":{"properties":{"id":{"type":"string","title":"Id","description":"Unique envelope identifier (provider document ID)."},"source":{"type":"string","title":"Source","description":"Originating provider — 'boldsign' or 'docusign'."},"title":{"type":"string","title":"Title","description":"Envelope/document title.","default":""},"status":{"type":"string","title":"Status","description":"Normalized cross-provider lifecycle status (e.g. 'completed', 'in_progress', 'expired').","default":""},"source_status":{"type":"string","title":"Source Status","description":"Raw provider-specific status string, preserved for auditing.","default":""},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"ISO-8601 creation timestamp."},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At","description":"ISO-8601 completion timestamp, if completed."},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"ISO-8601 expiry timestamp, if set."},"signers":{"items":{"$ref":"#/components/schemas/cbesig__Signer"},"type":"array","title":"Signers","description":"Recipients on the envelope."},"documents":{"items":{"$ref":"#/components/schemas/cbesig__Document"},"type":"array","title":"Documents","description":"Files attached to the envelope."},"download_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Download Url","description":"Presigned cbfiles URL to download the archived signed PDF. Populated on demand only when 'pdf_archived' is true; expires after a short window."},"pdf_archived":{"type":"boolean","title":"Pdf Archived","description":"Whether the completed signed PDF has been archived to cbfiles and is therefore available for download.","default":false}},"type":"object","required":["id","source"],"title":"Envelope","description":"A normalized signing envelope unified across BoldSign and DocuSign.\n\nOne ``Envelope`` represents a complete signing request: its title,\nlifecycle status, recipients, attached documents, and — when the signed\nPDF has been archived to cbfiles — a presigned ``download_url``.","example":{"completed_at":"2026-05-30T14:22:10Z","created_at":"2026-05-28T09:00:00Z","documents":[{"name":"Offer Letter.pdf","page_count":3}],"download_url":"https://files.campaignbrain.dev/signed/...","id":"f3a1b2c4-1234-5678-9abc-def012345678","pdf_archived":true,"signers":[{"email":"jane@example.com","name":"Jane Doe","signed_at":"2026-05-30T14:22:10Z","source_status":"Completed","status":"completed"}],"source":"boldsign","source_status":"Completed","status":"completed","title":"2026 Onboarding — Jane Doe"}},"cbesig__EnvelopeListResponse":{"properties":{"page":{"type":"integer","title":"Page","description":"Current page number (1-based)."},"page_size":{"type":"integer","title":"Page Size","description":"Number of envelopes per page."},"total":{"type":"integer","title":"Total","description":"Total envelopes matching the filters."},"envelopes":{"items":{"$ref":"#/components/schemas/cbesig__Envelope"},"type":"array","title":"Envelopes","description":"Envelopes on this page, newest first."}},"type":"object","required":["page","page_size","total","envelopes"],"title":"EnvelopeListResponse","description":"Paginated list of archived envelopes.","example":{"envelopes":[],"page":1,"page_size":20,"total":137}},"cbesig__ExistingFormField":{"properties":{"id":{"type":"string","title":"Id","description":"Field id from the template's detail response (e.g. 'TextBox1', 'CheckBox1')."},"value":{"type":"string","title":"Value","description":"Value to pre-fill. For checkboxes/radio buttons use 'on'/'off'."}},"type":"object","required":["id","value"],"title":"ExistingFormField","description":"Pre-fill value for a form field defined in a BoldSign template.\n\nThe ``id`` matches the field's ``id`` from\n``GET /api/v1/templates/detailed`` (e.g. \"TextBox1\", \"CheckBox1\",\n\"RadioGroupChild1\"). For checkboxes and radio buttons use \"on\"/\"off\".","example":{"id":"TextBox1","value":"Acme Corp"}},"cbesig__FormFieldDetail":{"properties":{"id":{"type":"string","title":"Id","description":"Field id used to target this field for prefill."},"field_type":{"type":"string","title":"Field Type","description":"Field type, e.g. 'TextBox', 'CheckBox', 'RadioButton'.","default":""},"is_required":{"type":"boolean","title":"Is Required","description":"Whether the field must be filled to complete signing.","default":false},"page_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Page Number","description":"Page the field appears on (1-based)."},"value":{"type":"string","title":"Value","description":"Current/default value of the field.","default":""},"group_name":{"type":"string","title":"Group Name","description":"Radio-group name; use this (not the child id) to prefill radios.","default":""},"label":{"type":"string","title":"Label","description":"Display label for the field.","default":""}},"type":"object","required":["id"],"title":"FormFieldDetail","description":"Form field defined in a template — surfaces id/type/page for prefill.\n\nFor radio buttons, prefill via groupName (not the child id) — set\n{\"id\": \"<groupName>\", \"value\": \"<child id>\"} in existingFormFields.","example":{"field_type":"TextBox","group_name":"","id":"TextBox1","is_required":true,"label":"Employer Name","page_number":1,"value":""}},"cbesig__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbesig__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbesig__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbesig__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbesig__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbesig__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbesig__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbesig__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbesig__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbesig__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbesig__RemindRequest":{"properties":{"message":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Message","description":"Optional override for the reminder email body. Defaults to BoldSign's stock reminder copy when omitted."},"signer_emails":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Signer Emails","description":"Optional list of pending signer emails to nudge. When omitted, BoldSign nudges every outstanding signer on the envelope."}},"type":"object","title":"RemindRequest","description":"Options for nudging a document's outstanding signers.","example":{"message":"A friendly reminder to sign your onboarding packet.","signer_emails":["jane@example.com"]}},"cbesig__RemindResponse":{"properties":{"reminded":{"type":"boolean","title":"Reminded","description":"True when the reminder was dispatched to BoldSign."},"document_id":{"type":"string","title":"Document Id","description":"Document the reminder was sent for."}},"type":"object","required":["reminded","document_id"],"title":"RemindResponse","description":"Result of a reminder request.","example":{"document_id":"doc_abc123","reminded":true}},"cbesig__SendDocumentRequest":{"properties":{"title":{"type":"string","title":"Title","description":"Title for the document being sent."},"message":{"type":"string","title":"Message","description":"Email message body shown to signers.","default":""},"signers":{"items":{"$ref":"#/components/schemas/cbesig__DocumentSigner"},"type":"array","title":"Signers","description":"Recipients who must sign the document."},"file_base64":{"type":"string","title":"File Base64","description":"Base64-encoded PDF content"},"labels":{"items":{"type":"string"},"type":"array","title":"Labels","description":"Freeform labels/tags applied to the document for filtering."},"metadata":{"additionalProperties":{"type":"string"},"type":"object","title":"Metadata","description":"Arbitrary key/value metadata stored alongside the document."},"use_text_tags":{"type":"boolean","title":"Use Text Tags","description":"If true, BoldSign parses text tags embedded in the PDF (e.g. {{s:1}} for signature, {{d:1}} for date). Without matching tags in the PDF, documents may be created in an invalid state. Leave false for PDFs without BoldSign tags.","default":false}},"type":"object","required":["title","signers","file_base64"],"title":"SendDocumentRequest","description":"Request to send a raw PDF for signing.\n\nUse this when there is no reusable template — supply the PDF inline as\nbase64 plus the list of signers.","example":{"file_base64":"JVBERi0xLjQKJ...","labels":["nda"],"message":"Please sign the attached NDA.","metadata":{"deal":"D-42"},"signers":[{"emailAddress":"jane@example.com","name":"Jane Doe","signerOrder":1,"signerType":"Signer"}],"title":"NDA — Jane Doe","use_text_tags":false}},"cbesig__SendFromTemplateRequest":{"properties":{"template_id":{"type":"string","title":"Template Id","description":"BoldSign template identifier to send."},"title":{"type":"string","title":"Title","description":"Override title for the sent document.","default":""},"message":{"type":"string","title":"Message","description":"Email message body shown to signers.","default":""},"roles":{"items":{"$ref":"#/components/schemas/cbesig__TemplateRole"},"type":"array","title":"Roles","description":"One entry per required template role, bound to a signer."},"cc":{"items":{"type":"string"},"type":"array","title":"Cc","description":"Email addresses CC'd on the document — they receive a copy of the completed signing but are NOT signers. Used e.g. for a termination notice where the manager signs and the employee is CC'd the copy (cbpulse#461)."},"labels":{"items":{"type":"string"},"type":"array","title":"Labels","description":"Freeform labels/tags applied to the document for filtering."},"metadata":{"additionalProperties":{"type":"string"},"type":"object","title":"Metadata","description":"Arbitrary key/value metadata stored alongside the document."},"enable_signing_order":{"type":"boolean","title":"Enable Signing Order","description":"If true, signers must complete in the order defined by the template's signerOrder. Use for sequential workflows like 'employee signs, then manager approves'.","default":false}},"type":"object","required":["template_id","roles"],"title":"SendFromTemplateRequest","description":"Request to send a document from a BoldSign template.\n\nThe common send path: pick a template, bind each of its role slots to a\nreal signer, and fire. Use ``GET /api/v1/templates/detailed`` first to\nlearn which role indexes a template requires.","example":{"cc":["hr@example.com"],"enable_signing_order":false,"labels":["onboarding"],"message":"Please review and sign your onboarding packet.","metadata":{"employee_id":"E-1024"},"roles":[{"roleIndex":1,"signerEmail":"jane@example.com","signerName":"Jane Doe","signerType":"Signer"}],"template_id":"tmpl_abc123","title":"2026 Onboarding — Jane Doe"}},"cbesig__Signer":{"properties":{"name":{"type":"string","title":"Name","description":"Signer's display name.","default":""},"email":{"type":"string","title":"Email","description":"Signer's email address.","default":""},"status":{"type":"string","title":"Status","description":"Normalized signing status for this signer (e.g. 'completed', 'pending').","default":""},"source_status":{"type":"string","title":"Source Status","description":"Raw provider-specific status string as reported by BoldSign or DocuSign, preserved for auditing.","default":""},"signed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed At","description":"ISO-8601 timestamp of when this signer completed, if signed."}},"type":"object","title":"Signer","description":"One recipient on an archived envelope and their signing progress.\n\n``status`` is the normalized cross-provider value; ``source_status``\npreserves the raw provider-specific status string for auditing.","example":{"email":"jane@example.com","name":"Jane Doe","signed_at":"2026-05-30T14:22:10Z","source_status":"Completed","status":"completed"}},"cbesig__SignerDetail":{"properties":{"signer_name":{"type":"string","title":"Signer Name","description":"Signer's display name.","default":""},"signer_email":{"type":"string","title":"Signer Email","description":"Signer's email address.","default":""},"status":{"type":"string","title":"Status","description":"This signer's status, e.g. 'Completed' or 'InProgress'.","default":""},"signer_type":{"type":"string","title":"Signer Type","description":"Recipient type, e.g. 'Signer' or 'CC'.","default":""}},"type":"object","title":"SignerDetail","description":"Signer status within a document.","example":{"signer_email":"jane@example.com","signer_name":"Jane Doe","signer_type":"Signer","status":"Completed"}},"cbesig__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbesig__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbesig__TemplateDetail":{"properties":{"template_id":{"type":"string","title":"Template Id","description":"Template identifier."},"title":{"type":"string","title":"Title","description":"Template title.","default":""},"description":{"type":"string","title":"Description","description":"Template description.","default":""},"document_title":{"type":"string","title":"Document Title","description":"Default document title applied to sent documents.","default":""},"document_message":{"type":"string","title":"Document Message","description":"Default email message applied to sent documents.","default":""},"roles":{"items":{"$ref":"#/components/schemas/cbesig__TemplateRoleDetail"},"type":"array","title":"Roles","description":"Role slots that must be bound to signers."},"files":{"items":{"$ref":"#/components/schemas/cbesig__TemplateFileInfo"},"type":"array","title":"Files","description":"Files bundled into the template."},"labels":{"items":{"type":"string"},"type":"array","title":"Labels","description":"Labels/tags on the template."},"created_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created Date","description":"Creation time as a Unix epoch timestamp (seconds)."}},"type":"object","required":["template_id"],"title":"TemplateDetail","description":"Full template info — everything needed to set up a send call.\n\nReturned by ``GET /api/v1/templates/detailed``. Read ``roles`` to learn\nwhich ``roleIndex`` values a send-from-template request must supply, and\n``files``/each role's ``form_fields`` to discover prefill targets.","example":{"created_date":1746000000,"description":"New-hire onboarding packet","document_message":"Please review and sign.","document_title":"2026 Onboarding","files":[],"labels":["hr"],"roles":[],"template_id":"tmpl_abc123","title":"Standard Onboarding"}},"cbesig__TemplateFileInfo":{"properties":{"document_name":{"type":"string","title":"Document Name","description":"File/document name.","default":""},"order":{"type":"integer","title":"Order","description":"Order of the file within the template.","default":0},"page_count":{"type":"integer","title":"Page Count","description":"Number of pages in the file.","default":0}},"type":"object","title":"TemplateFileInfo","description":"File included in a template.","example":{"document_name":"Offer Letter.pdf","order":0,"page_count":3}},"cbesig__TemplateListResponse":{"properties":{"page":{"type":"integer","title":"Page","description":"Current page number (1-based)."},"page_size":{"type":"integer","title":"Page Size","description":"Number of templates per page."},"total":{"type":"integer","title":"Total","description":"Total templates matching the search."},"templates":{"items":{"$ref":"#/components/schemas/cbesig__TemplateSummary"},"type":"array","title":"Templates","description":"Templates on this page."}},"type":"object","required":["page","page_size","total","templates"],"title":"TemplateListResponse","description":"Paginated list of templates.","example":{"page":1,"page_size":20,"templates":[],"total":8}},"cbesig__TemplateRole":{"properties":{"roleIndex":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Roleindex","description":"Template role slot index (1-50)."},"signerName":{"type":"string","title":"Signername","description":"Full name of the person filling this role."},"signerEmail":{"type":"string","title":"Signeremail","description":"Email address of the signer."},"signerType":{"type":"string","title":"Signertype","description":"Recipient type, e.g. 'Signer' or 'CC'.","default":"Signer"},"existingFormFields":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbesig__ExistingFormField"},"type":"array"},{"type":"null"}],"title":"Existingformfields","description":"Optional list of {id, value} pairs to pre-fill template form fields."}},"type":"object","required":["roleIndex","signerName","signerEmail"],"title":"TemplateRole","description":"Maps a template role slot to an actual signer.\n\nEach BoldSign template defines one or more numbered role slots; this\nbinds a slot (``role_index``) to a concrete person and, optionally,\npre-fills that role's form fields.","example":{"roleIndex":1,"signerEmail":"jane@example.com","signerName":"Jane Doe","signerType":"Signer"}},"cbesig__TemplateRoleDetail":{"properties":{"index":{"type":"integer","title":"Index","description":"Role slot index, used as roleIndex when sending."},"name":{"type":"string","title":"Name","description":"Role name, e.g. 'Employee' or 'Manager'.","default":""},"signer_type":{"type":"string","title":"Signer Type","description":"Recipient type for this role, e.g. 'Signer'.","default":""},"allow_role_edit":{"type":"boolean","title":"Allow Role Edit","description":"Whether the role's signer details can be edited.","default":false},"form_fields":{"items":{"$ref":"#/components/schemas/cbesig__FormFieldDetail"},"type":"array","title":"Form Fields","description":"Form fields assigned to this role."}},"type":"object","required":["index"],"title":"TemplateRoleDetail","description":"Role slot in a template — tells you what to fill in when sending.","example":{"allow_role_edit":true,"form_fields":[],"index":1,"name":"Employee","signer_type":"Signer"}},"cbesig__TemplateSummary":{"properties":{"template_id":{"type":"string","title":"Template Id","description":"Template identifier."},"template_name":{"type":"string","title":"Template Name","description":"Human-readable template name.","default":""},"description":{"type":"string","title":"Description","description":"Template description.","default":""},"created_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created Date","description":"Creation time as a Unix epoch timestamp (seconds)."},"labels":{"items":{"type":"string"},"type":"array","title":"Labels","description":"Labels/tags on the template."}},"type":"object","required":["template_id"],"title":"TemplateSummary","description":"Template in a list response.","example":{"created_date":1746000000,"description":"New-hire onboarding packet","labels":["hr"],"template_id":"tmpl_abc123","template_name":"Standard Onboarding"}},"cbesig__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbetl__AddressNormalizeRequest":{"properties":{"street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Street","description":"Street address","examples":["123 Main St","456 Elm Avenue"]},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"City name","examples":["Detroit","Columbus"]},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State abbreviation or full name","examples":["MI","Ohio"]},"postal_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Postal Code","description":"ZIP or ZIP+4 code","examples":["48201","43215-1234"]},"country":{"type":"string","title":"Country","description":"ISO 3166-1 alpha-2 country code","default":"US","examples":["US"]},"address_type":{"type":"string","title":"Address Type","description":"Address type (home, work, mailing, other)","default":"home","examples":["home","work"]}},"type":"object","title":"AddressNormalizeRequest","description":"Request model for address normalization.","examples":[{"city":"Detroit","postal_code":"48201","state":"MI","street":"123 Main St"},{"address_type":"work","city":"Columbus","postal_code":"43215","state":"Ohio","street":"456 Elm Avenue"}]},"cbetl__AddressParseRequest":{"properties":{"address_string":{"type":"string","title":"Address String","description":"Raw address string to parse into structured components","examples":["123 Main St, Detroit, MI 48201","456 Elm Ave, Columbus, OH 43215"]}},"type":"object","required":["address_string"],"title":"AddressParseRequest","description":"Request model for parsing raw address string.","examples":[{"address_string":"123 Main St, Detroit, MI 48201"},{"address_string":"456 Elm Avenue, Columbus, Ohio 43215"}]},"cbetl__AddressResponse":{"properties":{"street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Street"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"postal_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Postal Code"},"country":{"type":"string","title":"Country","default":"US"},"address_type":{"type":"string","title":"Address Type","default":"home"},"hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hash","description":"Pipe-delimited normalized string for deduplication (street|city|state|zip5)"}},"type":"object","title":"AddressResponse","description":"Response model for normalized address.\n\nThe ``hash`` field contains a pipe-delimited normalized string suitable\nfor deduplication: ``normalized_street|city|state|zip5``.","examples":[{"address_type":"home","city":"Detroit","country":"US","hash":"123 main street|detroit|mi|48201","postal_code":"48201","state":"MI","street":"123 Main Street"}]},"cbetl__BatchNormalizeRequest":{"properties":{"addresses":{"items":{"$ref":"#/components/schemas/cbetl__AddressNormalizeRequest"},"type":"array","title":"Addresses","description":"List of addresses to normalize"}},"type":"object","required":["addresses"],"title":"BatchNormalizeRequest","description":"Request model for batch address normalization."},"cbetl__BatchNormalizeResponse":{"properties":{"addresses":{"items":{"$ref":"#/components/schemas/cbetl__AddressResponse"},"type":"array","title":"Addresses","description":"List of normalized addresses"},"total":{"type":"integer","title":"Total","description":"Total number of addresses processed"},"success_count":{"type":"integer","title":"Success Count","description":"Number of successfully normalized addresses"}},"type":"object","required":["addresses","total","success_count"],"title":"BatchNormalizeResponse","description":"Response model for batch address normalization."},"cbetl__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbetl__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbetl__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbetl__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbetl__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbetl__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbetl__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbetl__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbetl__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbetl__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbetl__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbetl__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbetl__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbetl__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbfiles__APIKeyCreateResponse":{"properties":{"key_info":{"$ref":"#/components/schemas/cbfiles__APIKeyInfo"},"api_key":{"type":"string","title":"Api Key","description":"Full API key - shown only once!"}},"additionalProperties":false,"type":"object","required":["key_info","api_key"],"title":"APIKeyCreateResponse","description":"Response when creating an API key (includes full key once).","x-model-type":"cbfiles"},"cbfiles__APIKeyInfo":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"user_id":{"type":"string","format":"uuid","title":"User Id"},"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name","description":"Key identifier, e.g. 'prod-server-1'"},"prefix":{"type":"string","title":"Prefix","description":"First 8 chars of key for identification"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"additionalProperties":false,"type":"object","required":["user_id","name","prefix"],"title":"APIKeyInfo","description":"API key metadata (not the key itself).","x-model-type":"cbfiles"},"cbfiles__AssetType":{"type":"string","enum":["image","video","audio","note","document"],"title":"AssetType","description":"Media asset type."},"cbfiles__Body_upload_file_api_v1_buckets__bucket_name__files__object_name__post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_file_api_v1_buckets__bucket_name__files__object_name__post"},"cbfiles__Body_upload_media_api_v1_media_upload_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_media_api_v1_media_upload_post"},"cbfiles__BrandSummary":{"properties":{"slug":{"type":"string","title":"Slug"},"display_name":{"type":"string","title":"Display Name"},"image_count":{"type":"integer","title":"Image Count","default":0},"video_count":{"type":"integer","title":"Video Count","default":0},"total_count":{"type":"integer","title":"Total Count","default":0}},"additionalProperties":false,"type":"object","required":["slug","display_name"],"title":"BrandSummary","description":"Summary of a brand's media assets.","x-model-type":"cbfiles"},"cbfiles__Bucket":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When this resource was created (UTC)","example":"2024-01-15T10:30:00Z"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When this resource was last modified (UTC)","example":"2024-01-15T14:45:00Z"},"name":{"type":"string","maxLength":63,"minLength":3,"pattern":"^[a-z0-9][a-z0-9.-]*[a-z0-9]$","title":"Name"},"owner_id":{"type":"string","format":"uuid","title":"Owner Id"},"access":{"$ref":"#/components/schemas/cbfiles__BucketAccess","default":"private"},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Description"},"backend":{"$ref":"#/components/schemas/cbfiles__StorageBackend","default":"local"}},"additionalProperties":false,"type":"object","required":["name","owner_id"],"title":"Bucket","description":"MinIO bucket metadata.","x-model-type":"cbfiles"},"cbfiles__BucketAccess":{"type":"string","enum":["private","public_read"],"title":"BucketAccess","description":"Bucket access level."},"cbfiles__BucketCreate":{"properties":{"name":{"type":"string","maxLength":63,"minLength":3,"pattern":"^[a-z0-9][a-z0-9.-]*[a-z0-9]$","title":"Name"},"access":{"$ref":"#/components/schemas/cbfiles__BucketAccess","default":"private"},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Description"}},"additionalProperties":false,"type":"object","required":["name"],"title":"BucketCreate","description":"Request model for creating a bucket.","x-model-type":"cbfiles"},"cbfiles__BucketStorageUsage":{"properties":{"bucket":{"type":"string","title":"Bucket","description":"Bucket name"},"owner_id":{"type":"string","format":"uuid","title":"Owner Id","description":"Bucket owner user ID"},"file_count":{"type":"integer","minimum":0.0,"title":"File Count","description":"Number of files in bucket","default":0},"total_bytes":{"type":"integer","minimum":0.0,"title":"Total Bytes","description":"Total storage used in bytes","default":0}},"additionalProperties":false,"type":"object","required":["bucket","owner_id"],"title":"BucketStorageUsage","description":"Storage usage for a single bucket.","x-model-type":"cbfiles"},"cbfiles__BucketUpdate":{"properties":{"access":{"anyOf":[{"$ref":"#/components/schemas/cbfiles__BucketAccess"},{"type":"null"}]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"additionalProperties":false,"type":"object","title":"BucketUpdate","description":"Request model for updating a bucket.","x-model-type":"cbfiles"},"cbfiles__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbfiles__ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message describing what went wrong","example":"Bucket not found: my-bucket"}},"additionalProperties":false,"type":"object","required":["detail"],"title":"ErrorResponse","description":"Standard error response returned by all endpoints.","example":{"detail":"Bucket not found: my-bucket"}},"cbfiles__FileGrant":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When this resource was created (UTC)","example":"2024-01-15T10:30:00Z"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When this resource was last modified (UTC)","example":"2024-01-15T14:45:00Z"},"id":{"type":"string","format":"uuid","title":"Id"},"bucket":{"type":"string","maxLength":63,"minLength":1,"title":"Bucket"},"resource_type":{"$ref":"#/components/schemas/cbfiles__GrantResourceType","default":"object"},"path":{"type":"string","maxLength":1024,"minLength":1,"title":"Path"},"permission":{"$ref":"#/components/schemas/cbfiles__GrantPermission","default":"read"},"grantee_id":{"type":"string","format":"uuid","title":"Grantee Id"},"granted_by":{"type":"string","format":"uuid","title":"Granted By"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"}},"additionalProperties":false,"type":"object","required":["bucket","path","grantee_id","granted_by"],"title":"FileGrant","description":"A grant of access to one object (or a folder/prefix) for a specific user.\n\n``created_at`` is the share time. ``path`` is an object key when\n``resource_type == object``, or a key prefix when ``resource_type == prefix``\n(a prefix grant covers every object under it, including future uploads).","x-model-type":"cbfiles"},"cbfiles__FileGrantCreate":{"properties":{"grantee_email":{"type":"string","maxLength":256,"title":"Grantee Email"},"permission":{"$ref":"#/components/schemas/cbfiles__GrantPermission","default":"read"},"objects":{"items":{"type":"string"},"type":"array","title":"Objects"},"prefix":{"anyOf":[{"type":"string","maxLength":1024},{"type":"null"}],"title":"Prefix"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"}},"additionalProperties":false,"type":"object","required":["grantee_email"],"title":"FileGrantCreate","description":"Request to share one-off / multi-select / whole-folder with a user.\n\nProvide EITHER ``objects`` (one key for a one-off, many for a checkbox\nmulti-select) OR ``prefix`` (a whole folder) — not both, not neither.","x-model-type":"cbfiles"},"cbfiles__FileInfo":{"properties":{"bucket":{"type":"string","title":"Bucket"},"name":{"type":"string","title":"Name","description":"Object name/path in bucket"},"size":{"type":"integer","minimum":0.0,"title":"Size","description":"Size in bytes"},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type"},"etag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Etag"},"last_modified":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Modified"},"visibility":{"$ref":"#/components/schemas/cbfiles__FileVisibility","default":"private"},"metadata":{"additionalProperties":{"type":"string"},"type":"object","title":"Metadata"}},"additionalProperties":false,"type":"object","required":["bucket","name","size"],"title":"FileInfo","description":"File metadata from MinIO.","x-model-type":"cbfiles"},"cbfiles__FileOperation":{"type":"string","enum":["upload","download","delete","list","copy"],"title":"FileOperation","description":"File operation types for usage tracking."},"cbfiles__FileUploadResponse":{"properties":{"bucket":{"type":"string","title":"Bucket"},"name":{"type":"string","title":"Name"},"size":{"type":"integer","title":"Size"},"etag":{"type":"string","title":"Etag"},"url":{"type":"string","title":"Url","description":"Direct access URL (signed if private)"}},"additionalProperties":false,"type":"object","required":["bucket","name","size","etag","url"],"title":"FileUploadResponse","description":"Response after successful file upload.","x-model-type":"cbfiles"},"cbfiles__FileVisibility":{"type":"string","enum":["private","public"],"title":"FileVisibility","description":"File visibility status."},"cbfiles__GDriveBackendCreate":{"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name","description":"Display name for this drive"},"gcs_bucket":{"type":"string","maxLength":222,"minLength":3,"title":"Gcs Bucket","description":"Actual GCS bucket name"},"local_bucket_name":{"type":"string","maxLength":63,"minLength":3,"pattern":"^[a-z0-9][a-z0-9.-]*[a-z0-9]$","title":"Local Bucket Name","description":"Local bucket name (how it appears in CBFiles)"},"access_key":{"type":"string","minLength":1,"title":"Access Key","description":"GCS HMAC access key"},"secret_key":{"type":"string","minLength":1,"title":"Secret Key","description":"GCS HMAC secret key"},"endpoint":{"type":"string","title":"Endpoint","description":"S3-compatible endpoint","default":"storage.googleapis.com"},"read_only":{"type":"boolean","title":"Read Only","description":"Whether writes are blocked","default":true},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Description"}},"additionalProperties":false,"type":"object","required":["name","gcs_bucket","local_bucket_name","access_key","secret_key"],"title":"GDriveBackendCreate","description":"Request model for registering a Google Drive (GCS) backend.","x-model-type":"cbfiles"},"cbfiles__GDriveBackendResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"gcs_bucket":{"type":"string","title":"Gcs Bucket"},"local_bucket_name":{"type":"string","title":"Local Bucket Name"},"endpoint":{"type":"string","title":"Endpoint"},"read_only":{"type":"boolean","title":"Read Only"},"owner_id":{"type":"string","format":"uuid","title":"Owner Id"},"is_active":{"type":"boolean","title":"Is Active"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["id","name","gcs_bucket","local_bucket_name","endpoint","read_only","owner_id","is_active","created_at","updated_at"],"title":"GDriveBackendResponse","description":"API response for a GDrive backend (no secrets).","x-model-type":"cbfiles"},"cbfiles__GoogleDriveBackendCreate":{"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name","description":"Display name for this drive"},"drive_id":{"type":"string","maxLength":256,"minLength":1,"title":"Drive Id","description":"Google Shared Drive ID"},"local_bucket_name":{"type":"string","maxLength":63,"minLength":3,"pattern":"^[a-z0-9][a-z0-9.-]*[a-z0-9]$","title":"Local Bucket Name","description":"Local bucket name (how it appears in CBFiles)"},"service_account_json":{"type":"string","minLength":1,"title":"Service Account Json","description":"Service account JSON key contents"},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Description"}},"additionalProperties":false,"type":"object","required":["name","drive_id","local_bucket_name","service_account_json"],"title":"GoogleDriveBackendCreate","description":"Request model for registering a Google Shared Drive backend.","x-model-type":"cbfiles"},"cbfiles__GoogleDriveBackendResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"drive_id":{"type":"string","title":"Drive Id"},"local_bucket_name":{"type":"string","title":"Local Bucket Name"},"owner_id":{"type":"string","format":"uuid","title":"Owner Id"},"is_active":{"type":"boolean","title":"Is Active"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["id","name","drive_id","local_bucket_name","owner_id","is_active","created_at","updated_at"],"title":"GoogleDriveBackendResponse","description":"API response for a Google Drive backend (no secrets).","x-model-type":"cbfiles"},"cbfiles__GrantPermission":{"type":"string","enum":["read","write"],"title":"GrantPermission","description":"Per-user share grant permission level."},"cbfiles__GrantResourceType":{"type":"string","enum":["object","prefix"],"title":"GrantResourceType","description":"What a share grant covers: a single object or a folder (key prefix)."},"cbfiles__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbfiles__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbfiles__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbfiles__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbfiles__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbfiles__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbfiles__IndexJobResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"bucket_name":{"type":"string","title":"Bucket Name"},"status":{"$ref":"#/components/schemas/cbfiles__IndexStatus"},"text_only":{"type":"boolean","title":"Text Only"},"total_files":{"type":"integer","title":"Total Files"},"indexed_files":{"type":"integer","title":"Indexed Files"},"skipped_files":{"type":"integer","title":"Skipped Files"},"failed_files":{"type":"integer","title":"Failed Files"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","bucket_name","status","text_only","total_files","indexed_files","skipped_files","failed_files","created_at"],"title":"IndexJobResponse","description":"API response for an index job.","x-model-type":"cbfiles"},"cbfiles__IndexStatus":{"type":"string","enum":["pending","running","completed","failed"],"title":"IndexStatus","description":"Index job status."},"cbfiles__MediaAssetResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"bucket":{"type":"string","title":"Bucket","default":"vip-media"},"brand":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand"},"tenant_app":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant App"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"object_name":{"type":"string","title":"Object Name"},"drive_file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Drive File Id"},"asset_type":{"$ref":"#/components/schemas/cbfiles__AssetType"},"content_type":{"type":"string","title":"Content Type"},"size_bytes":{"type":"integer","title":"Size Bytes"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"tagged_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Tagged At"},"inferred_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Inferred At"},"source":{"type":"string","title":"Source"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["id","object_name","asset_type","content_type","size_bytes","tags","source","created_at","updated_at"],"title":"MediaAssetResponse","description":"API response for a single media asset.","x-model-type":"cbfiles"},"cbfiles__MediaAssetUpsertRequest":{"properties":{"bucket":{"type":"string","title":"Bucket","description":"MinIO bucket name"},"object_name":{"type":"string","title":"Object Name","description":"MinIO object path within bucket"},"asset_type":{"$ref":"#/components/schemas/cbfiles__AssetType"},"content_type":{"type":"string","title":"Content Type","description":"MIME type"},"size_bytes":{"type":"integer","minimum":0.0,"title":"Size Bytes","default":0},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"etag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Etag"},"tenant_app":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant App"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"brand":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand"},"source":{"type":"string","title":"Source","default":"intel"}},"additionalProperties":false,"type":"object","required":["bucket","object_name","asset_type","content_type"],"title":"MediaAssetUpsertRequest","description":"Request to upsert a media asset by (bucket, object_name).\n\nUsed by external tagger workflows (e.g., cbintel Intel scan) that want to\nrecord inferred tags + description for a file without going through the\nhospitality drive-sync flow.","x-model-type":"cbfiles"},"cbfiles__MediaSearchResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/cbfiles__MediaAssetResponse"},"type":"array","title":"Results"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"additionalProperties":false,"type":"object","required":["results","total","limit","offset"],"title":"MediaSearchResponse","description":"API response for media search.","x-model-type":"cbfiles"},"cbfiles__MediaSyncJobResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"brands":{"items":{"type":"string"},"type":"array","title":"Brands"},"status":{"$ref":"#/components/schemas/cbfiles__MediaSyncStatus"},"total_files":{"type":"integer","title":"Total Files"},"synced_files":{"type":"integer","title":"Synced Files"},"tagged_files":{"type":"integer","title":"Tagged Files"},"skipped_files":{"type":"integer","title":"Skipped Files"},"failed_files":{"type":"integer","title":"Failed Files"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","brands","status","total_files","synced_files","tagged_files","skipped_files","failed_files","created_at"],"title":"MediaSyncJobResponse","description":"API response for a media sync job.","x-model-type":"cbfiles"},"cbfiles__MediaSyncStatus":{"type":"string","enum":["pending","running","completed","failed"],"title":"MediaSyncStatus","description":"Media sync job status."},"cbfiles__MediaTagUpdateRequest":{"properties":{"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Full replacement tag list"}},"additionalProperties":false,"type":"object","required":["tags"],"title":"MediaTagUpdateRequest","description":"Request to update tags on a media asset.","x-model-type":"cbfiles"},"cbfiles__MediaUploadResponse":{"properties":{"asset":{"$ref":"#/components/schemas/cbfiles__MediaAssetResponse"}},"additionalProperties":false,"type":"object","required":["asset"],"title":"MediaUploadResponse","description":"Response after uploading a media asset.","x-model-type":"cbfiles"},"cbfiles__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbfiles__MinioServerState":{"type":"string","enum":["stopped","starting","running","stopping","error"],"title":"MinioServerState","description":"MinIO server process state."},"cbfiles__MinioServerStatus":{"properties":{"state":{"$ref":"#/components/schemas/cbfiles__MinioServerState"},"pid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pid"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endpoint"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"additionalProperties":false,"type":"object","required":["state"],"title":"MinioServerStatus","description":"MinIO server status information.","x-model-type":"cbfiles"},"cbfiles__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbfiles__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbfiles__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbfiles__SQLRequest":{"properties":{"sql":{"type":"string","title":"Sql","description":"DuckDB SQL (SELECT/WITH/EXPLAIN only)"},"limit":{"type":"integer","maximum":10000.0,"minimum":1.0,"title":"Limit","description":"Max rows","default":10000}},"type":"object","required":["sql"],"title":"SQLRequest","description":"Read-only SQL query request."},"cbfiles__SearchRequest":{"properties":{"query":{"type":"string","maxLength":2000,"minLength":1,"title":"Query"},"buckets":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Buckets","description":"Buckets to search (None = all indexed buckets)"},"k":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"K","description":"Number of results","default":10},"min_score":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Min Score","default":0.0}},"additionalProperties":false,"type":"object","required":["query"],"title":"SearchRequest","description":"API request for semantic search.","x-model-type":"cbfiles"},"cbfiles__SearchResponse":{"properties":{"query":{"type":"string","title":"Query"},"results":{"items":{"$ref":"#/components/schemas/cbfiles__SearchResultItem"},"type":"array","title":"Results"},"total":{"type":"integer","title":"Total"}},"additionalProperties":false,"type":"object","required":["query","results","total"],"title":"SearchResponse","description":"API response for semantic search.","x-model-type":"cbfiles"},"cbfiles__SearchResultItem":{"properties":{"bucket":{"type":"string","title":"Bucket"},"object_name":{"type":"string","title":"Object Name"},"score":{"type":"number","title":"Score"},"snippet":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snippet","description":"Matching text snippet"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Presigned download URL"}},"additionalProperties":false,"type":"object","required":["bucket","object_name","score"],"title":"SearchResultItem","description":"Single search result.","x-model-type":"cbfiles"},"cbfiles__ServiceUser":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When this resource was created (UTC)","example":"2024-01-15T10:30:00Z"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When this resource was last modified (UTC)","example":"2024-01-15T14:45:00Z"},"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"email":{"type":"string","maxLength":256,"title":"Email"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"additionalProperties":false,"type":"object","required":["name","email"],"title":"ServiceUser","description":"API consumer account.","x-model-type":"cbfiles"},"cbfiles__ServiceUserCreate":{"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"email":{"type":"string","maxLength":256,"title":"Email"}},"additionalProperties":false,"type":"object","required":["name","email"],"title":"ServiceUserCreate","description":"Request model for creating a service user.","x-model-type":"cbfiles"},"cbfiles__SharedItem":{"properties":{"grant_id":{"type":"string","format":"uuid","title":"Grant Id"},"bucket":{"type":"string","title":"Bucket"},"resource_type":{"$ref":"#/components/schemas/cbfiles__GrantResourceType"},"path":{"type":"string","title":"Path"},"permission":{"$ref":"#/components/schemas/cbfiles__GrantPermission"},"granted_by_email":{"type":"string","title":"Granted By Email"},"granted_by_name":{"type":"string","title":"Granted By Name"},"shared_at":{"type":"string","format":"date-time","title":"Shared At"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"}},"additionalProperties":false,"type":"object","required":["grant_id","bucket","resource_type","path","permission","granted_by_email","granted_by_name","shared_at"],"title":"SharedItem","description":"A 'shared with me' entry — a grant enriched with grantor provenance so\nthe UI can label/group it ('from Kevin').","x-model-type":"cbfiles"},"cbfiles__SignedUrlResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"bucket":{"type":"string","title":"Bucket"},"object_name":{"type":"string","title":"Object Name"},"short_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Short Url","description":"Shortened URL via cblinks (when shorten=true)"}},"additionalProperties":false,"type":"object","required":["url","expires_at","bucket","object_name"],"title":"SignedUrlResponse","description":"Signed URL response.","x-model-type":"cbfiles"},"cbfiles__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbfiles__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbfiles__StorageBackend":{"type":"string","enum":["local","gcs","google_drive"],"title":"StorageBackend","description":"Storage backend type for buckets."},"cbfiles__StorageUsageResponse":{"properties":{"total_buckets":{"type":"integer","minimum":0.0,"title":"Total Buckets","description":"Total number of buckets","default":0},"total_files":{"type":"integer","minimum":0.0,"title":"Total Files","description":"Total number of files","default":0},"total_bytes":{"type":"integer","minimum":0.0,"title":"Total Bytes","description":"Total storage used in bytes","default":0},"by_user":{"items":{"$ref":"#/components/schemas/cbfiles__UserStorageUsage"},"type":"array","title":"By User","description":"Storage breakdown by user"}},"additionalProperties":false,"type":"object","title":"StorageUsageResponse","description":"Overall storage usage response with per-user breakdown.","x-model-type":"cbfiles"},"cbfiles__SyncJobResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"backend_id":{"type":"string","format":"uuid","title":"Backend Id"},"bucket_name":{"type":"string","title":"Bucket Name"},"status":{"$ref":"#/components/schemas/cbfiles__SyncStatus"},"sync_type":{"$ref":"#/components/schemas/cbfiles__SyncType"},"total_files":{"type":"integer","title":"Total Files"},"synced_files":{"type":"integer","title":"Synced Files"},"skipped_files":{"type":"integer","title":"Skipped Files"},"failed_files":{"type":"integer","title":"Failed Files"},"total_bytes":{"type":"integer","title":"Total Bytes"},"synced_bytes":{"type":"integer","title":"Synced Bytes"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","backend_id","bucket_name","status","sync_type","total_files","synced_files","skipped_files","failed_files","total_bytes","synced_bytes","created_at"],"title":"SyncJobResponse","description":"API response for a sync job.","x-model-type":"cbfiles"},"cbfiles__SyncStatus":{"type":"string","enum":["pending","running","completed","failed"],"title":"SyncStatus","description":"Drive sync job status."},"cbfiles__SyncTriggerRequest":{"properties":{"sync_type":{"$ref":"#/components/schemas/cbfiles__SyncType","default":"full"},"switch_backend":{"type":"boolean","title":"Switch Backend","description":"Switch bucket backend to LOCAL after sync completes","default":false}},"additionalProperties":false,"type":"object","title":"SyncTriggerRequest","description":"API request to trigger a sync.","x-model-type":"cbfiles"},"cbfiles__SyncType":{"type":"string","enum":["full","incremental"],"title":"SyncType","description":"Drive sync type."},"cbfiles__UsageRecord":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique record identifier"},"user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"User Id","description":"User who performed the operation (None for anonymous)"},"api_key_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Api Key Id","description":"API key used for the request"},"operation":{"$ref":"#/components/schemas/cbfiles__FileOperation","description":"Type of file operation"},"bucket":{"type":"string","title":"Bucket","description":"Target bucket name"},"object_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Object Name","description":"Target object path (None for bucket-level ops)"},"size_bytes":{"type":"integer","minimum":0.0,"title":"Size Bytes","description":"Size of file or data transferred in bytes","default":0},"success":{"type":"boolean","title":"Success","description":"Whether the operation succeeded","default":true},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Error message if operation failed"},"latency_ms":{"type":"number","minimum":0.0,"title":"Latency Ms","description":"Operation latency in milliseconds","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the operation occurred"}},"additionalProperties":false,"type":"object","required":["operation","bucket"],"title":"UsageRecord","description":"Individual file operation record for usage tracking.","x-model-type":"cbfiles"},"cbfiles__UsageRecordResponse":{"properties":{"records":{"items":{"$ref":"#/components/schemas/cbfiles__UsageRecord"},"type":"array","title":"Records","description":"List of usage records"},"total":{"type":"integer","minimum":0.0,"title":"Total","description":"Total matching records"},"limit":{"type":"integer","title":"Limit","description":"Page size limit"},"offset":{"type":"integer","title":"Offset","description":"Current offset"}},"additionalProperties":false,"type":"object","required":["records","total","limit","offset"],"title":"UsageRecordResponse","description":"Paginated usage records response.","x-model-type":"cbfiles"},"cbfiles__UsageSummary":{"properties":{"period_start":{"type":"string","format":"date-time","title":"Period Start","description":"Start of the summary period"},"period_end":{"type":"string","format":"date-time","title":"Period End","description":"End of the summary period"},"total_requests":{"type":"integer","minimum":0.0,"title":"Total Requests","description":"Total operations performed","default":0},"total_uploads":{"type":"integer","minimum":0.0,"title":"Total Uploads","description":"Total upload operations","default":0},"total_downloads":{"type":"integer","minimum":0.0,"title":"Total Downloads","description":"Total download operations","default":0},"total_deletes":{"type":"integer","minimum":0.0,"title":"Total Deletes","description":"Total delete operations","default":0},"total_bytes_uploaded":{"type":"integer","minimum":0.0,"title":"Total Bytes Uploaded","description":"Total bytes uploaded","default":0},"total_bytes_downloaded":{"type":"integer","minimum":0.0,"title":"Total Bytes Downloaded","description":"Total bytes downloaded","default":0},"success_rate":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Success Rate","description":"Percentage of successful operations","default":1.0},"avg_latency_ms":{"type":"number","minimum":0.0,"title":"Avg Latency Ms","description":"Average operation latency in ms","default":0}},"additionalProperties":false,"type":"object","required":["period_start","period_end"],"title":"UsageSummary","description":"Aggregated usage statistics for a time period.","x-model-type":"cbfiles"},"cbfiles__UserStorageUsage":{"properties":{"user_id":{"type":"string","format":"uuid","title":"User Id","description":"User ID"},"user_name":{"type":"string","title":"User Name","description":"User display name"},"user_email":{"type":"string","title":"User Email","description":"User email"},"bucket_count":{"type":"integer","minimum":0.0,"title":"Bucket Count","description":"Number of buckets owned","default":0},"total_files":{"type":"integer","minimum":0.0,"title":"Total Files","description":"Total files across all buckets","default":0},"total_bytes":{"type":"integer","minimum":0.0,"title":"Total Bytes","description":"Total storage used in bytes","default":0},"buckets":{"items":{"$ref":"#/components/schemas/cbfiles__BucketStorageUsage"},"type":"array","title":"Buckets","description":"Per-bucket breakdown"}},"additionalProperties":false,"type":"object","required":["user_id","user_name","user_email"],"title":"UserStorageUsage","description":"Storage usage summary for a single user.","x-model-type":"cbfiles"},"cbfiles__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbfiles__ZipRequest":{"properties":{"object_names":{"items":{"type":"string"},"type":"array","maxItems":500,"minItems":1,"title":"Object Names","description":"Objects in this bucket to archive into the .zip"},"dest":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dest","description":"Destination object key for the .zip (defaults to zips/<uuid>.zip)"}},"additionalProperties":false,"type":"object","required":["object_names"],"title":"ZipRequest","description":"Request to bundle several existing objects into one .zip object.","x-model-type":"cbfiles"},"cbforge__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbforge__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbforge__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbforge__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbforge__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbforge__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbforge__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbforge__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbforge__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbforge__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbforge__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbforge__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbforge__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbforge__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbfront__AnalyticsExportRequest":{"properties":{"type":{"type":"string","title":"Type","description":"Export type (\"messages\" or \"events\")."},"start":{"type":"number","title":"Start","description":"Start time as Unix timestamp."},"end":{"type":"number","title":"End","description":"End time as Unix timestamp."},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone","description":"Optional timezone (e.g. 'America/New_York')."},"filters":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Filters","description":"Optional additional filters."}},"type":"object","required":["type","start","end"],"title":"AnalyticsExportRequest","description":"Request body for creating an analytics export.\n\nAttributes:\n    type: Export type (\"messages\" or \"events\").\n    start: Start time as Unix timestamp.\n    end: End time as Unix timestamp.\n    timezone: Optional timezone (e.g. \"America/New_York\").\n    filters: Optional additional filters.","examples":[{"end":1719792000,"filters":{"inbox_ids":["inb_55c8c149"]},"start":1717200000,"timezone":"America/New_York","type":"messages"}]},"cbfront__AssignConversationRequest":{"properties":{"assignee_id":{"type":"string","title":"Assignee Id","description":"Teammate ID to assign, or empty string to unassign."}},"type":"object","required":["assignee_id"],"title":"AssignConversationRequest","description":"Request body for assigning a conversation.\n\nAttributes:\n    assignee_id: Teammate ID to assign, or empty string to unassign.","examples":[{"assignee_id":"tea_55c8c149"},{"assignee_id":""}]},"cbfront__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbfront__CreateChannelDraftRequest":{"properties":{"body":{"type":"string","title":"Body","description":"Draft body content."},"author_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Id","description":"Teammate ID."},"to":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"To","description":"Recipient emails."},"cc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Cc","description":"CC recipients."},"bcc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Bcc","description":"BCC recipients."},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Subject line."},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode","description":"Draft mode (\"shared\" or \"private\")."}},"type":"object","required":["body"],"title":"CreateChannelDraftRequest","description":"Request body for creating a draft on a channel (new conversation).\n\nAttributes:\n    body: Draft body content.\n    author_id: Optional teammate ID.\n    to: Optional recipient emails.\n    cc: Optional CC recipients.\n    bcc: Optional BCC recipients.\n    subject: Optional subject line.\n    mode: Optional draft mode (e.g. \"shared\", \"private\").","examples":[{"author_id":"tea_55c8c149","body":"<p>Hello — opening a new thread about the rally.</p>","mode":"shared","subject":"Rally logistics","to":["jordan.rivera@example.com"]}]},"cbfront__CreateCommentRequest":{"properties":{"body":{"type":"string","title":"Body","description":"Comment body text."},"author_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Id","description":"Optional teammate ID for the author."}},"type":"object","required":["body"],"title":"CreateCommentRequest","description":"Request body for creating a comment on a conversation.\n\nAttributes:\n    body: Comment body text (required).\n    author_id: Optional teammate ID for the author.","examples":[{"author_id":"tea_55c8c149","body":"Looping in the field team before we reply."}]},"cbfront__CreateDraftRequest":{"properties":{"body":{"type":"string","title":"Body","description":"Draft body content."},"channel_id":{"type":"string","title":"Channel Id","description":"Channel to create draft in."},"author_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Id","description":"Teammate ID."},"to":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"To","description":"Recipient emails."},"cc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Cc","description":"CC recipients."},"bcc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Bcc","description":"BCC recipients."},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Subject line."},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode","description":"Draft mode (\"shared\" or \"private\")."}},"type":"object","required":["body","channel_id"],"title":"CreateDraftRequest","description":"Request body for creating a draft on a conversation.\n\nAttributes:\n    body: Draft body content.\n    channel_id: Channel to create draft in.\n    author_id: Optional teammate ID.\n    to: Optional recipient emails.\n    cc: Optional CC recipients.\n    bcc: Optional BCC recipients.\n    subject: Optional subject line.\n    mode: Optional draft mode (e.g. \"shared\", \"private\").","examples":[{"author_id":"tea_55c8c149","body":"<p>Thanks for reaching out — here is our response.</p>","channel_id":"cha_55c8c149","mode":"shared","subject":"Re: Press release review"}]},"cbfront__DeleteDraftRequest":{"properties":{"version":{"type":"string","title":"Version","description":"Draft version for optimistic concurrency."}},"type":"object","required":["version"],"title":"DeleteDraftRequest","description":"Request body for deleting a draft.\n\nFront uses optimistic concurrency — the version must match.\n\nAttributes:\n    version: Draft version string for optimistic concurrency.","examples":[{"version":"1719795600"}]},"cbfront__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbfront__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbfront__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbfront__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbfront__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbfront__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbfront__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbfront__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbfront__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbfront__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbfront__ReplyRequest":{"properties":{"body":{"type":"string","title":"Body","description":"Reply body content."},"to":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"To","description":"Override recipients."},"cc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Cc","description":"CC recipients."},"bcc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Bcc","description":"BCC recipients."},"author_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Id","description":"Teammate ID to send on behalf of."},"channel_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel Id","description":"Channel to send reply from."},"body_format":{"type":"string","title":"Body Format","description":"Body format (\"html\" or \"markdown\").","default":"html"}},"type":"object","required":["body"],"title":"ReplyRequest","description":"Request body for replying to a conversation.\n\nAttributes:\n    body: Reply body content.\n    to: Optional override of recipients.\n    cc: Optional CC recipients.\n    bcc: Optional BCC recipients.\n    author_id: Optional teammate ID to send on behalf of.\n    channel_id: Optional channel to send reply from.\n    body_format: Body format, defaults to \"html\".","examples":[{"author_id":"tea_55c8c149","body":"<p>Thanks — confirming the details now.</p>","body_format":"html"}]},"cbfront__SendMessageRequest":{"properties":{"to":{"items":{"type":"string"},"type":"array","title":"To","description":"Recipient email addresses."},"body":{"type":"string","title":"Body","description":"Message body content."},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Subject line."},"cc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Cc","description":"CC recipients."},"bcc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Bcc","description":"BCC recipients."},"author_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Id","description":"Teammate ID to send on behalf of."},"body_format":{"type":"string","title":"Body Format","description":"Body format (\"html\" or \"markdown\").","default":"html"}},"type":"object","required":["to","body"],"title":"SendMessageRequest","description":"Request body for sending a new message via a channel.\n\nAttributes:\n    to: List of recipient email addresses.\n    body: Message body content.\n    subject: Optional subject line.\n    cc: Optional CC recipients.\n    bcc: Optional BCC recipients.\n    author_id: Optional teammate ID to send on behalf of.\n    body_format: Body format, defaults to \"html\".","examples":[{"author_id":"tea_55c8c149","body":"<p>Hello — reaching out about the rally.</p>","body_format":"html","subject":"Rally logistics","to":["jordan.rivera@example.com"]}]},"cbfront__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbfront__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbfront__TagIdsRequest":{"properties":{"tag_ids":{"items":{"type":"string"},"type":"array","title":"Tag Ids","description":"List of tag IDs."}},"type":"object","required":["tag_ids"],"title":"TagIdsRequest","description":"Request body for applying or removing tags.\n\nAttributes:\n    tag_ids: List of tag IDs to apply or remove.","examples":[{"tag_ids":["tag_1a2b3c","tag_4d5e6f"]}]},"cbfront__UpdateConversationRequest":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"New conversation status."},"assignee_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee Id","description":"Teammate ID to assign."},"inbox_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Inbox Id","description":"Move to this inbox."},"tag_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tag Ids","description":"Replace all tags with this list."},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Update the subject line."}},"type":"object","title":"UpdateConversationRequest","description":"Request body for updating a conversation.\n\nAll fields are optional — only provided fields are sent to Front.\n\nAttributes:\n    status: New conversation status.\n    assignee_id: Teammate ID to assign, or empty string to unassign.\n    inbox_id: Move conversation to this inbox.\n    tag_ids: Replace all tags with this list.\n    subject: Update the subject line.","examples":[{"status":"archived"},{"assignee_id":"tea_55c8c149","subject":"Press release review (final)","tag_ids":["tag_1a2b3c"]}]},"cbfront__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbgeo__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbgeo__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbgeo__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbgeo__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbgeo__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbgeo__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbgeo__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbgeo__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbgeo__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbgeo__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbgeo__ReverseResult":{"properties":{"place_id":{"type":"integer","title":"Place Id","description":"Internal Nominatim place ID"},"licence":{"type":"string","title":"Licence","description":"Data license information"},"osm_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Osm Type","description":"OpenStreetMap object type (node/way/relation)"},"osm_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Osm Id","description":"OpenStreetMap object ID"},"lat":{"type":"string","title":"Lat","description":"Latitude of the place"},"lon":{"type":"string","title":"Lon","description":"Longitude of the place"},"display_name":{"type":"string","title":"Display Name","description":"Full formatted address"},"address":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Address","description":"Address breakdown (when addressdetails=1)"},"boundingbox":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Boundingbox","description":"Bounding box [south,north,west,east]"}},"type":"object","required":["place_id","licence","lat","lon","display_name"],"title":"ReverseResult","description":"A reverse geocoding result."},"cbgeo__SearchResult":{"properties":{"place_id":{"type":"integer","title":"Place Id","description":"Internal Nominatim place ID"},"licence":{"type":"string","title":"Licence","description":"Data license information"},"osm_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Osm Type","description":"OpenStreetMap object type (node/way/relation)"},"osm_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Osm Id","description":"OpenStreetMap object ID"},"lat":{"type":"string","title":"Lat","description":"Latitude of the place"},"lon":{"type":"string","title":"Lon","description":"Longitude of the place"},"display_name":{"type":"string","title":"Display Name","description":"Full formatted address"},"place_rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Place Rank","description":"Search rank of this place"},"importance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Importance","description":"Importance score (0-1)"},"address":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Address","description":"Address breakdown (when addressdetails=1)"},"boundingbox":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Boundingbox","description":"Bounding box [south,north,west,east]"}},"type":"object","required":["place_id","licence","lat","lon","display_name"],"title":"SearchResult","description":"A single geocoding search result."},"cbgeo__StatusResult":{"properties":{"status":{"type":"integer","title":"Status","description":"Status code (0 = OK)"},"message":{"type":"string","title":"Message","description":"Status message"},"data_updated":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Updated","description":"Last data update timestamp"},"software_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Software Version","description":"Nominatim version"},"database_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Database Version","description":"Database version"}},"type":"object","required":["status","message"],"title":"StatusResult","description":"API status information."},"cbgeo__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbgreenroom__Approval":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"hit_id":{"type":"string","format":"uuid","title":"Hit Id","description":"The Hit this approval gates.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"requested_by":{"type":"string","format":"uuid","title":"Requested By","description":"User principal who requested approval. Greenroom Entity Staff is rejected; checked against role.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"requested_at":{"type":"string","format":"date-time","title":"Requested At","description":"UTC timestamp when the approval request was created.","examples":["2026-06-21T14:30:00Z"]},"decisions":{"items":{"$ref":"#/components/schemas/cbgreenroom__ApprovalDecision"},"type":"array","maxItems":2,"minItems":2,"title":"Decisions","description":"Exactly two decisions, one per approver. Initialized as PENDING when the request is created.","examples":[[{"approver_user_id":"550e8400-e29b-41d4-a716-446655440000","disposition":"pending"},{"approver_user_id":"6ba7b810-9dad-11d1-80b4-00c04fd430c8","disposition":"pending"}]]},"outcome":{"$ref":"#/components/schemas/cbgreenroom__ApprovalOutcome","description":"Aggregate outcome computed from decisions. Updated by ApprovalService when a decision arrives.","default":"pending","examples":["pending","cleared"]},"cleared_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cleared At","description":"UTC timestamp when outcome became CLEARED. None until cleared.","examples":["2026-06-21T14:30:00Z"]},"cleared_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Cleared By","description":"The approver whose APPROVED decision cleared the request.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"UTC timestamp after which the approval request is considered stale. Stale requests do not auto-decline; the operator decides whether to re-request or cancel.","examples":["2026-06-22T14:30:00Z"]},"suppressed_in_demo":{"type":"boolean","title":"Suppressed In Demo","description":"True if the approver notification was suppressed because the tenant was in demo mode or outbound_locked. The Approval record exists regardless; only the outbound notification is suppressed.","default":false,"examples":[false]}},"additionalProperties":false,"type":"object","required":["tenant_id","hit_id","requested_by","requested_at","decisions"],"title":"Approval","description":"A dual-approval request for a Hit.\n\nCreated when a Hit transitions toward BOOKED (or, for walk-ins, recorded\nafter the fact for compliance). Carries both ApprovalDecision children.\nOutcome is computed from the decisions:\n\n- PENDING while no approver has decided\n- CLEARED when at least one approver APPROVED\n- DECLINED when an approver DECLINED before any approver approved\n\nThe mobile no-login flow uses cblinks signed-action tokens; the signed_token_id\non each ApprovalDecision references the consumed token."},"cbgreenroom__ApprovalChannel":{"type":"string","enum":["sms","email","web"],"title":"ApprovalChannel","description":"Channel through which an approval decision was captured."},"cbgreenroom__ApprovalDecision":{"properties":{"approver_user_id":{"type":"string","format":"uuid","title":"Approver User Id","description":"User principal of the approver.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"disposition":{"$ref":"#/components/schemas/cbgreenroom__ApprovalDisposition","description":"Current disposition for this approver.","default":"pending","examples":["pending","approved"]},"decided_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Decided At","description":"UTC timestamp when this approver decided. None while PENDING.","examples":["2026-06-21T14:30:00Z"]},"decided_via_channel":{"anyOf":[{"$ref":"#/components/schemas/cbgreenroom__ApprovalChannel"},{"type":"null"}],"description":"Channel through which the decision was captured. None while PENDING.","examples":["sms","email"]},"signed_token_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed Token Id","description":"cblinks signed_action token consumed to record this decision. None for web-channel decisions.","examples":["tok_9f8b2c1a7d4e"]},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes","description":"Optional notes captured alongside the decision.","examples":["Approved pending confirmation of the topic brief."]}},"additionalProperties":false,"type":"object","required":["approver_user_id"],"title":"ApprovalDecision","description":"A single approver's decision on an approval request.\n\nEmbedded as a list on Approval. Two ApprovalDecision records per Approval:\none per approver. Initial state is PENDING; updated to APPROVED or DECLINED\nwhen the approver acts."},"cbgreenroom__ApprovalDisposition":{"type":"string","enum":["pending","approved","declined"],"title":"ApprovalDisposition","description":"Per-approver decision on a dual-approval request."},"cbgreenroom__ApprovalOutcome":{"type":"string","enum":["pending","cleared","declined"],"title":"ApprovalOutcome","description":"Aggregate outcome of a dual-approval request.\n\nEither-clears-both-see semantics: CLEARED if at least one approver\napproves; DECLINED if at least one declines and none approve before that;\nPENDING while no approver has decided."},"cbgreenroom__AssistantPrincipalRole":{"properties":{"type":{"type":"string","const":"assistant_principal","title":"Type","description":"Discriminator.","default":"assistant_principal","examples":["assistant_principal"]},"delegating_principal_id":{"type":"string","format":"uuid","title":"Delegating Principal Id","description":"The candidate or principal this assistant acts on behalf of.","examples":["550e8400-e29b-41d4-a716-446655440000"]}},"additionalProperties":false,"type":"object","required":["delegating_principal_id"],"title":"AssistantPrincipalRole","description":"The candidate's assistant.\n\nAuthenticated as a distinct principal from the candidate, with role-scoped\npermissions and audited as a distinct actor."},"cbgreenroom__Booker-Input":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"full_name":{"type":"string","maxLength":300,"title":"Full Name","description":"Full name as known publicly.","examples":["Sam Rivers"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Editorial email. None if not publicly available or not verified.","examples":["sam.rivers@morningdrive.example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Editorial phone. E.164 format.","examples":["+12025550143"]},"role_tag":{"$ref":"#/components/schemas/cbgreenroom__ContactRoleTag","description":"Editorial vs commercial. Editorial roles are appropriate pitch targets; commercial roles are not. This drives outbound permission checks.","examples":["editorial","commercial"]},"biography":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Biography","description":"Editorial biography. Pulled into briefing packets.","examples":["Veteran broadcaster covering Iowa politics since 2008."]},"twitter_handle":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Twitter Handle","description":"Twitter/X handle without the @.","examples":["samriversradio"]},"notes":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Notes","description":"Internal notes. Visible to operators; never included in outbound or briefings.","examples":["Prefers email contact; declines weekend bookings."]},"provenance":{"$ref":"#/components/schemas/cbgreenroom__TierProvenance","description":"Provenance stamp. Required on every contact record.","examples":[{"source":"cbintel","tier":"verified"}]},"primary_show_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Show Id","description":"Reference to the show this booker primarily handles.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"typical_lead_time_days":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Typical Lead Time Days","description":"Approximate number of days of lead time this booker typically requires.","examples":[7]}},"additionalProperties":false,"type":"object","required":["tenant_id","full_name","role_tag","provenance"],"title":"Booker","description":"A show booker or assignment editor.\n\nThe transactional contact for getting a guest scheduled. May or may not be\nthe same as the producer."},"cbgreenroom__Booker-Output":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"full_name":{"type":"string","maxLength":300,"title":"Full Name","description":"Full name as known publicly.","examples":["Sam Rivers"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Editorial email. None if not publicly available or not verified.","examples":["sam.rivers@morningdrive.example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Editorial phone. E.164 format.","examples":["+12025550143"]},"role_tag":{"$ref":"#/components/schemas/cbgreenroom__ContactRoleTag","description":"Editorial vs commercial. Editorial roles are appropriate pitch targets; commercial roles are not. This drives outbound permission checks.","examples":["editorial","commercial"]},"biography":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Biography","description":"Editorial biography. Pulled into briefing packets.","examples":["Veteran broadcaster covering Iowa politics since 2008."]},"twitter_handle":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Twitter Handle","description":"Twitter/X handle without the @.","examples":["samriversradio"]},"notes":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Notes","description":"Internal notes. Visible to operators; never included in outbound or briefings.","examples":["Prefers email contact; declines weekend bookings."]},"provenance":{"$ref":"#/components/schemas/cbgreenroom__TierProvenance","description":"Provenance stamp. Required on every contact record.","examples":[{"source":"cbintel","tier":"verified"}]},"primary_show_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Show Id","description":"Reference to the show this booker primarily handles.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"typical_lead_time_days":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Typical Lead Time Days","description":"Approximate number of days of lead time this booker typically requires.","examples":[7]}},"additionalProperties":false,"type":"object","required":["tenant_id","full_name","role_tag","provenance"],"title":"Booker","description":"A show booker or assignment editor.\n\nThe transactional contact for getting a guest scheduled. May or may not be\nthe same as the producer."},"cbgreenroom__BriefingPacket":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"hit_id":{"type":"string","format":"uuid","title":"Hit Id","description":"The Hit this packet briefs.","examples":["aa11bb22-cc33-4d44-9e55-66f7788990aa"]},"show_id":{"type":"string","format":"uuid","title":"Show Id","description":"Show being appeared on. Cached for query convenience.","examples":["bb22cc33-dd44-4e55-9f66-7788990011bb"]},"candidate_principal_id":{"type":"string","format":"uuid","title":"Candidate Principal Id","description":"The principal who will appear.","examples":["cc33dd44-ee55-4f66-9077-8899001122cc"]},"messaging_snapshot_id":{"type":"string","format":"uuid","title":"Messaging Snapshot Id","description":"MessagingSnapshot pinned at packet generation time.","examples":["dd44ee55-ff66-4077-9188-990011223344"]},"sections":{"items":{"$ref":"#/components/schemas/cbgreenroom__BriefingSection"},"type":"array","title":"Sections","description":"Structured packet content.","examples":[[{"order":0,"section_kind":"talking_points","title":"Key Talking Points"}]]},"target_air_time":{"type":"string","format":"date-time","title":"Target Air Time","description":"UTC timestamp when the appearance airs. Used by the cbcron T-2hr trigger.","examples":["2026-06-21T14:00:00Z"]},"delivered_to_assistant_email":{"type":"string","title":"Delivered To Assistant Email","description":"Email address of the assistant who receives the packet.","examples":["assistant@candidate.example.com"]},"delivery_status":{"$ref":"#/components/schemas/cbgreenroom__PacketDeliveryStatus","description":"Lifecycle of generation and delivery.","examples":[{"generated_at":"2026-06-21T12:00:00Z","status":"generated"}]},"disclosure_injected":{"type":"boolean","title":"Disclosure Injected","description":"True if a disclosure paragraph was injected into the packet because the show is conflict-flagged.","default":false,"examples":[false]},"disclosure_template_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Disclosure Template Id","description":"DisclosureTemplate used, when injected.","examples":["b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"]}},"additionalProperties":false,"type":"object","required":["tenant_id","hit_id","show_id","candidate_principal_id","messaging_snapshot_id","target_air_time","delivered_to_assistant_email"],"title":"BriefingPacket","description":"A briefing packet for a single Hit.\n\nGenerated by BriefingService at T-2hr (or on demand for walk-ins). Pulls\nfrom the active MessagingSnapshot for current talking points, from the\nshow graph for host context, from cbintel for recent episodes, and from\ncbindex for back-catalog search. Delivered to the assistant via cbfront\n(email + PDF) and produces a calendar event with prep block via cbcalendar."},"cbgreenroom__BriefingSection":{"properties":{"section_kind":{"type":"string","title":"Section Kind","description":"What kind of section this is. 'host_bio', 'recent_episodes', 'likely_questions', 'talking_points', 'bridges', 'prior_history', 'disclosure_paragraph', 'no_go_topics', or free-form. Free string for v1; enum candidate for v2.","examples":["talking_points"]},"title":{"type":"string","maxLength":200,"title":"Title","description":"Display title for the section.","examples":["Key Talking Points"]},"body":{"type":"string","maxLength":20000,"title":"Body","description":"Section body. Markdown or plain text.","examples":["- Lead with the jobs message\n- Pivot any healthcare question to costs"]},"order":{"type":"integer","minimum":0.0,"title":"Order","description":"Display order within the packet. Lower numbers come first.","examples":[0]},"structured_payload":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Structured Payload","description":"Optional structured content for downstream renderers. e.g. for 'recent_episodes' a list of episode summaries with timestamps.","examples":[{"episodes":[{"aired_at":"2026-06-14T09:00:00Z","title":"Ep. 412"}]}]},"source_citations":{"items":{"type":"string"},"type":"array","title":"Source Citations","description":"Source URLs or cb:// URIs supporting the content of this section.","examples":[["cb://greenroom/shows/abc-123","https://who-am.example.com/about"]]}},"additionalProperties":false,"type":"object","required":["section_kind","title","body","order"],"title":"BriefingSection","description":"A single section of a briefing packet.\n\nSections are typed via section_kind. Each section has a title and a body\nplus optional structured payload for downstream rendering."},"cbgreenroom__CampaignApproverRole":{"properties":{"type":{"type":"string","const":"campaign_approver","title":"Type","description":"Discriminator.","default":"campaign_approver","examples":["campaign_approver"]},"approval_priority":{"type":"integer","maximum":2.0,"minimum":1.0,"title":"Approval Priority","description":"Either approver clears under either-clears-both-see semantics, but this field determines display order in the approval UI.","default":1,"examples":[1,2]}},"additionalProperties":false,"type":"object","title":"CampaignApproverRole","description":"An approver in the dual-approval chain (e.g., Adam, John)."},"cbgreenroom__CampaignOperatorRole":{"properties":{"type":{"type":"string","const":"campaign_operator","title":"Type","description":"Discriminator.","default":"campaign_operator","examples":["campaign_operator"]},"can_pitch":{"type":"boolean","title":"Can Pitch","description":"Whether this operator can initiate outbound pitches.","default":true,"examples":[true]},"can_book_walkin":{"type":"boolean","title":"Can Book Walkin","description":"Whether this operator can record walk-in bookings.","default":true,"examples":[true]}},"additionalProperties":false,"type":"object","title":"CampaignOperatorRole","description":"The campaign manager or staffer who operates the system day-to-day."},"cbgreenroom__CloseRequest":{"properties":{"closure_reason":{"type":"string","maxLength":500,"title":"Closure Reason","description":"Why the pitch is being closed.","examples":["Booked elsewhere","No response after three attempts"]}},"type":"object","required":["closure_reason"],"title":"CloseRequest","description":"Body for closing a Pitch with a closure reason."},"cbgreenroom__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbgreenroom__ComposeDigestRequest":{"properties":{"target_user_id":{"type":"string","format":"uuid","title":"Target User Id","description":"The operator the digest is composed for.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"period_start":{"type":"string","format":"date-time","title":"Period Start","description":"Start of the digest period (UTC).","examples":["2026-06-14T00:00:00Z"]},"period_end":{"type":"string","format":"date-time","title":"Period End","description":"End of the digest period (UTC).","examples":["2026-06-21T00:00:00Z"]},"touchpoints":{"items":{"$ref":"#/components/schemas/cbgreenroom__Touchpoint"},"type":"array","title":"Touchpoints","description":"Touchpoints to bundle into the digest.","examples":[[]]},"summary":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Summary","description":"Optional human-readable summary of the digest.","examples":["Three stalled pitches and one missing thank-you this week."]},"suppressed_in_demo":{"type":"boolean","title":"Suppressed In Demo","description":"When True, suppress real delivery of the digest in DEMO mode.","default":false,"examples":[false]}},"type":"object","required":["target_user_id","period_start","period_end"],"title":"ComposeDigestRequest","description":"Body for composing a TouchpointDigest for a target operator."},"cbgreenroom__ConflictFlag":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"show_id":{"type":"string","format":"uuid","title":"Show Id","description":"The Show this flag attaches to.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"rationale":{"type":"string","maxLength":4000,"title":"Rationale","description":"Internal rationale for the conflict. Not surfaced to outbound. Used in audit records and counsel review.","examples":["Show is co-owned by a RuralAMFM affiliate; licensee overlap."]},"disclosure_template_id":{"type":"string","format":"uuid","title":"Disclosure Template Id","description":"Reference to the DisclosureTemplate that applies for this conflict.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"flagged_by":{"type":"string","format":"uuid","title":"Flagged By","description":"User principal that flagged the conflict.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"flagged_at":{"type":"string","format":"date-time","title":"Flagged At","description":"UTC timestamp when the flag was created.","examples":["2026-06-21T14:30:00Z"]},"cleared_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cleared At","description":"UTC timestamp if the flag has since been cleared. None while active.","examples":["2026-06-25T10:00:00Z"]},"cleared_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Cleared By","description":"User principal that cleared the flag. None while active.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]}},"additionalProperties":false,"type":"object","required":["tenant_id","show_id","rationale","disclosure_template_id","flagged_by","flagged_at"],"title":"ConflictFlag","description":"A conflict-of-interest flag attached to a Show.\n\nSet when a show's relationships, ownership, or hosts trigger a conflict\nwith the licensee. Drives disclosure-paragraph injection on outbound and\nbriefing packets that involve the show.\n\nPer-show conflict flags live on the Show record itself as a boolean; this\nrecord carries the rationale and template selection."},"cbgreenroom__ContactRoleTag":{"type":"string","enum":["editorial","commercial"],"title":"ContactRoleTag","description":"Editorial vs commercial role of a media contact.\n\nEditorial roles (host, producer, news director) are appropriate targets for\npress pitches. Commercial roles (sales, ad ops, business development) are\nnot. This tag drives outbound permission checks."},"cbgreenroom__CounselApproveBody":{"properties":{"counsel_approval_reference":{"type":"string","maxLength":500,"minLength":1,"title":"Counsel Approval Reference","description":"External reference to the counsel approval — memo number, email subject, signed-document id. Recorded on the template."}},"type":"object","required":["counsel_approval_reference"],"title":"CounselApproveBody","description":"Body for the counsel-approve endpoint."},"cbgreenroom__CreatePitchResponse":{"properties":{"pitch":{"$ref":"#/components/schemas/cbgreenroom__Pitch","description":"The newly created Pitch record."},"hit":{"$ref":"#/components/schemas/cbgreenroom__Hit","description":"The Hit linked to the Pitch, initialized at IDENTIFIED."}},"type":"object","required":["pitch","hit"],"title":"CreatePitchResponse","description":"Response for creating a Pitch — the new Pitch plus its linked Hit."},"cbgreenroom__CreateSnapshotRequest":{"properties":{"label":{"type":"string","maxLength":200,"title":"Label","description":"Human-readable label identifying the snapshot.","examples":["Q3 debate prep"]},"pinned_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pinned By","description":"Principal identifier of the user creating the snapshot.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes","description":"Optional free-text notes describing why the snapshot was taken.","examples":["Locked ahead of the Tuesday town hall."]}},"type":"object","required":["label"],"title":"CreateSnapshotRequest","description":"Request body for pinning the current messaging state into a snapshot."},"cbgreenroom__DecisionBody":{"properties":{"disposition":{"$ref":"#/components/schemas/cbgreenroom__ApprovalDisposition","description":"This approver's decision.","examples":["approved","declined"]},"channel":{"$ref":"#/components/schemas/cbgreenroom__ApprovalChannel","description":"Channel through which the decision was captured.","examples":["sms","email","web"]},"signed_token_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed Token Id","description":"cblinks signed-action token id, set by the mobile no-login flow.","examples":["lnk_3f9a2c8e7b1d"]},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes","description":"Optional free-text note attached to the decision.","examples":["Cleared after confirming no conflict of interest."]}},"type":"object","required":["disposition","channel"],"title":"DecisionBody","description":"Body for the mobile no-login decision endpoint.\n\nThe signed-link mobile flow lands here carrying a ``signed_token_id``.\nv1 records the token id; cblinks-side verification is wired in Wave 4."},"cbgreenroom__DisclosureTemplate":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"name":{"type":"string","maxLength":200,"title":"Name","description":"Human-readable template name.","examples":["Standard licensee conflict disclosure"]},"version_label":{"type":"string","maxLength":50,"title":"Version Label","description":"Free-form version label, e.g. 'v1', 'counsel-2026-04-15'.","examples":["counsel-2026-04-15"]},"body":{"type":"string","maxLength":8000,"title":"Body","description":"Template body. Plain text or HTML. Accepts {placeholder} substitutions for licensee_legal_entity, candidate_name, show_name.","examples":["This appearance is facilitated by {licensee_legal_entity}, which has a business relationship with {show_name}."]},"counsel_approved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Counsel Approved At","description":"UTC timestamp when counsel approved this template. Templates without counsel approval can be created in demo mode but cannot be injected into live-mode outbound.","examples":["2026-04-15T09:00:00Z"]},"counsel_approval_reference":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Counsel Approval Reference","description":"External reference to the counsel approval (memo number, email, signed doc).","examples":["COUNSEL-MEMO-2026-0042"]},"active":{"type":"boolean","title":"Active","description":"Whether this template is currently selectable.","default":true,"examples":[true]},"placeholder_keys":{"items":{"type":"string"},"type":"array","title":"Placeholder Keys","description":"The placeholder keys this template uses. ComplianceService validates substitution.","examples":[["licensee_legal_entity","candidate_name","show_name"]]}},"additionalProperties":false,"type":"object","required":["tenant_id","name","version_label","body"],"title":"DisclosureTemplate","description":"A disclosure paragraph template.\n\nUsed by ComplianceService to render a disclosure paragraph for injection\ninto outbound and briefing packets when a Hit involves a conflict-flagged\nshow. Templates are versioned at the record level via version_label."},"cbgreenroom__Episode-Input":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"show_id":{"type":"string","format":"uuid","title":"Show Id","description":"Reference to the parent Show.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"title":{"type":"string","maxLength":500,"title":"Title","description":"Episode title.","examples":["Episode 412: The Broadband Debate"]},"aired_at":{"type":"string","format":"date-time","title":"Aired At","description":"UTC timestamp when the episode aired or was published.","examples":["2026-06-21T14:30:00Z"]},"duration_seconds":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Duration Seconds","description":"Episode duration in seconds.","examples":[3600]},"summary":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Summary","description":"Editorial summary. May be ingested or generated; if generated, source must be cited.","examples":["Panel discusses statewide broadband funding proposals."]},"transcript_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Uri","description":"cb:// URI to a stored transcript in cbfiles. None if unavailable.","examples":["cb://cbfiles/greenroom/transcripts/550e8400.txt"]},"topics":{"items":{"type":"string"},"type":"array","title":"Topics","description":"Tags for episode topics. Set conservatively; do not over-tag.","examples":[["broadband","rural-policy"]]},"guests":{"items":{"type":"string"},"type":"array","title":"Guests","description":"Names of guests on this episode, if known.","examples":[["Sen. Perry","Mayor Lin"]]},"provenance":{"$ref":"#/components/schemas/cbgreenroom__TierProvenance","description":"Provenance stamp. Required on every Episode record.","examples":[{"source":"cbintel","tier":"verified"}]}},"additionalProperties":false,"type":"object","required":["tenant_id","show_id","title","aired_at","provenance"],"title":"Episode","description":"A single episode of a show.\n\nCreated during ingestion (cbintel crawl) for shows with serial release.\nUsed by the briefing pipeline to surface 'last 3 episodes' and to feed\ncbindex back-catalog search."},"cbgreenroom__Episode-Output":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"show_id":{"type":"string","format":"uuid","title":"Show Id","description":"Reference to the parent Show.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"title":{"type":"string","maxLength":500,"title":"Title","description":"Episode title.","examples":["Episode 412: The Broadband Debate"]},"aired_at":{"type":"string","format":"date-time","title":"Aired At","description":"UTC timestamp when the episode aired or was published.","examples":["2026-06-21T14:30:00Z"]},"duration_seconds":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Duration Seconds","description":"Episode duration in seconds.","examples":[3600]},"summary":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Summary","description":"Editorial summary. May be ingested or generated; if generated, source must be cited.","examples":["Panel discusses statewide broadband funding proposals."]},"transcript_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transcript Uri","description":"cb:// URI to a stored transcript in cbfiles. None if unavailable.","examples":["cb://cbfiles/greenroom/transcripts/550e8400.txt"]},"topics":{"items":{"type":"string"},"type":"array","title":"Topics","description":"Tags for episode topics. Set conservatively; do not over-tag.","examples":[["broadband","rural-policy"]]},"guests":{"items":{"type":"string"},"type":"array","title":"Guests","description":"Names of guests on this episode, if known.","examples":[["Sen. Perry","Mayor Lin"]]},"provenance":{"$ref":"#/components/schemas/cbgreenroom__TierProvenance","description":"Provenance stamp. Required on every Episode record.","examples":[{"source":"cbintel","tier":"verified"}]}},"additionalProperties":false,"type":"object","required":["tenant_id","show_id","title","aired_at","provenance"],"title":"Episode","description":"A single episode of a show.\n\nCreated during ingestion (cbintel crawl) for shows with serial release.\nUsed by the briefing pipeline to surface 'last 3 episodes' and to feed\ncbindex back-catalog search."},"cbgreenroom__GenerateRequest":{"properties":{"snapshot_id":{"type":"string","format":"uuid","title":"Snapshot Id","description":"The research snapshot to build the packet from.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"target_air_time":{"type":"string","format":"date-time","title":"Target Air Time","description":"When the appearance airs (UTC).","examples":["2026-06-21T14:30:00Z"]},"delivered_to_assistant_email":{"type":"string","maxLength":300,"minLength":3,"title":"Delivered To Assistant Email","description":"Email the rendered packet is delivered to.","examples":["assistant@perryforsenate.org"]},"disclosure_template_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Disclosure Template Id","description":"Optional DisclosureTemplate to inject into the packet.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"disclosure_context":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Disclosure Context","description":"Placeholder values substituted into the disclosure body.","examples":[{"candidate_name":"Perry","office":"U.S. Senate"}]},"disclosure_conflict_flag_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Disclosure Conflict Flag Id","description":"Optional conflict-flag id tied to the disclosure.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"cbcalendar_tenant_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbcalendar Tenant Slug","description":"Optional override for cbcalendar's slug-shaped tenant id (e.g., `perry-2026`). When omitted, falls back to ``Tenant.cbcalendar_tenant_slug``. cbcalendar uses slugs that are independent of cbgreenroom's UUID tenant ids."},"cbcalendar_calendar_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbcalendar Calendar Id","description":"Optional override for the cbcalendar calendar_id used when creating events. When omitted, falls back to ``Tenant.cbcalendar_primary_calendar_id``. With both a resolved slug + calendar id AND ``settings.enable_cbcalendar`` on, the service creates a primary appearance event + prep block in cbcalendar via mesh and stamps both event ids onto the packet's delivery_status. If neither override nor tenant default resolves, the calendar integration is skipped."},"cbcalendar_attendees":{"anyOf":[{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array"},{"type":"null"}],"title":"Cbcalendar Attendees","description":"Optional attendee list for the cbcalendar event (each entry is a dict with at minimum ``email``). Defaults to a single attendee matching ``delivered_to_assistant_email``."},"cbcalendar_prep_minutes":{"type":"integer","maximum":240.0,"minimum":15.0,"title":"Cbcalendar Prep Minutes","description":"Lead-time minutes for the prep block. Default 60.","default":60}},"type":"object","required":["snapshot_id","target_air_time","delivered_to_assistant_email"],"title":"GenerateRequest","description":"Body for operator-triggered briefing-packet generation."},"cbgreenroom__GreenroomEntityStaffRole":{"properties":{"type":{"type":"string","const":"greenroom_entity_staff","title":"Type","description":"Discriminator.","default":"greenroom_entity_staff","examples":["greenroom_entity_staff"]},"can_configure":{"type":"boolean","title":"Can Configure","description":"Whether this staffer can modify tenant configuration (templates, recusal lists).","default":true,"examples":[true]}},"additionalProperties":false,"type":"object","title":"GreenroomEntityStaffRole","description":"Greenroom Entity personnel.\n\nLoad-bearing authz boundary. Principals with this role cannot appear as\napprover, sender, booker, or pitcher on any record. Configuration and\nsupport actions only. Enforced at the validation layer in services that\ntake an actor reference."},"cbgreenroom__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbgreenroom__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbgreenroom__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbgreenroom__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbgreenroom__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbgreenroom__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbgreenroom__Hit":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"show_id":{"type":"string","format":"uuid","title":"Show Id","description":"Reference to the target Show.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"candidate_principal_id":{"type":"string","format":"uuid","title":"Candidate Principal Id","description":"The principal who would appear on the show (the candidate).","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"state":{"$ref":"#/components/schemas/cbgreenroom__HitState","description":"Current pipeline state. Walk-in entries set this to BOOKED at creation; everything else starts at IDENTIFIED.","default":"identified","examples":["identified","booked"]},"source":{"$ref":"#/components/schemas/cbgreenroom__HitSource","description":"Origin attribution. Required on every Hit. Drives sourced-vs-managed reporting and conditions which downstream flows fire.","examples":["greenroom_pitched","walk_in"]},"scheduled_for":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled For","description":"UTC timestamp of the scheduled appearance. None until BOOKED.","examples":["2026-06-21T14:30:00Z"]},"duration_minutes":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Duration Minutes","description":"Expected duration of the appearance.","examples":[15,30]},"location":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Location","description":"Physical or remote location of the appearance. Studio address, dial-in details, video conference URL, etc.","examples":["Studio 4B, 100 Broadcast Plaza, Des Moines, IA"]},"topic_brief":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Topic Brief","description":"Editorial topic for the appearance. Free text.","examples":["Discussing the campaign's rural broadband plan."]},"referrer_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Referrer User Id","description":"If source is REFERRAL, the user principal who made the referral.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"referrer_external_name":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Referrer External Name","description":"If source is REFERRAL and the referrer is not a user, their name.","examples":["Jane Doe"]},"pitch_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pitch Id","description":"If source is GREENROOM_PITCHED, reference to the originating Pitch.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"approval_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Approval Id","description":"Reference to the Approval record once the dual-approval flow has been initiated.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"briefing_packet_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Briefing Packet Id","description":"Reference to the BriefingPacket once the T-2hr trigger has fired.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"calendar_event_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Calendar Event Id","description":"cbcalendar event ID for the primary appearance event.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"prep_block_event_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Prep Block Event Id","description":"cbcalendar event ID for the linked prep-block event.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"transitions":{"items":{"$ref":"#/components/schemas/cbgreenroom__HitStateTransition"},"type":"array","title":"Transitions","description":"Append-only log of state transitions. The HitPipelineService maintains this.","examples":[[{"to_state":"identified","transitioned_at":"2026-06-21T14:30:00Z"}]]},"aired_clip_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Aired Clip Uri","description":"cb:// URI to the post-air clip in cbfiles. Set during the CLIPPED transition.","examples":["cb://cbfiles/greenroom/clips/550e8400.mp4"]},"report_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Report Uri","description":"cb:// URI to the post-hit report. Set during the REPORTED transition.","examples":["cb://cbfiles/greenroom/reports/550e8400.pdf"]}},"additionalProperties":false,"type":"object","required":["tenant_id","show_id","candidate_principal_id","source"],"title":"Hit","description":"A booking opportunity through its full lifecycle.\n\nCreated when a show is identified as a target, when a pitch is sent, when a\nwalk-in booking is recorded, when an invitation arrives directly, or when\na referral comes in. Every record carries source attribution; the eight-\nstate lifecycle drives downstream flows."},"cbgreenroom__HitSource":{"type":"string","enum":["greenroom_pitched","walked_in_external","direct_candidate_invite","referral"],"title":"HitSource","description":"Origin attribution for a Hit.\n\nRequired on every Hit. Drives the sourced-vs-managed split in pipeline\nreporting and determines which downstream flows fire."},"cbgreenroom__HitState":{"type":"string","enum":["identified","pitched","interested","booked","prepped","aired","clipped","reported"],"title":"HitState","description":"Lifecycle state of a Hit.\n\nThe eight-state pipeline. Walk-in entries initialize at BOOKED; everything\nelse initializes at IDENTIFIED. Transitions are enforced by HitPipelineService."},"cbgreenroom__HitStateTransition":{"properties":{"from_state":{"anyOf":[{"$ref":"#/components/schemas/cbgreenroom__HitState"},{"type":"null"}],"description":"Prior state. None if this is the initial transition.","examples":["identified"]},"to_state":{"$ref":"#/components/schemas/cbgreenroom__HitState","description":"New state.","examples":["pitched"]},"transitioned_at":{"type":"string","format":"date-time","title":"Transitioned At","description":"UTC timestamp of the transition.","examples":["2026-06-21T14:30:00Z"]},"transitioned_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Transitioned By","description":"User principal that triggered the transition. None for system-driven transitions.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes","description":"Optional human notes on why the transition happened.","examples":["Booker confirmed the slot by phone."]}},"additionalProperties":false,"type":"object","required":["from_state","to_state","transitioned_at"],"title":"HitStateTransition","description":"Audit-friendly record of a single state transition.\n\nEmbedded as a list on each Hit so the lifecycle is visible without joining\nagainst the audit log. Strict mode rejects unknown transition types; the\nHitPipelineService is the only authority that produces these."},"cbgreenroom__Host-Input":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"full_name":{"type":"string","maxLength":300,"title":"Full Name","description":"Full name as known publicly.","examples":["Sam Rivers"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Editorial email. None if not publicly available or not verified.","examples":["sam.rivers@morningdrive.example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Editorial phone. E.164 format.","examples":["+12025550143"]},"role_tag":{"$ref":"#/components/schemas/cbgreenroom__ContactRoleTag","description":"Editorial vs commercial. Editorial roles are appropriate pitch targets; commercial roles are not. This drives outbound permission checks.","examples":["editorial","commercial"]},"biography":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Biography","description":"Editorial biography. Pulled into briefing packets.","examples":["Veteran broadcaster covering Iowa politics since 2008."]},"twitter_handle":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Twitter Handle","description":"Twitter/X handle without the @.","examples":["samriversradio"]},"notes":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Notes","description":"Internal notes. Visible to operators; never included in outbound or briefings.","examples":["Prefers email contact; declines weekend bookings."]},"provenance":{"$ref":"#/components/schemas/cbgreenroom__TierProvenance","description":"Provenance stamp. Required on every contact record.","examples":[{"source":"cbintel","tier":"verified"}]},"primary_show_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Show Id","description":"Reference to the show this host primarily appears on.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"known_positions":{"items":{"type":"string"},"type":"array","title":"Known Positions","description":"Bulleted list of known editorial positions. Source-attributed in the biography or notes; this field is the structured summary.","examples":[["Supports rural broadband expansion","Skeptical of new taxes"]]},"prior_guest_appearances":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Prior Guest Appearances","description":"Approximate number of times the licensee's principal has appeared with this host.","examples":[3]}},"additionalProperties":false,"type":"object","required":["tenant_id","full_name","role_tag","provenance"],"title":"Host","description":"A show host.\n\nHosts carry editorial positions worth tracking over time and are the\nprimary subject of briefing packet content."},"cbgreenroom__Host-Output":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"full_name":{"type":"string","maxLength":300,"title":"Full Name","description":"Full name as known publicly.","examples":["Sam Rivers"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Editorial email. None if not publicly available or not verified.","examples":["sam.rivers@morningdrive.example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Editorial phone. E.164 format.","examples":["+12025550143"]},"role_tag":{"$ref":"#/components/schemas/cbgreenroom__ContactRoleTag","description":"Editorial vs commercial. Editorial roles are appropriate pitch targets; commercial roles are not. This drives outbound permission checks.","examples":["editorial","commercial"]},"biography":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Biography","description":"Editorial biography. Pulled into briefing packets.","examples":["Veteran broadcaster covering Iowa politics since 2008."]},"twitter_handle":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Twitter Handle","description":"Twitter/X handle without the @.","examples":["samriversradio"]},"notes":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Notes","description":"Internal notes. Visible to operators; never included in outbound or briefings.","examples":["Prefers email contact; declines weekend bookings."]},"provenance":{"$ref":"#/components/schemas/cbgreenroom__TierProvenance","description":"Provenance stamp. Required on every contact record.","examples":[{"source":"cbintel","tier":"verified"}]},"primary_show_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Show Id","description":"Reference to the show this host primarily appears on.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"known_positions":{"items":{"type":"string"},"type":"array","title":"Known Positions","description":"Bulleted list of known editorial positions. Source-attributed in the biography or notes; this field is the structured summary.","examples":[["Supports rural broadband expansion","Skeptical of new taxes"]]},"prior_guest_appearances":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Prior Guest Appearances","description":"Approximate number of times the licensee's principal has appeared with this host.","examples":[3]}},"additionalProperties":false,"type":"object","required":["tenant_id","full_name","role_tag","provenance"],"title":"Host","description":"A show host.\n\nHosts carry editorial positions worth tracking over time and are the\nprimary subject of briefing packet content."},"cbgreenroom__MarkEmailedRequest":{"properties":{"message_id":{"type":"string","maxLength":500,"minLength":1,"title":"Message Id","description":"Outbound message id of the delivery email.","examples":["msg_8f17c0a2b3d4"]},"suppressed_in_demo":{"type":"boolean","title":"Suppressed In Demo","description":"When True, the email send was suppressed in DEMO mode.","default":false,"examples":[false]}},"type":"object","required":["message_id"],"title":"MarkEmailedRequest","description":"Body for marking a briefing packet as emailed."},"cbgreenroom__MarkPdfRenderedRequest":{"properties":{"pdf_uri":{"type":"string","maxLength":2000,"minLength":1,"title":"Pdf Uri","description":"Storage URI of the rendered briefing PDF.","examples":["cb://files/greenroom/briefings/550e8400.pdf"]}},"type":"object","required":["pdf_uri"],"title":"MarkPdfRenderedRequest","description":"Body for stamping a rendered-PDF URI onto a briefing packet."},"cbgreenroom__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbgreenroom__MessagingSnapshot":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"label":{"type":"string","maxLength":200,"title":"Label","description":"Human-readable snapshot label. e.g. 'Week of 2026-05-04' or 'pre-debate-briefing-2026-05-12'.","examples":["pre-debate-briefing-2026-05-12"]},"pinned_at":{"type":"string","format":"date-time","title":"Pinned At","description":"UTC timestamp when this snapshot was created.","examples":["2026-06-21T14:30:00Z"]},"pinned_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pinned By","description":"User principal that created the snapshot. None for system-created snapshots.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"entries":{"items":{"$ref":"#/components/schemas/cbgreenroom__MessagingSnapshotEntry"},"type":"array","title":"Entries","description":"The bundle of pinned record versions.","examples":[[{"record_kind":"talking_point","version":2}]]},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes","description":"Optional notes captured at snapshot creation time.","examples":["Frozen 2 hours before the debate; do not edit until air."]}},"additionalProperties":false,"type":"object","required":["tenant_id","label","pinned_at"],"title":"MessagingSnapshot","description":"A point-in-time bundle of messaging records.\n\nCreated by MessagingService.create_snapshot. Consumed by BriefingService\nwhen rendering a briefing packet. Immutable once created; corrections\nrequire a new snapshot."},"cbgreenroom__MessagingSnapshotEntry":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"snapshot_id":{"type":"string","format":"uuid","title":"Snapshot Id","description":"Parent MessagingSnapshot.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"record_kind":{"type":"string","title":"Record Kind","description":"Kind of record referenced. 'pillar', 'talking_point', 'rapid_response_line', 'opposition_contrast', or 'no_go_topic'. Free string for v1; enum candidate for v2.","examples":["talking_point"]},"record_root_id":{"type":"string","format":"uuid","title":"Record Root Id","description":"Logical record reference.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"version":{"type":"integer","minimum":1.0,"title":"Version","description":"Specific version pinned by this snapshot.","examples":[2]},"version_id":{"type":"string","format":"uuid","title":"Version Id","description":"Concrete record id at this version.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]}},"additionalProperties":false,"type":"object","required":["tenant_id","snapshot_id","record_kind","record_root_id","version","version_id"],"title":"MessagingSnapshotEntry","description":"One entry in a snapshot: a reference to a specific version of a record.\n\nThe entry resolves to a Pillar, TalkingPoint, RapidResponseLine,\nOppositionContrast, or NoGoTopic at a specific version. Snapshots store\nreferences rather than copies so corrections do not duplicate content."},"cbgreenroom__NoGoTopic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"version":{"type":"integer","minimum":1.0,"title":"Version","description":"Version number, monotonically increasing per record_root_id.","examples":[1,3]},"record_root_id":{"type":"string","format":"uuid","title":"Record Root Id","description":"Stable identifier across versions of the same logical record. Version 1's id equals its record_root_id; later versions reference back.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"published_at":{"type":"string","format":"date-time","title":"Published At","description":"UTC timestamp when this version was published.","examples":["2026-06-21T14:30:00Z"]},"superseded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Superseded At","description":"UTC timestamp when this version was superseded by a newer one. None for current versions.","examples":["2026-06-28T09:00:00Z"]},"superseded_by_version_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Superseded By Version Id","description":"Reference to the version that superseded this one. None for current versions.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"topic":{"type":"string","maxLength":300,"title":"Topic","description":"The topic to avoid.","examples":["Primary opponent's personal bankruptcy"]},"reason":{"type":"string","maxLength":2000,"title":"Reason","description":"Internal reasoning for why this topic is no-go. Not surfaced to outbound.","examples":["Counsel advised; risks defamation exposure and off-message."]},"redirect_to":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Redirect To","description":"Optional pivot suggestion if the topic comes up live.","examples":["Pivot to our record on small-business support."]}},"additionalProperties":false,"type":"object","required":["tenant_id","version","record_root_id","published_at","topic","reason"],"title":"NoGoTopic","description":"A topic the candidate does not engage with.\n\nHard list. Pulled into briefing packets so the principal and assistant\nknow where the lines are."},"cbgreenroom__OppositionContrast":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"version":{"type":"integer","minimum":1.0,"title":"Version","description":"Version number, monotonically increasing per record_root_id.","examples":[1,3]},"record_root_id":{"type":"string","format":"uuid","title":"Record Root Id","description":"Stable identifier across versions of the same logical record. Version 1's id equals its record_root_id; later versions reference back.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"published_at":{"type":"string","format":"date-time","title":"Published At","description":"UTC timestamp when this version was published.","examples":["2026-06-21T14:30:00Z"]},"superseded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Superseded At","description":"UTC timestamp when this version was superseded by a newer one. None for current versions.","examples":["2026-06-28T09:00:00Z"]},"superseded_by_version_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Superseded By Version Id","description":"Reference to the version that superseded this one. None for current versions.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"opponent_name":{"type":"string","maxLength":300,"title":"Opponent Name","description":"Name of the opponent.","examples":["State Senator Dale Hartman"]},"issue":{"type":"string","maxLength":300,"title":"Issue","description":"Issue or domain of the contrast.","examples":["Rural broadband funding"]},"opponent_position":{"type":"string","maxLength":2000,"title":"Opponent Position","description":"Statement of the opponent's position. Source-attributed.","examples":["Voted twice to strip broadband grants from the infrastructure bill."]},"candidate_position":{"type":"string","maxLength":2000,"title":"Candidate Position","description":"Statement of the candidate's position.","examples":["Authored the county fiber expansion that connected 12,000 homes."]},"why_it_matters":{"type":"string","maxLength":2000,"title":"Why It Matters","description":"One- or two-sentence framing for why voters should care.","examples":["No broadband means no remote work, no telehealth, no future here."]},"source_citations":{"items":{"type":"string"},"type":"array","title":"Source Citations","description":"Source URLs or references supporting the opponent_position claim.","examples":[["https://legislature.example.gov/votes/SB-204"]]}},"additionalProperties":false,"type":"object","required":["tenant_id","version","record_root_id","published_at","opponent_name","issue","opponent_position","candidate_position","why_it_matters"],"title":"OppositionContrast","description":"A contrast point between the candidate and an opponent.\n\nStructured for clear delivery: opponent's position, candidate's position,\nwhy it matters."},"cbgreenroom__PacketDeliveryStatus":{"properties":{"status":{"$ref":"#/components/schemas/cbgreenroom__PacketDeliveryStatusType","description":"Current overall status.","default":"pending","examples":["generated"]},"generated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Generated At","description":"UTC timestamp when packet content was generated.","examples":["2026-06-21T12:00:00Z"]},"pdf_rendered_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Pdf Rendered At","description":"UTC timestamp when the PDF was rendered to cbfiles.","examples":["2026-06-21T12:01:00Z"]},"pdf_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pdf Uri","description":"cb:// URI of the rendered PDF in cbfiles.","examples":["cb://files/greenroom/briefings/abc-123.pdf"]},"emailed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Emailed At","description":"UTC timestamp when the email was sent.","examples":["2026-06-21T12:02:00Z"]},"email_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Message Id","description":"Provider message ID for the delivery email.","examples":["msg_01HXYZ8K3QF2"]},"calendar_event_created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Calendar Event Created At","description":"UTC timestamp when the calendar event with prep block was created.","examples":["2026-06-21T12:03:00Z"]},"calendar_event_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Calendar Event Id","description":"cbcalendar event id for the primary appearance event.","examples":["e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8091"]},"prep_block_event_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Prep Block Event Id","description":"cbcalendar event id for the linked prep block.","examples":["f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f809102"]},"suppressed_in_demo":{"type":"boolean","title":"Suppressed In Demo","description":"True if delivery was suppressed because the tenant was in demo mode. The packet content still exists; only the outbound is suppressed.","default":false,"examples":[false]},"error_summary":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Error Summary","description":"Human-readable error message if status is FAILED.","examples":["PDF render timed out after 30s"]}},"additionalProperties":false,"type":"object","title":"PacketDeliveryStatus","description":"Lifecycle of a briefing packet's generation and delivery.\n\nEmbedded on BriefingPacket. Tracks the multi-step pipeline:\ngeneration, PDF rendering, email delivery, calendar event creation."},"cbgreenroom__PacketDeliveryStatusType":{"type":"string","enum":["pending","generated","emailed","calendar_event_created","complete","failed"],"title":"PacketDeliveryStatusType","description":"Lifecycle of a briefing packet's delivery."},"cbgreenroom__Pillar":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"version":{"type":"integer","minimum":1.0,"title":"Version","description":"Version number, monotonically increasing per record_root_id.","examples":[1,3]},"record_root_id":{"type":"string","format":"uuid","title":"Record Root Id","description":"Stable identifier across versions of the same logical record. Version 1's id equals its record_root_id; later versions reference back.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"published_at":{"type":"string","format":"date-time","title":"Published At","description":"UTC timestamp when this version was published.","examples":["2026-06-21T14:30:00Z"]},"superseded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Superseded At","description":"UTC timestamp when this version was superseded by a newer one. None for current versions.","examples":["2026-06-28T09:00:00Z"]},"superseded_by_version_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Superseded By Version Id","description":"Reference to the version that superseded this one. None for current versions.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"title":{"type":"string","maxLength":200,"title":"Title","description":"Short pillar title.","examples":["Lower costs for working families"]},"description":{"type":"string","maxLength":4000,"title":"Description","description":"Full description of the pillar and how to talk about it.","examples":["Frame every kitchen-table issue around the cost of living: groceries, rent, gas, child care. Always pivot back to who pays."]},"order":{"type":"integer","minimum":0.0,"title":"Order","description":"Display order. Lower numbers come first.","examples":[0,1]}},"additionalProperties":false,"type":"object","required":["tenant_id","version","record_root_id","published_at","title","description","order"],"title":"Pillar","description":"A campaign pillar.\n\nThe strategic frame the campaign is building around. Few in number (3-7).\nStable over months. Surfaces in every briefing packet."},"cbgreenroom__Pitch":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"show_id":{"type":"string","format":"uuid","title":"Show Id","description":"Target show.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"target_contact_id":{"type":"string","format":"uuid","title":"Target Contact Id","description":"Reference to the Host, Producer, or Booker being pitched.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"target_role":{"type":"string","title":"Target Role","description":"'host', 'producer', or 'booker'. Free string for v1; enum candidate for v2.","examples":["producer"]},"candidate_principal_id":{"type":"string","format":"uuid","title":"Candidate Principal Id","description":"The principal being pitched (the candidate).","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"angle":{"type":"string","maxLength":4000,"title":"Angle","description":"Editorial angle for the pitch. Pulled into the briefing packet if the pitch lands.","examples":["Local kid made good, now fighting to keep the hospital open."]},"initiated_by":{"type":"string","format":"uuid","title":"Initiated By","description":"User principal who initiated the pitch sequence. Greenroom Entity Staff is rejected; checked against role.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"initiated_at":{"type":"string","format":"date-time","title":"Initiated At","description":"UTC timestamp when the pitch sequence began.","examples":["2026-06-21T14:30:00Z"]},"closed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Closed At","description":"UTC timestamp when the pitch was closed (booked, declined, or expired).","examples":["2026-06-24T17:00:00Z"]},"closure_reason":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Closure Reason","description":"Reason for closure. 'booked', 'declined', 'expired', or free text.","examples":["booked"]},"hit_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Hit Id","description":"Reference to the Hit produced by this pitch.","examples":["f47ac10b-58cc-4372-a567-0e02b2c3d479"]}},"additionalProperties":false,"type":"object","required":["tenant_id","show_id","target_contact_id","target_role","candidate_principal_id","angle","initiated_by","initiated_at"],"title":"Pitch","description":"A pitch sequence targeting a specific show.\n\nOwns one or more PitchOutboundRecord children and produces exactly one Hit.\nThe Hit's source is GREENROOM_PITCHED and its pitch_id points back here."},"cbgreenroom__PitchOutboundRecord":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"pitch_id":{"type":"string","format":"uuid","title":"Pitch Id","description":"Parent Pitch.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"sent_at":{"type":"string","format":"date-time","title":"Sent At","description":"UTC timestamp of send.","examples":["2026-06-21T14:30:00Z"]},"sent_by":{"type":"string","format":"uuid","title":"Sent By","description":"User principal who sent the pitch. Greenroom Entity Staff is rejected here; the field is required and is checked against role.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"channel":{"type":"string","title":"Channel","description":"'email' or 'sms'.","examples":["email"]},"target_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Email","description":"Email recipient. Required when channel is email.","examples":["producer@morningdriveshow.com"]},"target_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Phone","description":"Phone recipient in E.164. Required when channel is sms.","examples":["+15551234567"]},"subject":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Subject","description":"Subject line for email pitches.","examples":["Candidate available for your Friday segment"]},"body":{"type":"string","maxLength":20000,"title":"Body","description":"Message body. Disclosure paragraph is injected by the service if applicable.","examples":["Hi Sam — our candidate would love to talk rural broadband on the Morning Drive this week. Fifteen minutes, any morning that works."]},"disclosure_injected":{"type":"boolean","title":"Disclosure Injected","description":"Whether a disclosure paragraph was injected before send.","default":false,"examples":[false]},"suppressed_in_demo":{"type":"boolean","title":"Suppressed In Demo","description":"True if the send was suppressed because the tenant was in demo mode or outbound_locked. The record is preserved either way.","default":false,"examples":[false]},"external_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Message Id","description":"Provider-side message ID (cbfront thread, cbsms message). None if suppressed.","examples":["cbfront-thread-8f17c0"]},"response_received":{"type":"boolean","title":"Response Received","description":"Whether a response has been recorded against this outbound.","default":false,"examples":[false]}},"additionalProperties":false,"type":"object","required":["tenant_id","pitch_id","sent_at","sent_by","channel","body"],"title":"PitchOutboundRecord","description":"Single outbound message in a pitch sequence.\n\nPitches can have multiple touches (initial pitch, follow-up at 3 days,\nfollow-up at 7 days). Each outbound is recorded."},"cbgreenroom__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbgreenroom__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbgreenroom__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbgreenroom__Producer-Input":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"full_name":{"type":"string","maxLength":300,"title":"Full Name","description":"Full name as known publicly.","examples":["Sam Rivers"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Editorial email. None if not publicly available or not verified.","examples":["sam.rivers@morningdrive.example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Editorial phone. E.164 format.","examples":["+12025550143"]},"role_tag":{"$ref":"#/components/schemas/cbgreenroom__ContactRoleTag","description":"Editorial vs commercial. Editorial roles are appropriate pitch targets; commercial roles are not. This drives outbound permission checks.","examples":["editorial","commercial"]},"biography":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Biography","description":"Editorial biography. Pulled into briefing packets.","examples":["Veteran broadcaster covering Iowa politics since 2008."]},"twitter_handle":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Twitter Handle","description":"Twitter/X handle without the @.","examples":["samriversradio"]},"notes":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Notes","description":"Internal notes. Visible to operators; never included in outbound or briefings.","examples":["Prefers email contact; declines weekend bookings."]},"provenance":{"$ref":"#/components/schemas/cbgreenroom__TierProvenance","description":"Provenance stamp. Required on every contact record.","examples":[{"source":"cbintel","tier":"verified"}]},"primary_show_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Show Id","description":"Reference to the show this producer primarily works on.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"last_contacted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Contacted At","description":"UTC timestamp of last recorded outbound or touchpoint. Drives the aged-relationship signal.","examples":["2026-06-21T14:30:00Z"]}},"additionalProperties":false,"type":"object","required":["tenant_id","full_name","role_tag","provenance"],"title":"Producer","description":"A show producer.\n\nThe relationship-management focus. Touchpoints, thank-you notes, and\nlong-arc relationship investments target producers."},"cbgreenroom__Producer-Output":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"full_name":{"type":"string","maxLength":300,"title":"Full Name","description":"Full name as known publicly.","examples":["Sam Rivers"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Editorial email. None if not publicly available or not verified.","examples":["sam.rivers@morningdrive.example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Editorial phone. E.164 format.","examples":["+12025550143"]},"role_tag":{"$ref":"#/components/schemas/cbgreenroom__ContactRoleTag","description":"Editorial vs commercial. Editorial roles are appropriate pitch targets; commercial roles are not. This drives outbound permission checks.","examples":["editorial","commercial"]},"biography":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Biography","description":"Editorial biography. Pulled into briefing packets.","examples":["Veteran broadcaster covering Iowa politics since 2008."]},"twitter_handle":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Twitter Handle","description":"Twitter/X handle without the @.","examples":["samriversradio"]},"notes":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Notes","description":"Internal notes. Visible to operators; never included in outbound or briefings.","examples":["Prefers email contact; declines weekend bookings."]},"provenance":{"$ref":"#/components/schemas/cbgreenroom__TierProvenance","description":"Provenance stamp. Required on every contact record.","examples":[{"source":"cbintel","tier":"verified"}]},"primary_show_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Show Id","description":"Reference to the show this producer primarily works on.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"last_contacted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Contacted At","description":"UTC timestamp of last recorded outbound or touchpoint. Drives the aged-relationship signal.","examples":["2026-06-21T14:30:00Z"]}},"additionalProperties":false,"type":"object","required":["tenant_id","full_name","role_tag","provenance"],"title":"Producer","description":"A show producer.\n\nThe relationship-management focus. Touchpoints, thank-you notes, and\nlong-arc relationship investments target producers."},"cbgreenroom__PromoteToLiveRequest":{"properties":{"evidence_reference":{"type":"string","maxLength":500,"minLength":1,"title":"Evidence Reference","description":"Reference to the promotion evidence — counsel signoff id, license agreement reference, or operator confirmation token. Recorded on the ModeChangedEvent audit row."}},"type":"object","required":["evidence_reference"],"title":"PromoteToLiveRequest","description":"Body for the promote-to-live endpoint."},"cbgreenroom__RapidResponseLine":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"version":{"type":"integer","minimum":1.0,"title":"Version","description":"Version number, monotonically increasing per record_root_id.","examples":[1,3]},"record_root_id":{"type":"string","format":"uuid","title":"Record Root Id","description":"Stable identifier across versions of the same logical record. Version 1's id equals its record_root_id; later versions reference back.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"published_at":{"type":"string","format":"date-time","title":"Published At","description":"UTC timestamp when this version was published.","examples":["2026-06-21T14:30:00Z"]},"superseded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Superseded At","description":"UTC timestamp when this version was superseded by a newer one. None for current versions.","examples":["2026-06-28T09:00:00Z"]},"superseded_by_version_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Superseded By Version Id","description":"Reference to the version that superseded this one. None for current versions.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"trigger_topic":{"type":"string","maxLength":500,"title":"Trigger Topic","description":"The topic or attack vector this responds to.","examples":["Opponent ad claiming we voted to raise taxes"]},"response":{"type":"string","maxLength":4000,"title":"Response","description":"The pre-cleared response.","examples":["That ad has been fact-checked false. The only thing we raised was the minimum wage."]},"severity":{"type":"string","maxLength":50,"title":"Severity","description":"Severity tag. 'standard', 'critical', or free text.","default":"standard","examples":["critical"]}},"additionalProperties":false,"type":"object","required":["tenant_id","version","record_root_id","published_at","trigger_topic","response"],"title":"RapidResponseLine","description":"A rapid response line.\n\nPre-cleared messaging for predictable attacks or news cycles. Pulled into\nbriefings only if relevant to the show's recent topics."},"cbgreenroom__RecordOutcomeRequest":{"properties":{"result":{"$ref":"#/components/schemas/cbgreenroom__TouchpointResult","description":"The captured outcome of the touchpoint.","examples":["completed","escalated","skipped"]},"completed_by":{"type":"string","format":"uuid","title":"Completed By","description":"The operator who acted on the touchpoint.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"notes":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Notes","description":"Optional free-text note about the outcome.","examples":["Left a voicemail with the producer; will retry Friday."]},"follow_up_required":{"type":"boolean","title":"Follow Up Required","description":"Whether the outcome requires a follow-up touchpoint.","default":false,"examples":[false]}},"type":"object","required":["result","completed_by"],"title":"RecordOutcomeRequest","description":"Body for recording the outcome of a Touchpoint."},"cbgreenroom__ReferralRequest":{"properties":{"show_id":{"type":"string","format":"uuid","title":"Show Id","description":"Identifier of the Show the referral targets.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"candidate_principal_id":{"type":"string","format":"uuid","title":"Candidate Principal Id","description":"Principal identifier of the referred candidate.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"referrer_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Referrer User Id","description":"Internal user who made the referral, if a known principal.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"referrer_external_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Referrer External Name","description":"Free-text name of an external referrer when no user id exists.","examples":["Jane Producer"]},"topic_brief":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Topic Brief","description":"Short brief on the topic or angle for the appearance.","examples":["Local economy roundtable"]}},"type":"object","required":["show_id","candidate_principal_id"],"title":"ReferralRequest","description":"Request body for recording a referral-sourced Hit."},"cbgreenroom__RequestApprovalBody":{"properties":{"hit_id":{"type":"string","format":"uuid","title":"Hit Id","description":"The Hit requiring dual approval.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"approver_ids":{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":2,"minItems":2,"title":"Approver Ids","description":"Exactly two distinct approver user_ids.","examples":[["550e8400-e29b-41d4-a716-446655440000","6ba7b810-9dad-11d1-80b4-00c04fd430c8"]]},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Optional UTC expiry after which a PENDING request is stale.","examples":["2026-06-21T14:30:00Z"]},"suppressed_in_demo":{"type":"boolean","title":"Suppressed In Demo","description":"When True, suppress real approver notifications in DEMO mode.","default":false,"examples":[false]}},"type":"object","required":["hit_id","approver_ids"],"title":"RequestApprovalBody","description":"Body for opening a dual-approval request on a Hit."},"cbgreenroom__SendOutboundRequest":{"properties":{"channel":{"type":"string","title":"Channel","description":"Outbound channel — 'email' or 'sms'.","examples":["email","sms"]},"body":{"type":"string","maxLength":20000,"title":"Body","description":"The outbound message body.","examples":["Hi — would the Senator be a good fit for tomorrow's segment?"]},"target_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Email","description":"Recipient email when channel is 'email'.","examples":["assistant@perryforsenate.org"]},"target_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Phone","description":"Recipient phone (E.164) when channel is 'sms'.","examples":["+15125550142"]},"subject":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Subject","description":"Subject line for email sends.","examples":["Booking request — drive-time segment"]},"disclosure_injected":{"type":"boolean","title":"Disclosure Injected","description":"Whether a compliance disclosure was injected into the body.","default":false,"examples":[false]}},"type":"object","required":["channel","body"],"title":"SendOutboundRequest","description":"Body for recording an outbound pitch send over email or SMS."},"cbgreenroom__SetConflictFlagRequest":{"properties":{"rationale":{"type":"string","maxLength":4000,"title":"Rationale","description":"Explanation of why the Show is being flagged as a conflict.","examples":["Host's spouse is a registered lobbyist for the opposing campaign."]},"disclosure_template_id":{"type":"string","format":"uuid","title":"Disclosure Template Id","description":"Identifier of the disclosure template to attach to the flag.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"flagged_by":{"type":"string","format":"uuid","title":"Flagged By","description":"Principal identifier of the user raising the conflict flag.","examples":["550e8400-e29b-41d4-a716-446655440000"]}},"type":"object","required":["rationale","disclosure_template_id","flagged_by"],"title":"SetConflictFlagRequest","description":"Request body for flagging a Show as a conflict of interest."},"cbgreenroom__SetConflictFlagResponse":{"properties":{"show":{"$ref":"#/components/schemas/cbgreenroom__Show-Output","description":"The Show with its conflict_flag set to true."},"flag":{"$ref":"#/components/schemas/cbgreenroom__ConflictFlag","description":"The newly created ConflictFlag record."}},"type":"object","required":["show","flag"],"title":"SetConflictFlagResponse","description":"Response returned after flagging a Show, carrying the updated Show and new flag."},"cbgreenroom__Show-Input":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"name":{"type":"string","maxLength":300,"title":"Name","description":"Display name of the show.","examples":["The Morning Drive with Sam Rivers"]},"show_type":{"$ref":"#/components/schemas/cbgreenroom__ShowType","description":"Medium of the show.","examples":["radio","podcast"]},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Description","description":"Editorial description of the show.","examples":["Daily political talk covering Iowa statewide races."]},"website":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Website","description":"Show's primary website.","examples":["https://morningdrive.example.com"]},"rss_feed":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Rss Feed","description":"RSS feed URL for podcasts and similar serial media.","examples":["https://morningdrive.example.com/feed.xml"]},"callsign":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Callsign","description":"Radio or TV callsign. Optional even for radio shows.","examples":["WHO","KXNO"]},"market":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Market","description":"Designated market area or geographic scope. Free text.","examples":["Des Moines, IA"]},"audience_estimate":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Audience Estimate","description":"Estimated audience size per episode. Source-attributed; never invented.","examples":[25000]},"political_lean":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Political Lean","description":"Free-text political lean tag. Set conservatively, not algorithmically.","examples":["center-right"]},"conflict_flag":{"type":"boolean","title":"Conflict Flag","description":"Whether this show triggers the disclosure-injection flow. Set when any conflict of interest exists between the show and the licensee. Disclosure templates are injected into outbound and briefing packets for conflict-flagged shows.","default":false,"examples":[false]},"primary_host_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Host Id","description":"Reference to the primary Host record. Optional for shows with rotating hosts.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"primary_producer_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Producer Id","description":"Reference to the primary Producer record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"primary_booker_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Booker Id","description":"Reference to the primary Booker record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"provenance":{"$ref":"#/components/schemas/cbgreenroom__TierProvenance","description":"Provenance stamp. Required on every Show record.","examples":[{"source":"cbintel","tier":"verified"}]}},"additionalProperties":false,"type":"object","required":["tenant_id","name","show_type","provenance"],"title":"Show","description":"A media show.\n\nMedia-agnostic by design. show_type tags the medium; medium-specific fields\n(callsign, frequency, market) are optional and populated only when relevant."},"cbgreenroom__Show-Output":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"name":{"type":"string","maxLength":300,"title":"Name","description":"Display name of the show.","examples":["The Morning Drive with Sam Rivers"]},"show_type":{"$ref":"#/components/schemas/cbgreenroom__ShowType","description":"Medium of the show.","examples":["radio","podcast"]},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Description","description":"Editorial description of the show.","examples":["Daily political talk covering Iowa statewide races."]},"website":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Website","description":"Show's primary website.","examples":["https://morningdrive.example.com"]},"rss_feed":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Rss Feed","description":"RSS feed URL for podcasts and similar serial media.","examples":["https://morningdrive.example.com/feed.xml"]},"callsign":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Callsign","description":"Radio or TV callsign. Optional even for radio shows.","examples":["WHO","KXNO"]},"market":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Market","description":"Designated market area or geographic scope. Free text.","examples":["Des Moines, IA"]},"audience_estimate":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Audience Estimate","description":"Estimated audience size per episode. Source-attributed; never invented.","examples":[25000]},"political_lean":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Political Lean","description":"Free-text political lean tag. Set conservatively, not algorithmically.","examples":["center-right"]},"conflict_flag":{"type":"boolean","title":"Conflict Flag","description":"Whether this show triggers the disclosure-injection flow. Set when any conflict of interest exists between the show and the licensee. Disclosure templates are injected into outbound and briefing packets for conflict-flagged shows.","default":false,"examples":[false]},"primary_host_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Host Id","description":"Reference to the primary Host record. Optional for shows with rotating hosts.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"primary_producer_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Producer Id","description":"Reference to the primary Producer record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"primary_booker_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Primary Booker Id","description":"Reference to the primary Booker record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"provenance":{"$ref":"#/components/schemas/cbgreenroom__TierProvenance","description":"Provenance stamp. Required on every Show record.","examples":[{"source":"cbintel","tier":"verified"}]}},"additionalProperties":false,"type":"object","required":["tenant_id","name","show_type","provenance"],"title":"Show","description":"A media show.\n\nMedia-agnostic by design. show_type tags the medium; medium-specific fields\n(callsign, frequency, market) are optional and populated only when relevant."},"cbgreenroom__ShowType":{"type":"string","enum":["radio","podcast","tv","streaming","print","livestream"],"title":"ShowType","description":"Media type of a show.\n\nGreenroom is media-agnostic. Show records carry this tag so media-specific\nfields stay optional and downstream handlers can specialize."},"cbgreenroom__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbgreenroom__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbgreenroom__TMinusTwoHRequest":{"properties":{"snapshot_id":{"type":"string","format":"uuid","title":"Snapshot Id","description":"The research snapshot to build the packet from.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"delivered_to_assistant_email":{"type":"string","maxLength":300,"minLength":3,"title":"Delivered To Assistant Email","description":"Email the rendered packet is delivered to.","examples":["assistant@perryforsenate.org"]},"cbcalendar_tenant_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbcalendar Tenant Slug","description":"Optional override for cbcalendar's slug-shaped tenant id; falls back to ``Tenant.cbcalendar_tenant_slug`` when omitted.","examples":["perry-2026"]},"cbcalendar_calendar_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbcalendar Calendar Id","description":"Optional override for the cbcalendar calendar_id; falls back to ``Tenant.cbcalendar_primary_calendar_id`` when omitted.","examples":["cal_8f17c0a2b3d4"]},"cbcalendar_prep_minutes":{"type":"integer","maximum":240.0,"minimum":15.0,"title":"Cbcalendar Prep Minutes","description":"Lead-time minutes for the prep block. Default 60.","default":60,"examples":[60]}},"type":"object","required":["snapshot_id","delivered_to_assistant_email"],"title":"TMinusTwoHRequest","description":"Body for the T-2hr briefing trigger.\n\ncbcron passes the snapshot to use plus the assistant email; the hit's\nown ``scheduled_for`` is the implicit target air time. cbcalendar\nfields are optional overrides — when omitted, the briefing flow falls\nback to ``Tenant.cbcalendar_tenant_slug`` and\n``Tenant.cbcalendar_primary_calendar_id``."},"cbgreenroom__TalkingPoint":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"version":{"type":"integer","minimum":1.0,"title":"Version","description":"Version number, monotonically increasing per record_root_id.","examples":[1,3]},"record_root_id":{"type":"string","format":"uuid","title":"Record Root Id","description":"Stable identifier across versions of the same logical record. Version 1's id equals its record_root_id; later versions reference back.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"published_at":{"type":"string","format":"date-time","title":"Published At","description":"UTC timestamp when this version was published.","examples":["2026-06-21T14:30:00Z"]},"superseded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Superseded At","description":"UTC timestamp when this version was superseded by a newer one. None for current versions.","examples":["2026-06-28T09:00:00Z"]},"superseded_by_version_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Superseded By Version Id","description":"Reference to the version that superseded this one. None for current versions.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"title":{"type":"string","maxLength":200,"title":"Title","description":"Short talking point title.","examples":["Prescription drug prices"]},"body":{"type":"string","maxLength":4000,"title":"Body","description":"The talking point as it should be delivered.","examples":["Seniors in this state are splitting pills to make rent. We capped insulin at $35 and we're not stopping there."]},"pillar_root_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pillar Root Id","description":"Optional reference to the parent Pillar's record_root_id.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"bridges":{"items":{"type":"string"},"type":"array","title":"Bridges","description":"Bridge phrases the principal can use to pivot from any topic into this talking point. Three bridges are surfaced in the briefing packet.","examples":[["What this really comes down to is cost...","Families I talk to keep asking the same thing..."]]},"week_starting":{"type":"string","format":"date-time","title":"Week Starting","description":"UTC start of the week this talking point applies to.","examples":["2026-06-15T00:00:00Z"]}},"additionalProperties":false,"type":"object","required":["tenant_id","version","record_root_id","published_at","title","body","week_starting"],"title":"TalkingPoint","description":"A weekly talking point.\n\nTactical messaging tied to current events or campaign emphasis. Updated\nweekly (or more often). Surfaces in current briefing packets only."},"cbgreenroom__Tenant":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this platform record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"slug":{"type":"string","maxLength":64,"minLength":2,"pattern":"^[a-z0-9][a-z0-9_-]*$","title":"Slug","description":"URL-safe stable slug. Used in cb:// URIs and DNS subdomains. Leading digits are allowed because political tenants like ``190personnel`` and ``mi20`` are common in cbtenant; ``deps.get_tenant_id``'s slug regex is in lockstep.","examples":["perry-2026","mi20"]},"display_name":{"type":"string","maxLength":200,"title":"Display Name","description":"Human-readable tenant name (e.g., 'Perry for Senate').","examples":["Perry for Senate"]},"licensee_legal_entity":{"type":"string","maxLength":200,"title":"Licensee Legal Entity","description":"Legal entity that holds the Greenroom license for this tenant. Used in audit records and disclosure templates.","examples":["Perry for Senate Committee, Inc."]},"mode":{"$ref":"#/components/schemas/cbgreenroom__TenantMode","description":"Current operating mode. New tenants always start in DEMO. Promotion to LIVE requires an evidence-bearing call to the tenant promotion endpoint.","default":"demo","examples":["demo","live"]},"outbound_locked":{"type":"boolean","title":"Outbound Locked","description":"Hard lock on outbound. Even if mode is LIVE, an outbound_locked tenant produces no real outbound. Used for tenants in pre-counsel review state. Defaults to True for safety; explicit unlock required.","default":true,"examples":[true]},"disclosure_template_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Disclosure Template Id","description":"Reference to the active DisclosureTemplate for this tenant.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"cbcalendar_tenant_slug":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Cbcalendar Tenant Slug","description":"cbcalendar's slug-shaped tenant id for this Greenroom tenant (e.g., 'perry-2026'). cbcalendar uses slugs that are independent of cbgreenroom's UUID tenant ids; this field maps one to the other so callers don't pass it per request. Briefing endpoints still accept an explicit override.","examples":["perry-2026"]},"cbcalendar_primary_calendar_id":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Cbcalendar Primary Calendar Id","description":"Default cbcalendar calendar_id used when the briefing flow creates appearance + prep-block events. Briefing endpoints still accept an explicit override.","examples":["primary","appearances@perryforsenate.org"]},"autoprovisioned":{"type":"boolean","title":"Autoprovisioned","description":"True when this Tenant was created by the auto-provisioning path in ``deps.get_tenant_id`` (slug-touch from cbauth without a prior cbgreenroom record). Auto-provisioned tenants carry placeholder ``display_name`` / ``licensee_legal_entity`` values and are blocked from LIVE promotion until an operator updates those fields and clears this flag via ``update_tenant``. Stays False for tenants created explicitly through ``POST /api/v1/tenants`` with real values supplied by the caller.","default":false,"examples":[false]}},"additionalProperties":false,"type":"object","required":["slug","display_name","licensee_legal_entity"],"title":"Tenant","description":"A Greenroom tenant.\n\nEach tenant corresponds to one campaign or licensee organization. Tenant\nrecords sit at the platform level rather than inside any tenant, which is\nwhy this inherits from PlatformResource."},"cbgreenroom__TenantMode":{"type":"string","enum":["demo","live"],"title":"TenantMode","description":"Demo vs live mode for a tenant.\n\nTenants in DEMO mode produce no real outbound. Promotion to LIVE requires\nan evidence-bearing call to the tenant promotion endpoint."},"cbgreenroom__TierLevel":{"type":"string","enum":["tier_1","tier_2","tier_3"],"title":"TierLevel","description":"Provenance tier of an ingested record.\n\nTier 1: free public sources (FCC, Census, FEC, public podcasts).\nTier 2: paid licensed sources (Precision Trax, Strata, Nielsen, SRDS).\nTier 3: RuralAMFM internal data and any other source behind the legal wall.\nTier 3 records are blocked at the ingestion boundary."},"cbgreenroom__TierProvenance":{"properties":{"source_url":{"type":"string","title":"Source Url","description":"URL or stable identifier of the source. For Tier 1 sources this is typically the public URL the data was scraped from. For Tier 2, the licensed dataset name and record reference. For Tier 3, the presence of any value here is grounds for the ingestion middleware to block.","examples":["https://example-radio.com/shows/morning-drive"]},"tier":{"$ref":"#/components/schemas/cbgreenroom__TierLevel","description":"Provenance tier. Tier 3 records are blocked at the ingestion boundary and never reach the spine; this field is included for completeness so blocked-attempt audit events can record what was rejected.","examples":["tier_1"]},"ingested_at":{"type":"string","format":"date-time","title":"Ingested At","description":"UTC timestamp when this record was ingested.","examples":["2026-06-21T14:30:00Z"]},"ingestion_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ingestion Job Id","description":"Optional reference to the ingestion job that produced this record.","examples":["ingest-2026-06-21-0042"]},"license_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"License Reference","description":"For Tier 2 records, the license agreement or contract identifier under which this record was obtained. None for Tier 1.","examples":["LICENSE-NIELSEN-2026"]}},"additionalProperties":false,"type":"object","required":["source_url","tier"],"title":"TierProvenance","description":"Provenance stamp on an ingested record.\n\nEmbedded as a sub-record on Show, Host, Producer, Booker, Episode, and\nany other entity created from external data. Records authored inside\ncbgreenroom (Hits, Approvals, BriefingPackets) do not carry one."},"cbgreenroom__Touchpoint":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"digest_id":{"type":"string","format":"uuid","title":"Digest Id","description":"TouchpointDigest this recommendation appears in.","examples":["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"]},"title":{"type":"string","maxLength":300,"title":"Title","description":"Short title. e.g. 'Call Producer Jane Doe at WHO-AM about Q3 booking'.","examples":["Call Producer Jane Doe at WHO-AM about Q3 booking"]},"description":{"type":"string","maxLength":4000,"title":"Description","description":"Full recommendation including suggested talking points or context.","examples":["Jane has booked us twice before; the Q3 slot is opening up. Lead with the jobs segment."]},"target_record_uri":{"type":"string","title":"Target Record Uri","description":"cb:// URI of the primary record this touchpoint relates to (Producer, Show, Pitch).","examples":["cb://greenroom/producers/abc-123"]},"contributing_signal_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Contributing Signal Ids","description":"TouchpointSignal records that contributed to this recommendation.","examples":[["b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"]]},"suggested_channel":{"type":"string","title":"Suggested Channel","description":"'phone', 'email', 'in_person', or free string. cbai-suggested.","examples":["phone"]},"priority":{"type":"integer","maximum":10.0,"minimum":1.0,"title":"Priority","description":"Operator priority. 1 is highest. Drives display order in the digest.","default":5,"examples":[2]},"estimated_minutes":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Estimated Minutes","description":"Rough time estimate for the action.","examples":[15]},"outcome_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Outcome Id","description":"Reference to the TouchpointOutcome once captured.","examples":["c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f"]}},"additionalProperties":false,"type":"object","required":["tenant_id","digest_id","title","description","target_record_uri","suggested_channel"],"title":"Touchpoint","description":"A single recommended human action.\n\nThe leverage unit. Surfaced in the weekly TouchpointDigest and consumed\nby MrGreen. Outcomes are captured back via TouchpointOutcome, which loops\ninto next week's signal extraction."},"cbgreenroom__TouchpointDigest":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"target_user_id":{"type":"string","format":"uuid","title":"Target User Id","description":"Operator who receives this digest (typically MrGreen).","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"period_start":{"type":"string","format":"date-time","title":"Period Start","description":"UTC start of the period this digest covers.","examples":["2026-06-15T00:00:00Z"]},"period_end":{"type":"string","format":"date-time","title":"Period End","description":"UTC end of the period this digest covers.","examples":["2026-06-21T23:59:59Z"]},"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"UTC timestamp when the digest was generated.","examples":["2026-06-22T06:00:00Z"]},"delivered_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Delivered At","description":"UTC timestamp when the digest was delivered. None if not yet sent or suppressed.","examples":["2026-06-22T06:05:00Z"]},"suppressed_in_demo":{"type":"boolean","title":"Suppressed In Demo","description":"True if delivery was suppressed in demo mode. Digest content exists either way.","default":false,"examples":[false]},"summary":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Summary","description":"Top-of-digest summary written by cbai. Useful when scanning the digest quickly.","examples":["5 high-leverage actions this week: 2 producer calls, 1 escalation, 2 thank-yous."]},"touchpoint_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Touchpoint Ids","description":"Touchpoints included in this digest, in priority order.","examples":[["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"]]}},"additionalProperties":false,"type":"object","required":["tenant_id","target_user_id","period_start","period_end","generated_at"],"title":"TouchpointDigest","description":"Weekly bundle of touchpoint recommendations for an operator.\n\nGenerated by cbcron weekly trigger calling TouchpointService. Delivered\nto the operator via cbfront. Read-once but not deleted; serves as a\nhistorical record of what was recommended when."},"cbgreenroom__TouchpointOutcome":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"touchpoint_id":{"type":"string","format":"uuid","title":"Touchpoint Id","description":"The Touchpoint being closed out.","examples":["d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f80"]},"result":{"$ref":"#/components/schemas/cbgreenroom__TouchpointResult","description":"What happened.","examples":["completed"]},"attempted_at":{"type":"string","format":"date-time","title":"Attempted At","description":"UTC timestamp when the operator attempted the action.","examples":["2026-06-21T16:30:00Z"]},"completed_by":{"type":"string","format":"uuid","title":"Completed By","description":"User principal who logged the outcome.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"notes":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Notes","description":"Free-form notes. Useful for the next digest's signal extraction.","examples":["Reached Jane; she'll hold the Q3 slot and confirm by Friday."]},"follow_up_required":{"type":"boolean","title":"Follow Up Required","description":"Whether a follow-up touchpoint should be generated next cycle.","default":false,"examples":[true]}},"additionalProperties":false,"type":"object","required":["tenant_id","touchpoint_id","result","attempted_at","completed_by"],"title":"TouchpointOutcome","description":"Captured outcome of a touchpoint recommendation.\n\nCloses the loop so next week's digest can deduplicate, escalate, or\nreinforce based on what happened."},"cbgreenroom__TouchpointResult":{"type":"string","enum":["completed","escalated","skipped"],"title":"TouchpointResult","description":"Outcome captured back from a touchpoint recommendation."},"cbgreenroom__TouchpointSignal":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"kind":{"$ref":"#/components/schemas/cbgreenroom__TouchpointSignalKind","description":"What kind of signal this is.","examples":["stalled_pitch"]},"detected_at":{"type":"string","format":"date-time","title":"Detected At","description":"UTC timestamp when the signal was detected.","examples":["2026-06-21T08:00:00Z"]},"related_record_uri":{"type":"string","title":"Related Record Uri","description":"cb:// URI of the record the signal pertains to (Show, Pitch, Hit, Producer).","examples":["cb://greenroom/pitches/abc-123"]},"weight":{"type":"number","maximum":10.0,"minimum":0.0,"title":"Weight","description":"Relative weight of this signal. TouchpointService uses this to rank recommendations.","default":1.0,"examples":[2.5]},"rationale":{"type":"string","maxLength":2000,"title":"Rationale","description":"Why this signal fired. Surfaced to the operator alongside the touchpoint.","examples":["Pitch sent 14 days ago with no producer reply."]},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Optional structured context.","examples":[{"days_stalled":14,"last_contact":"2026-06-07T10:00:00Z"}]}},"additionalProperties":false,"type":"object","required":["tenant_id","kind","detected_at","related_record_uri","rationale"],"title":"TouchpointSignal","description":"An upstream signal that may produce a touchpoint recommendation.\n\nSignals are derived from queries over hits, pitches, shows, and cbscout\nrelationship memory. Several signals can compound into a single\nTouchpoint recommendation."},"cbgreenroom__TouchpointSignalKind":{"type":"string","enum":["stalled_pitch","missing_thank_you","new_show_discovered","aged_producer_relationship","host_position_drift","escalation_needed"],"title":"TouchpointSignalKind","description":"The upstream signal that produced a touchpoint recommendation."},"cbgreenroom__TransitionRequest":{"properties":{"to_state":{"$ref":"#/components/schemas/cbgreenroom__HitState","description":"Target pipeline state to advance the Hit to.","examples":["booked"]},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes","description":"Optional context recorded alongside the state transition.","examples":["Confirmed by producer over email."]}},"type":"object","required":["to_state"],"title":"TransitionRequest","description":"Request body for advancing a Hit to a new pipeline state."},"cbgreenroom__User":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Stable identifier for this record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant this record belongs to. Cross-tenant reads are forbidden.","examples":["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when this record was first written.","examples":["2026-06-21T14:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp when this record was last modified.","examples":["2026-06-21T15:45:00Z"]},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User principal that created this record. None for system-created records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"updated_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Updated By","description":"User principal that last modified this record. None for system-modified records.","examples":["7c9e6679-7425-40de-944b-e07fc1f90ae7"]},"auth_user_id":{"type":"string","format":"uuid","title":"Auth User Id","description":"Reference to the cbauth user record.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"email":{"type":"string","format":"email","title":"Email","description":"Primary email for outbound notifications.","examples":["assistant@perryforsenate.org"]},"display_name":{"type":"string","maxLength":200,"title":"Display Name","description":"Human-readable name (e.g., 'MrGreen').","examples":["MrGreen"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"E.164 phone number for SMS notifications. None if SMS is not enabled for this user.","examples":["+12025550143"]},"role":{"oneOf":[{"$ref":"#/components/schemas/cbgreenroom__CampaignOperatorRole"},{"$ref":"#/components/schemas/cbgreenroom__CampaignApproverRole"},{"$ref":"#/components/schemas/cbgreenroom__GreenroomEntityStaffRole"},{"$ref":"#/components/schemas/cbgreenroom__AssistantPrincipalRole"}],"title":"Role","description":"The user's role within this tenant.","examples":[{"can_book_walkin":true,"can_pitch":true,"type":"campaign_operator"}],"discriminator":{"propertyName":"type","mapping":{"assistant_principal":"#/components/schemas/AssistantPrincipalRole","campaign_approver":"#/components/schemas/CampaignApproverRole","campaign_operator":"#/components/schemas/CampaignOperatorRole","greenroom_entity_staff":"#/components/schemas/GreenroomEntityStaffRole"}}},"is_greenroom_entity_staff":{"type":"boolean","title":"Is Greenroom Entity Staff","description":"Fast-path mirror of role.type == GREENROOM_ENTITY_STAFF. Services check this field on every state-changing operation that takes an actor; Greenroom Entity Staff is rejected as actor everywhere except configuration paths.","examples":[false]},"active":{"type":"boolean","title":"Active","description":"Whether this user is currently active. Inactive users cannot authenticate.","default":true,"examples":[true]}},"additionalProperties":false,"type":"object","required":["tenant_id","auth_user_id","email","display_name","role","is_greenroom_entity_staff"],"title":"User","description":"A user principal.\n\nAuthenticated through cbauth. Permissions are derived from role and tenant\nmembership. is_greenroom_entity_staff is a fast-path field that mirrors\nthe role's discriminator value; services check it on every state-changing\noperation."},"cbgreenroom__UserRoleType":{"type":"string","enum":["campaign_operator","campaign_approver","greenroom_entity_staff","assistant_principal"],"title":"UserRoleType","description":"Principal types in cbgreenroom.\n\nDrives permission resolution. GREENROOM_ENTITY_STAFF is the load-bearing\nauthz boundary: principals with this role cannot appear as approver,\nsender, booker, or pitcher on any record."},"cbgreenroom__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbgreenroom__WalkInRequest":{"properties":{"show_id":{"type":"string","format":"uuid","title":"Show Id","description":"Identifier of the Show the candidate is booked on.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"candidate_principal_id":{"type":"string","format":"uuid","title":"Candidate Principal Id","description":"Principal identifier of the candidate being booked.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"scheduled_for":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled For","description":"Scheduled appearance time, if known.","examples":["2026-06-21T14:30:00Z"]},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location","description":"Where the appearance takes place (studio, remote, venue).","examples":["Studio B, Des Moines"]},"topic_brief":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Topic Brief","description":"Short brief on the topic or angle for the appearance.","examples":["Healthcare policy segment"]}},"type":"object","required":["show_id","candidate_principal_id"],"title":"WalkInRequest","description":"Request body for recording a walk-in booking as a new Hit."},"cbgreenroom__WeeklyDigestRequest":{"properties":{"target_user_id":{"type":"string","format":"uuid","title":"Target User Id","description":"The operator the weekly digest is composed for.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"period_start":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Period Start","description":"Start of the digest period (UTC). Defaults to seven days before period_end.","examples":["2026-06-14T00:00:00Z"]},"period_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Period End","description":"End of the digest period (UTC). Defaults to now.","examples":["2026-06-21T00:00:00Z"]}},"type":"object","required":["target_user_id"],"title":"WeeklyDigestRequest","description":"Body for the weekly touchpoint digest trigger."},"cbgroundgame__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbgroundgame__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbgroundgame__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbgroundgame__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbgroundgame__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbgroundgame__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbgroundgame__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbgroundgame__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbgroundgame__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbgroundgame__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbgroundgame__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbgroundgame__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbgroundgame__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbgroundgame__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbharvest__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbharvest__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbharvest__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbharvest__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbharvest__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbharvest__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbharvest__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbharvest__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbharvest__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbharvest__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbharvest__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbharvest__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbharvest__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbharvest__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbhive__AgentOutput":{"properties":{"output":{"additionalProperties":true,"type":"object","title":"Output","description":"The agent step's result — must satisfy the step's contract output schema if it has one.","examples":[{"plan":"clear on deploy","root_cause":"stale cache"}]}},"type":"object","required":["output"],"title":"AgentOutput","description":"The typed output of a suspended agent judgment step.\n\nValidated against the agent's contract (see ``GET /flows/contracts``) before\nthe run advances; a non-conforming ``output`` keeps the run parked.","examples":[{"output":{"kind":"bug","repo":"Nominate-AI/cbloom"}}]},"cbhive__ApproveRequest":{"properties":{"fork":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fork","description":"Override the triage's proposed fork: loom | stitch | both | issue. Required when the proposal was 'unsure'; omit to accept the proposal.","examples":["loom"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Optional rationale recorded with the gate decision.","examples":["approved — ship as a flash-app"]},"service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service","description":"The target repo/service this builds against. When given, the deterministic blast-radius gate runs — a never-auto corner (cbauth/money) is blocked unless override=true.","examples":["cbloom"]},"override":{"type":"boolean","title":"Override","description":"Approve anyway when the blast-radius gate flags a never-auto corner (a human takes explicit responsibility; recorded in the note).","default":false}},"type":"object","title":"ApproveRequest","description":"Gate decision. ``fork`` overrides the triage's proposal (required when the\nproposal was 'unsure'); omit to accept the proposed fork.","examples":[{"fork":"loom","note":"ship it"}]},"cbhive__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbhive__CredentialLease":{"properties":{"id":{"type":"string","title":"Id","description":"Lease id (uuid).","examples":["7f3a-..."]},"service":{"type":"string","title":"Service","description":"Target service.","examples":["cbpulse"]},"scope":{"type":"string","title":"Scope","description":"cbauth permission granted.","examples":["cbpulse.*.*"]},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Advisory role label.","examples":["admin"]},"reason":{"type":"string","title":"Reason","description":"Audit — why this lease.","examples":["cbpulse#501 Saint backfill"]},"requested_by":{"type":"string","title":"Requested By","description":"Flow id or operator handle.","examples":["loomdsl:resolve-cj-issue"]},"status":{"type":"string","title":"Status","description":"Lifecycle: requested | planned | active | denied | revoked | expired.","examples":["requested"]},"mode":{"type":"string","title":"Mode","description":"Broker mode this lease was cut under: plan | live.","examples":["plan"]},"key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Id","description":"The minted key id (None until granted)."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the lease/request was recorded."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"Request staleness window, then the credential TTL after approval."},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At","description":"When revoked/expired, if it has been."},"approved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved By","description":"Operator who passed/declined the gate.","examples":["operator"]},"approved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Approved At","description":"When the gate decision was made."},"ttl_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttl Seconds","description":"Intended TTL; expires_at is set from it at approval.","examples":[900]},"sa_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sa Email","description":"The minted service-account email (for group unwind on revoke)."},"groups":{"items":{"type":"string"},"type":"array","title":"Groups","description":"cbauth groups granted for this lease.","examples":[["finance"]]},"raw_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Key","description":"The credential — present ONLY on the one-time grant return, never persisted or re-served."}},"type":"object","required":["id","service","scope","role","reason","requested_by","status","mode","created_at","expires_at"],"title":"CredentialLease","description":"A recorded lease — who leased what scope, why, when, and its lifecycle.\n\n``raw_key`` is populated only on the one-time grant return (``approve``/\n``lease``), never persisted and never carried on a list/audit fetch.","examples":[{"created_at":"2026-06-20T10:00:00Z","expires_at":"2026-06-20T10:15:00Z","groups":["finance"],"id":"7f3a9b2c-...","mode":"plan","reason":"cbpulse#501 Saint backfill","requested_by":"loomdsl:resolve-cj-issue","role":"admin","scope":"cbpulse.*.*","service":"cbpulse","status":"requested","ttl_seconds":900}]},"cbhive__Digest":{"properties":{"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"When the digest was built."},"raw_signals":{"type":"integer","title":"Raw Signals","description":"Total raw signals before collapsing.","examples":[228]},"unique":{"type":"integer","title":"Unique","description":"Distinct items after collapsing by fingerprint.","examples":[45]},"needs_attention":{"items":{"$ref":"#/components/schemas/cbhive__DigestItem"},"type":"array","title":"Needs Attention","description":"Items worth the operator's attention, ranked."},"known_noise":{"items":{"$ref":"#/components/schemas/cbhive__DigestItem"},"type":"array","title":"Known Noise","description":"Demoted / low-severity items, ranked."}},"type":"object","required":["generated_at","raw_signals","unique","needs_attention","known_noise"],"title":"Digest","description":"The one-screen horizon view — raw signals folded into ranked distinct items,\nsplit into \"needs attention\" vs \"known noise\".","examples":[{"generated_at":"2026-06-20T10:00:00Z","known_noise":[],"needs_attention":[],"raw_signals":228,"unique":45}]},"cbhive__DigestItem":{"properties":{"fingerprint":{"type":"string","title":"Fingerprint","description":"The deduping signature for this group.","examples":["cbintel:cron:cbintel-drip:timeout"]},"title":{"type":"string","title":"Title","description":"Representative title (the most-severe occurrence).","examples":["cbcron 'cbintel-drip' - timeout x3 in 48h"]},"services":{"items":{"type":"string"},"type":"array","title":"Services","description":"Distinct services across the collapsed signals.","examples":[["cbintel"]]},"sources":{"items":{"type":"string"},"type":"array","title":"Sources","description":"Distinct intake sources.","examples":[["cbcron"]]},"severity":{"type":"string","title":"Severity","description":"Most severe across occurrences: info | warning | critical.","examples":["warning"]},"count":{"type":"integer","title":"Count","description":"Summed occurrence count.","examples":[3]},"distinct_signals":{"type":"integer","title":"Distinct Signals","description":"How many raw Signals collapsed into this.","examples":[2]},"first_seen":{"type":"string","format":"date-time","title":"First Seen","description":"Earliest occurrence across the group."},"last_seen":{"type":"string","format":"date-time","title":"Last Seen","description":"Most recent occurrence across the group."},"score":{"type":"number","title":"Score","description":"Rank score (severity x log(count) x recency x boost, ruling-adjusted).","examples":[42.5]},"boost":{"type":"number","title":"Boost","description":"Strongest non-severity adjustment across the group (picked up >1, blocked <1).","default":1.0,"examples":[1.0]},"pinned":{"type":"boolean","title":"Pinned","description":"Held open deliberately — exempt from recency decay.","default":false,"examples":[false]},"refs":{"items":{"type":"string"},"type":"array","title":"Refs","description":"Up to 10 source refs.","examples":[["cbintel-drip"]]},"ruling":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ruling","description":"Operator disposition, if any (known_noise | acknowledged | muted | watch).","examples":["known_noise"]}},"type":"object","required":["fingerprint","title","services","sources","severity","count","distinct_signals","first_seen","last_seen","score","refs"],"title":"DigestItem","description":"A fingerprint's worth of collapsed signals, ranked by score.","examples":[{"boost":1.0,"count":3,"distinct_signals":1,"fingerprint":"cbintel:cron:cbintel-drip:timeout","first_seen":"2026-06-19T00:00:00Z","last_seen":"2026-06-20T00:00:00Z","refs":["cbintel-drip"],"score":42.5,"services":["cbintel"],"severity":"warning","sources":["cbcron"],"title":"cbcron 'cbintel-drip' - timeout x3 in 48h"}]},"cbhive__DispatchRequest":{"properties":{"delivery_ref":{"type":"string","title":"Delivery Ref","description":"Where the work landed: an issue URL, flash-app id, or VirtualService name.","examples":["https://github.com/Nominate-AI/cbloom/issues/501","/app/turnout"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Optional rationale recorded with the handoff.","examples":["built /app/turnout, pending review"]}},"type":"object","required":["delivery_ref"],"title":"DispatchRequest","description":"Record the handoff to the fork delivery — the agent has built (or is\nbuilding) it. ``delivery_ref`` is the issue URL / flash-app id / VS name.","examples":[{"delivery_ref":"/app/turnout","note":"shipped"}]},"cbhive__FlowOutcome":{"properties":{"run_id":{"type":"string","title":"Run Id","description":"The flow run id this outcome records.","examples":["a1b2c3d4"]},"flow_name":{"type":"string","title":"Flow Name","description":"The flow that ran.","examples":["resolve-cj-issue"]},"status":{"type":"string","title":"Status","description":"Terminal status: done | held | failed.","examples":["done"]},"item_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Item Id","description":"The mailbox item driven (if any).","examples":[42]},"fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fingerprint","description":"The item's fingerprint — the ruling key fed back to K.","examples":["stash:victory:doc-drop"]},"repo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Repo","description":"From the classify-issue step.","examples":["Nominate-AI/cbloom"]},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind","description":"Issue class — the clustering axis.","examples":["bug"]},"cluster_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cluster Id","description":"Cluster this outcome belongs to.","examples":["cbloom:bug"]},"summary":{"type":"string","title":"Summary","description":"A one-line summary of the run.","default":""},"recorded_at":{"type":"string","format":"date-time","title":"Recorded At","description":"When the outcome was recorded."}},"type":"object","required":["run_id","flow_name","status","recorded_at"],"title":"FlowOutcome","description":"One terminal flow run's outcome — the Execute→Analyze feedback record.\n\nA ``done`` run demoted its item (acknowledged ruling + resolved); a\n``held``/``failed`` run promoted it (watch). Outcomes also accumulate into a\nledger so Analyze can spot recurring work (same repo+kind) and cluster it.","examples":[{"fingerprint":"stash:victory:doc-drop","flow_name":"resolve-cj-issue","item_id":42,"kind":"bug","recorded_at":"2026-06-20T10:00:00Z","repo":"Nominate-AI/cbloom","run_id":"a1b2c3d4","status":"done","summary":"resolve-cj-issue done — 9 steps"}]},"cbhive__FlowRun":{"properties":{"id":{"type":"string","title":"Id","description":"The run id (uuid).","examples":["a1b2c3d4"]},"flow_name":{"type":"string","title":"Flow Name","description":"The flow that is running.","examples":["resolve-cj-issue"]},"item_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Item Id","description":"Driven mailbox item id (free-form).","examples":["42"]},"status":{"type":"string","title":"Status","description":"running | awaiting_gate | awaiting_agent | done | held | failed.","examples":["awaiting_gate"]},"mode":{"type":"string","title":"Mode","description":"dry (preview) | live.","examples":["dry"]},"cursor":{"type":"integer","title":"Cursor","description":"Index of the next step to execute.","examples":[5]},"ctx":{"additionalProperties":true,"type":"object","title":"Ctx","description":"The run's accumulated context."},"step_log":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Step Log","description":"Per-step execution log (id, kind, ok, summary, ts)."},"halt_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Halt Reason","description":"The gate prompt / agent contract awaiting input, when suspended."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the run started."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the run last advanced."}},"type":"object","required":["id","flow_name","status","mode","cursor","created_at","updated_at"],"title":"FlowRun","description":"The persisted, resumable state of one flow walk.","examples":[{"created_at":"2026-06-20T10:00:00Z","ctx":{"repo":"Nominate-AI/cbloom"},"cursor":2,"flow_name":"deploy-fix","halt_reason":"Ship cbloom?","id":"a1b2c3d4","item_id":"42","mode":"live","status":"awaiting_gate","step_log":[],"updated_at":"2026-06-20T10:01:00Z"}]},"cbhive__FlowSpec":{"properties":{"flow":{"type":"string","title":"Flow","description":"The flow name.","examples":["resolve-cj-issue"]},"on":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"On","description":"Advisory trigger.","examples":["mailbox.item"]},"stages":{"items":{"type":"string"},"type":"array","title":"Stages","description":"Advisory band ordering.","examples":[["scope","fix","ship","confirm"]]},"steps":{"items":{"$ref":"#/components/schemas/cbhive__Step"},"type":"array","title":"Steps","description":"The ordered steps a runner walks."}},"type":"object","required":["flow","steps"],"title":"FlowSpec","description":"A whole flow — name, optional trigger/stages metadata, and ordered steps.","examples":[{"flow":"deploy-fix","stages":["fix","ship","confirm"],"steps":[{"band":"ship","id":"gate-deploy","kind":"gate","prompt":"Ship {{ctx.repo}}?"}]}]},"cbhive__FlowStartRequest":{"properties":{"spec":{"anyOf":[{"$ref":"#/components/schemas/cbhive__FlowSpec"},{"type":"null"}],"description":"A full inline flow spec. Provide this OR 'flow', not both."},"flow":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flow","description":"A canonical library flow name (see GET /flows/library). Provide this OR 'spec', not both.","examples":["resolve-cj-issue","deploy-fix","investigate"]},"item_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Item Id","description":"The driven mailbox item id, if any — an integer one links the run's outcome back to a mailbox row (the K-feedback loop).","examples":["42"]},"ctx":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ctx","description":"The bindings the flow's templates need (see a library flow's required_ctx). Keys are resolved into {{ctx.*}} templates at each step. Optional `confirm_check` selects what the data-level confirm asserts — one of {\"non_empty\": true} (default), {\"min_count\": N}, or {\"equals\": <value>}. Use `equals` when the deploy changes a value rather than adding one, or confirm passes without proving anything (cbhive#22).","examples":[{"number":"501","repo":"Nominate-AI/cbloom","service":"cbloom"}]},"dry_run":{"type":"boolean","title":"Dry Run","description":"Preview the entire route with no side effects (default). Live mode executes run macros and genuinely suspends at gates/agents.","default":true,"examples":[true]}},"type":"object","title":"FlowStartRequest","description":"Start a flow run — from an inline ``spec`` OR a library ``flow`` name.\n\nProvide *exactly one* of ``spec`` (a full inline FlowSpec) or ``flow`` (a\ncanonical library name from ``GET /flows/library``). ``dry_run`` (default\nTrue) previews the whole route with no side effects; live execution is the\ndeliberate arming step.","examples":[{"ctx":{"number":"501","repo":"Nominate-AI/cbloom"},"dry_run":true,"flow":"resolve-cj-issue","item_id":"42"}]},"cbhive__FlowSummary":{"properties":{"name":{"type":"string","title":"Name","description":"The canonical flow name to start it by.","examples":["resolve-cj-issue"]},"description":{"type":"string","title":"Description","description":"What the flow does.","examples":["Full Plan+Execute loop: scope→diagnose→patch→…"]},"stages":{"items":{"type":"string"},"type":"array","title":"Stages","description":"The flow's advisory stage ordering.","examples":[["scope","fix","ship","confirm"]]},"steps":{"type":"integer","title":"Steps","description":"Number of steps in the flow.","examples":[10]},"required_ctx":{"items":{"type":"string"},"type":"array","title":"Required Ctx","description":"The ctx keys the operator must supply at start (excludes step outputs).","examples":[["repo","number","service","health_url"]]}},"type":"object","required":["name","description","stages","steps","required_ctx"],"title":"FlowSummary","description":"A library entry — what `GET /flows/library` returns.","examples":[{"description":"Gated ship+verify spine for an edit already made.","name":"deploy-fix","required_ctx":["repo","service","health_url","commit_message"],"stages":["fix","ship","confirm"],"steps":6}]},"cbhive__GateDecision":{"properties":{"by":{"type":"string","title":"By","description":"The operator handle (or flow id) passing/declining the gate.","default":"operator","examples":["operator","bisenbek"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Free-text rationale. For a flow gate, set this to approve | deny | hold to carry the decision (defaults to approve).","examples":["approve","deny — needs more scope"]}},"type":"object","title":"GateDecision","description":"Who is passing/declining the gate, and (for flow gates) the decision itself.\n\nReused by the broker gate (`/broker/leases/{id}/approve|deny`) and the flow\ngate (`/flows/{run_id}/gate`). For a flow gate the decision rides in ``note``\nas ``approve``/``deny``/``hold`` (default ``approve``).","examples":[{"by":"operator","note":"approve"}]},"cbhive__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbhive__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbhive__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbhive__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbhive__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbhive__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbhive__HoldRequest":{"properties":{"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Optional rationale recorded with the transition.","examples":["parked — waiting on county data"]}},"type":"object","title":"HoldRequest","description":"Park a work-item / mark one done — carries only an optional note.","examples":[{"note":"not now"}]},"cbhive__LeaseRequest":{"properties":{"service":{"type":"string","title":"Service","description":"Target service, e.g. 'cbpulse'."},"scope":{"type":"string","title":"Scope","description":"cbauth permission to grant, e.g. 'cbpulse.*.*'."},"reason":{"type":"string","minLength":1,"title":"Reason","description":"Audit — why this lease."},"requested_by":{"type":"string","title":"Requested By","description":"Who/what — a flow id or operator handle."},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Advisory label for the role these groups map to."},"groups":{"items":{"type":"string"},"type":"array","title":"Groups","description":"cbauth groups to grant the minted SA for the lease duration (removed on revoke). The mechanism behind role-gated access — e.g. ['finance'] makes the SA cbpulse-admin (cbpulse maps finance→admin). Empty = permission-only (access without a role)."},"ttl_seconds":{"type":"integer","maximum":3600.0,"minimum":60.0,"title":"Ttl Seconds","description":"Credential lifetime in seconds (60-3600). Privileged ops are short; a long-lived lease is a smell.","default":900,"examples":[900]}},"type":"object","required":["service","scope","reason","requested_by"],"title":"LeaseRequest","description":"A request for a scoped, time-boxed credential.","examples":[{"groups":["finance"],"reason":"cbpulse#501 Saint backfill","requested_by":"loomdsl:resolve-cj-issue","role":"admin","scope":"cbpulse.*.*","service":"cbpulse","ttl_seconds":900}]},"cbhive__MailboxItem":{"properties":{"id":{"type":"integer","title":"Id","description":"Stable mailbox item id.","examples":[42]},"fingerprint":{"type":"string","title":"Fingerprint","description":"The deduping signature — a recurrence bumps an existing item.","examples":["stash:victory:doc-drop"]},"origin":{"type":"string","title":"Origin","description":"What created the item.","default":"hive","examples":["cbhive"]},"kind":{"type":"string","title":"Kind","description":"Source class: incident (fleet-health) | work-request (intake).","default":"incident","examples":["work-request"]},"title":{"type":"string","title":"Title","description":"Human one-liner.","examples":["Stash doc-drop · victory: 3 items"]},"service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service","description":"The service / workspace the item concerns.","examples":["victory"]},"severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity","description":"info | warning | critical (from the source signal).","examples":["warning"]},"status":{"type":"string","title":"Status","description":"Lifecycle: open | acknowledged | reopened | resolved.","examples":["open"]},"count":{"type":"integer","title":"Count","description":"Occurrences observed.","default":1,"examples":[3]},"first_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Seen","description":"Earliest occurrence."},"last_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen","description":"Most recent occurrence."},"opened_at":{"type":"string","format":"date-time","title":"Opened At","description":"When the item was first opened."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the item was last refreshed."},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At","description":"When resolved, if it has been."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Free-text context for the triager (set only on initial open)."}},"type":"object","required":["id","fingerprint","title","status","opened_at","updated_at"],"title":"MailboxItem","description":"A durable, fingerprint-deduped, stateful item — the delivery surface and\nthe seed of an in-house, no-GitHub issue tracker.\n\n``kind`` classes the source: ``incident`` (fleet-health) vs ``work-request``\n(intake).","examples":[{"count":3,"fingerprint":"stash:victory:doc-drop","id":42,"kind":"work-request","opened_at":"2026-06-20T09:00:00Z","origin":"cbhive","service":"victory","severity":"warning","status":"open","title":"Stash doc-drop · victory: 3 items (1 note, 2 file)","updated_at":"2026-06-20T09:30:00Z"}]},"cbhive__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbhive__MintRequest":{"properties":{"ask":{"type":"string","minLength":1,"title":"Ask","description":"Plain-English 'I want an app that does X'.","examples":["A dashboard of donor totals by month for the finance team"]},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug","description":"Optional target /app/<slug>; derived from the ask if omitted.","examples":["donor-totals"]},"requested_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested By","description":"Who asked (attribution only).","examples":["curtis@victoryphones.com"]}},"type":"object","required":["ask"],"title":"MintRequest","description":"An app-mint request — the plain-English ask that seeds a minted Loom app."},"cbhive__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbhive__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbhive__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbhive__Ruling":{"properties":{"fingerprint":{"type":"string","title":"Fingerprint","description":"The signal fingerprint ruled on (service:error-class).","examples":["cbcron:cron:cbintel-drip:timeout"]},"disposition":{"type":"string","title":"Disposition","description":"One of known_noise | acknowledged | muted (demote) or watch (promote).","examples":["known_noise"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Optional rationale recorded with the ruling.","examples":["benign skip_if_running"]},"ruled_by":{"type":"string","title":"Ruled By","description":"Who issued it — an operator handle or a flow id.","default":"operator","examples":["operator","loomdsl:resolve-cj-issue"]},"ruled_at":{"type":"string","format":"date-time","title":"Ruled At","description":"When the ruling was first created (preserved across updates)."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the ruling was last changed."}},"type":"object","required":["fingerprint","disposition","ruled_at","updated_at"],"title":"Ruling","description":"A persisted operator disposition on a signal fingerprint — the K in MAPE-K.\n\n``known_noise``/``acknowledged``/``muted`` demote the fingerprint out of\n\"needs attention\"; ``watch`` forces it in. Every later digest honors it.","examples":[{"disposition":"known_noise","fingerprint":"cbcron:cron:cbintel-drip:timeout","note":"benign skip_if_running","ruled_at":"2026-06-20T12:00:00Z","ruled_by":"operator","updated_at":"2026-06-20T12:00:00Z"}]},"cbhive__RulingRequest":{"properties":{"fingerprint":{"type":"string","title":"Fingerprint","description":"The signal fingerprint to rule on (service:error-class).","examples":["cbcron:cron:cbintel-drip:timeout"]},"disposition":{"type":"string","title":"Disposition","description":"One of known_noise | acknowledged | muted (demote) or watch (promote).","examples":["known_noise","watch"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Optional free-text rationale recorded with the ruling.","examples":["benign skip_if_running, not a real failure"]},"ruled_by":{"type":"string","title":"Ruled By","description":"Who issued the ruling — an operator handle or a flow id.","default":"operator","examples":["operator","loomdsl:resolve-cj-issue"]}},"type":"object","required":["fingerprint","disposition"],"title":"RulingRequest","description":"An operator's disposition for a signal fingerprint — the K in MAPE-K.\n\n``known_noise``/``acknowledged``/``muted`` demote the fingerprint out of\n\"needs attention\"; ``watch`` forces it in. Honored by every later digest.","examples":[{"disposition":"known_noise","fingerprint":"cbcron:cron:cbintel-drip:timeout","note":"long-running drip, not a fault","ruled_by":"operator"}]},"cbhive__Signal":{"properties":{"source":{"type":"string","title":"Source","description":"The intake adapter that produced it (e.g. cbcron | cboverseer).","examples":["cbcron"]},"service":{"type":"string","title":"Service","description":"The normalized service the signal concerns.","examples":["cbintel"]},"kind":{"type":"string","title":"Kind","description":"Short error class — what happened.","examples":["cron_timeout"]},"fingerprint":{"type":"string","title":"Fingerprint","description":"Stable (service, error-class) signature — the deduping key.","examples":["cbintel:cron:cbintel-drip:timeout"]},"severity":{"type":"string","title":"Severity","description":"How bad the thing is: info | low | warning | high | critical.","default":"warning","examples":["warning"]},"title":{"type":"string","title":"Title","description":"Human one-liner.","examples":["cbcron 'cbintel-drip' - timeout x3 in 48h"]},"count":{"type":"integer","title":"Count","description":"Occurrences observed in the window.","default":1,"examples":[3]},"first_seen":{"type":"string","format":"date-time","title":"First Seen","description":"Earliest occurrence in the window."},"last_seen":{"type":"string","format":"date-time","title":"Last Seen","description":"Most recent occurrence."},"ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref","description":"Source id / link (job id, issue url, incident id).","examples":["cbintel-drip"]},"boost":{"type":"number","title":"Boost","description":"Score multiplier for facts that are NOT severity — an operator picking this up next (>1), or its being blocked on someone else (<1). 1.0 = no adjustment.","default":1.0,"examples":[1.0]},"pinned":{"type":"boolean","title":"Pinned","description":"Exempt from recency decay. For things held open deliberately (chosen work, a standing hold): their age is not evidence they matter less, and letting them fade is how a chosen queue rots.","default":false,"examples":[false]},"detail":{"additionalProperties":true,"type":"object","title":"Detail","description":"Source-specific metadata (schedule, metric, keys, …)."}},"type":"object","required":["source","service","kind","fingerprint","title","first_seen","last_seen"],"title":"Signal","description":"One normalized thing-needing-attention — the unit a Hive service reasons over.\n\nIntake adapters turn raw events into Signals; everything downstream (dedup,\nprecedent matching, recurrence detection) keys off ``fingerprint``, which\nmust be STABLE across repeated occurrences of the same underlying thing.","examples":[{"count":3,"detail":{"failure_class":"timeout","job_name":"cbintel-drip"},"fingerprint":"cbintel:cron:cbintel-drip:timeout","first_seen":"2026-06-19T00:00:00Z","kind":"cron_timeout","last_seen":"2026-06-20T00:00:00Z","ref":"cbintel-drip","service":"cbintel","severity":"warning","source":"cbcron","title":"cbcron 'cbintel-drip' - timeout x3 in 48h"}]},"cbhive__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbhive__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbhive__Step":{"properties":{"id":{"type":"string","title":"Id","description":"Unique step id within the flow.","examples":["scope","gate-deploy"]},"kind":{"type":"string","enum":["run","assert","gate","agent"],"title":"Kind","description":"The step kind — selects which of verb/predicate/prompt/agent applies.","examples":["run"]},"band":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Band","description":"MAPE-K-ish stage label (scope/fix/ship/confirm).","examples":["ship"]},"when":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"When","description":"Optional guard — a dotted ctx path (opt. leading '!'); the step is skipped unless it resolves truthy. e.g. 'ctx.has_schema_change'."},"out":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Out","description":"ctx key to store this step's result under.","examples":["scope"]},"verb":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verb","description":"(run) The macro verb, e.g. shell | ruff_check | git_commit_push | deploy_and_health.","examples":["git_commit_push"]},"args":{"additionalProperties":true,"type":"object","title":"Args","description":"(run/assert) kwargs for the verb/predicate; string values are templated against ctx via {{ctx.*}}."},"predicate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Predicate","description":"(assert) The predicate, e.g. health_200 | endpoint_returns.","examples":["endpoint_returns"]},"prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt","description":"(gate) The operator prompt shown on suspend (templated).","examples":["Deploy {{ctx.repo}}#{{ctx.number}}?"]},"agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent","description":"(agent) The judgment-step name, e.g. classify-issue | root-cause.","examples":["classify-issue"]},"contract":{"additionalProperties":true,"type":"object","title":"Contract","description":"(agent) Extra free-form hints surfaced alongside the typed contract."}},"type":"object","required":["id","kind"],"title":"Step","description":"One node of a flow. Exactly one of run / assert / gate / agent is the kind.\n\n- ``run`` — a deterministic macro (``verb`` + ``args``). Failure = hard stop.\n- ``assert`` — a verification ``predicate``. Failure = hold (rollback).\n- ``gate`` — an operator ``prompt``. Suspends until approve/deny/hold.\n- ``agent`` — an LLM judgment step (``agent`` name + typed contract). Suspends\n  until its output is supplied.","examples":[{"agent":"classify-issue","band":"scope","id":"scope","kind":"agent","out":"scope"}]},"cbhive__TriageDoc":{"properties":{"key":{"type":"string","title":"Key","description":"The cbfiles home key for this doc.","examples":["victory/ab12/upload/note/2026-06-20/ask.txt"]},"upload_class":{"type":"string","title":"Upload Class","description":"The Stash upload class: note | file | voice.","examples":["note"]},"role":{"type":"string","title":"Role","description":"The doc's role in the pile: ask | evidence | reference.","examples":["ask"]},"summary":{"type":"string","title":"Summary","description":"One line — what this doc is / says.","default":"","examples":["Kevin's request prose"]}},"type":"object","required":["key","upload_class","role"],"title":"TriageDoc","description":"One doc from the pile, classified by the triaging agent.","examples":[{"key":"victory/ab12/upload/note/2026-06-20/ask.txt","role":"ask","summary":"Kevin's request prose","upload_class":"note"}]},"cbhive__TriageRequest":{"properties":{"summary":{"type":"string","title":"Summary","description":"What is being asked for, overall — the agent's one-paragraph read of the whole pile.","examples":["Kevin wants a per-precinct turnout dashboard plus a nightly CSV export."]},"docs":{"items":{"$ref":"#/components/schemas/cbhive__TriageDoc"},"type":"array","title":"Docs","description":"Each doc from the pile, classified (ask | evidence | reference)."},"work_items":{"items":{"$ref":"#/components/schemas/cbhive__WorkItemDraft"},"type":"array","title":"Work Items","description":"The distilled deliverables, each fork-tagged (loom | stitch | both | issue | unsure)."},"open_questions":{"items":{"type":"string"},"type":"array","title":"Open Questions","description":"Anything the agent couldn't resolve from the pile and needs the operator/requester to answer.","examples":[["Which counties are in scope?","Daily or hourly refresh?"]]},"triaged_by":{"type":"string","title":"Triaged By","description":"Which agent/operator produced this triage.","default":"claude-code","examples":["claude-code"]},"confidence":{"type":"string","title":"Confidence","description":"The agent's confidence in this read: low | medium | high.","default":"medium","examples":["medium"]}},"type":"object","required":["summary"],"title":"TriageRequest","description":"The structured read of a doc-drop, produced by the triaging agent.\n\n``workspace`` is derived from the mailbox item; everything else is the\nagent's judgment. ``work_items`` are the distilled deliverables (each\nfork-tagged: loom | stitch | both | issue | unsure) that LoomDSL orders into\na roadmap and the operator forks at the gate.","examples":[{"confidence":"medium","docs":[{"key":"victory/ab12/upload/note/2026-06-20/ask.txt","role":"ask","summary":"Kevin's request prose","upload_class":"note"}],"open_questions":["Which counties are in scope?"],"summary":"Per-precinct turnout dashboard + nightly CSV export.","triaged_by":"claude-code","work_items":[{"acceptance":["renders all precincts","filter by county"],"detail":"Loom view over the county feed.","fork":"loom","title":"Turnout-by-precinct flash-app"}]}]},"cbhive__TriageResult":{"properties":{"item_id":{"type":"integer","title":"Item Id","description":"The parent mailbox item id.","examples":[42]},"workspace":{"type":"string","title":"Workspace","description":"The workspace this triage belongs to (from the mailbox item).","examples":["victory"]},"summary":{"type":"string","minLength":1,"title":"Summary","description":"What is being asked for, overall.","examples":["Per-precinct turnout dashboard + nightly export."]},"docs":{"items":{"$ref":"#/components/schemas/cbhive__TriageDoc"},"type":"array","title":"Docs","description":"The classified pile."},"work_items":{"items":{"$ref":"#/components/schemas/cbhive__WorkItemDraft"},"type":"array","title":"Work Items","description":"The distilled fork-tagged deliverables."},"open_questions":{"items":{"type":"string"},"type":"array","title":"Open Questions","description":"Unresolved questions for the operator/requester.","examples":[["Which counties are in scope?"]]},"triaged_by":{"type":"string","title":"Triaged By","description":"Which agent produced it.","default":"claude-code","examples":["claude-code"]},"confidence":{"type":"string","title":"Confidence","description":"Confidence: low | medium | high.","default":"medium","examples":["medium"]},"triaged_at":{"type":"string","format":"date-time","title":"Triaged At","description":"First triaged (preserved across re-triage)."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last (re-)triaged."}},"type":"object","required":["item_id","workspace","summary","triaged_at","updated_at"],"title":"TriageResult","description":"The structured work-item — triage's output, attached to a work-request\nmailbox item. cbhive stores it; an agent (file access) produces it.","examples":[{"confidence":"medium","docs":[],"item_id":42,"open_questions":["Which counties are in scope?"],"summary":"Per-precinct turnout dashboard + nightly CSV export.","triaged_at":"2026-06-20T09:10:00Z","triaged_by":"claude-code","updated_at":"2026-06-20T09:10:00Z","work_items":[],"workspace":"victory"}]},"cbhive__TriageSummary":{"properties":{"item_id":{"type":"integer","title":"Item Id","description":"The parent mailbox item id.","examples":[42]},"workspace":{"type":"string","title":"Workspace","description":"The triage's workspace.","examples":["victory"]},"summary":{"type":"string","title":"Summary","description":"The overall ask, one paragraph.","examples":["Per-precinct turnout dashboard."]},"forks":{"items":{"type":"string"},"type":"array","title":"Forks","description":"Distinct forks across the work-items.","examples":[["loom","stitch"]]},"n_work_items":{"type":"integer","title":"N Work Items","description":"Count of distilled deliverables.","default":0,"examples":[3]},"confidence":{"type":"string","title":"Confidence","description":"Confidence: low | medium | high.","default":"medium","examples":["medium"]},"triaged_by":{"type":"string","title":"Triaged By","description":"Which agent produced it.","default":"claude-code","examples":["claude-code"]},"triaged_at":{"type":"string","format":"date-time","title":"Triaged At","description":"First triaged."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last (re-)triaged."}},"type":"object","required":["item_id","workspace","summary","triaged_at","updated_at"],"title":"TriageSummary","description":"Lightweight roadmap-list row (no full payload) — what ``GET /triage`` returns.","examples":[{"confidence":"medium","forks":["loom"],"item_id":42,"n_work_items":2,"summary":"Per-precinct turnout dashboard + nightly export.","triaged_at":"2026-06-20T09:10:00Z","triaged_by":"claude-code","updated_at":"2026-06-20T09:10:00Z","workspace":"victory"}]},"cbhive__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbhive__WorkItem":{"properties":{"id":{"type":"integer","title":"Id","description":"Stable work-item id.","examples":[7]},"item_id":{"type":"integer","title":"Item Id","description":"Parent mailbox item (the doc-drop).","examples":[42]},"fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fingerprint","description":"The parent mailbox item's fingerprint (e.g. screenshot:<key>) — the durable backlink from the source capture to this work-item and, once dispatched, to its delivery_ref (issue URL). A cbloom Shot embeds this same fingerprint in the PNG, closing the image→issue loop.","examples":["screenshot:kevin/publish-2026-07-10.png"]},"seq":{"type":"integer","title":"Seq","description":"Order within the triage.","examples":[0]},"title":{"type":"string","title":"Title","description":"The deliverable, one line.","examples":["Turnout-by-precinct flash-app"]},"detail":{"type":"string","title":"Detail","description":"Fuller description.","default":"","examples":["Loom view over the county feed."]},"fork":{"type":"string","title":"Fork","description":"The triage's proposed fork: loom | stitch | both | issue | unsure.","examples":["loom"]},"fork_final":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fork Final","description":"The operator's gated decision (overrides the proposal); set at approve.","examples":["loom"]},"acceptance":{"items":{"type":"string"},"type":"array","title":"Acceptance","description":"Acceptance criteria.","examples":[["renders all precincts"]]},"status":{"type":"string","title":"Status","description":"proposed | approved | held | dispatched | done.","default":"proposed","examples":["proposed"]},"delivery_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivery Ref","description":"Where it landed: issue URL / flash-app id / VS name.","examples":["/app/turnout"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Rationale from the last transition."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When materialized onto the roadmap."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last transition time."}},"type":"object","required":["id","item_id","seq","title","fork","created_at","updated_at"],"title":"WorkItem","description":"A triage deliverable promoted to an addressable, gated record — the LoomDSL\nfork. Rides a state machine: proposed → (approve|hold) → approved →\ndispatched → done, where 'approve' is the operator gate that locks the fork.","examples":[{"acceptance":["renders all precincts"],"created_at":"2026-06-20T09:10:00Z","detail":"Loom view over the county feed.","fork":"loom","id":7,"item_id":42,"seq":0,"status":"proposed","title":"Turnout-by-precinct flash-app","updated_at":"2026-06-20T09:10:00Z"}]},"cbhive__WorkItemDraft":{"properties":{"title":{"type":"string","minLength":1,"title":"Title","description":"The deliverable, one line.","examples":["Turnout-by-precinct flash-app"]},"detail":{"type":"string","title":"Detail","description":"Fuller description / context.","default":"","examples":["Loom view over the county feed."]},"fork":{"type":"string","title":"Fork","description":"Where the work goes: loom | stitch | both | issue | unsure.","default":"unsure","examples":["loom"]},"acceptance":{"items":{"type":"string"},"type":"array","title":"Acceptance","description":"Acceptance criteria — how 'done' is judged.","examples":[["renders all precincts","filter by county"]]}},"type":"object","required":["title"],"title":"WorkItemDraft","description":"A distilled deliverable — the unit LoomDSL orders into a roadmap and the\noperator forks at the gate. Mirrors CJ's issue shape (title + acceptance).","examples":[{"acceptance":["renders all precincts","filter by county"],"detail":"Loom view over the county feed.","fork":"loom","title":"Turnout-by-precinct flash-app"}]},"cbinbox__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbinbox__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbinbox__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbinbox__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbinbox__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbinbox__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbinbox__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbinbox__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbinbox__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbinbox__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbinbox__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbinbox__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbinbox__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbinbox__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbindex__Body_index_image_ocr_index_image_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"Image file for OCR + indexing"},"url":{"type":"string","title":"Url","description":"URL of image for OCR + indexing","examples":["https://example.com/document.png"]},"base64_data":{"type":"string","title":"Base64 Data","description":"Base64-encoded image data"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID for storage (auto-created if omitted)","examples":["550e8400-e29b-41d4-a716-446655440000"]},"filename":{"type":"string","title":"Filename","description":"Filename for the document title","default":"image.png","examples":["scanned_receipt.png","whiteboard_photo.jpg"]},"model":{"type":"string","title":"Model","description":"LLM model for summarization","default":"claude-sonnet-4-6","examples":["claude-sonnet-4-6"]},"if_add_node_id":{"type":"string","title":"If Add Node Id","description":"Add unique node IDs","default":"yes","examples":["yes","no"]},"if_add_node_summary":{"type":"string","title":"If Add Node Summary","description":"Generate node summaries","default":"yes","examples":["yes","no"]},"if_add_doc_description":{"type":"string","title":"If Add Doc Description","description":"Generate document description","default":"no","examples":["yes","no"]}},"type":"object","title":"Body_index_image_ocr_index_image_post"},"cbindex__Body_index_markdown_endpoint_index_markdown_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"Markdown file to index. Provide this OR `content`, not both."},"content":{"type":"string","title":"Content","description":"Raw Markdown content as a string. Provide this OR `file`, not both.","examples":["# My Document\n\n## Section 1\n\nContent here...","# API Reference\n\n## Endpoints\n\n### GET /users\n\nReturns all users."]},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Existing workspace ID. If not provided, creates a new workspace.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"filename":{"type":"string","title":"Filename","description":"Filename for the document (used when providing `content`).","default":"document.md","examples":["readme.md","documentation.md","notes.md"]},"model":{"type":"string","title":"Model","description":"AI model for analysis and summarization.","default":"claude-sonnet-4-6","examples":["claude-sonnet-4-6"]},"if_thinning":{"type":"boolean","title":"If Thinning","description":"Enable tree thinning to merge small sections. Recommended for very large documents with many small headers.","default":false},"thinning_threshold":{"type":"integer","maximum":50000.0,"minimum":100.0,"title":"Thinning Threshold","description":"Minimum token count to preserve a section during thinning. Sections below this threshold may be merged with siblings.","default":5000,"examples":[1000,5000,10000]},"summary_token_threshold":{"type":"integer","maximum":5000.0,"minimum":10.0,"title":"Summary Token Threshold","description":"Minimum section size (tokens) to generate a summary. Very short sections skip summarization.","default":200,"examples":[100,200,500]},"if_add_node_id":{"type":"string","title":"If Add Node Id","description":"Add unique node IDs for referencing.","default":"yes","examples":["yes","no"]},"if_add_node_summary":{"type":"string","title":"If Add Node Summary","description":"Generate AI summaries for each section.","default":"yes","examples":["yes","no"]},"if_add_doc_description":{"type":"string","title":"If Add Doc Description","description":"Generate overall document description.","default":"no","examples":["yes","no"]},"if_add_node_text":{"type":"string","title":"If Add Node Text","description":"Include full text in each node (increases response size).","default":"no","examples":["yes","no"]}},"type":"object","title":"Body_index_markdown_endpoint_index_markdown_post"},"cbindex__Body_index_pdf_endpoint_index_pdf_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"PDF file to index. Must have `.pdf` extension."},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Existing workspace ID to store the file in. If not provided, a new workspace is created automatically.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"model":{"type":"string","title":"Model","description":"AI model to use for analysis. Higher-capability models produce better summaries.","default":"claude-sonnet-4-6","examples":["claude-sonnet-4-6","claude-haiku-4-20250514"]},"toc_check_page_num":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Toc Check Page Num","description":"Number of pages to scan when detecting table of contents. Increase for documents with late TOC placement.","default":20,"examples":[10,20,50]},"max_page_num_each_node":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Max Page Num Each Node","description":"Maximum number of pages that can be grouped into a single tree node. Smaller values create more granular structures.","default":10,"examples":[5,10,20]},"max_token_num_each_node":{"type":"integer","maximum":100000.0,"minimum":1000.0,"title":"Max Token Num Each Node","description":"Maximum tokens per node. Limits content size for each section to stay within model context limits.","default":20000,"examples":[10000,20000,50000]},"if_add_node_id":{"type":"string","title":"If Add Node Id","description":"Whether to add unique node IDs (e.g., '0001', '0002'). Required for precise node references.","default":"yes","examples":["yes","no"]},"if_add_node_summary":{"type":"string","title":"If Add Node Summary","description":"Whether to generate AI summaries for each node. Summaries enable quick content understanding without full reads.","default":"yes","examples":["yes","no"]},"if_add_doc_description":{"type":"string","title":"If Add Doc Description","description":"Whether to generate an overall document description. Useful for document cataloging and search.","default":"no","examples":["yes","no"]},"if_add_node_text":{"type":"string","title":"If Add Node Text","description":"Whether to include full text content in each node. Warning: Significantly increases response size.","default":"no","examples":["yes","no"]}},"type":"object","required":["file"],"title":"Body_index_pdf_endpoint_index_pdf_post"},"cbindex__Body_index_text_ocr_index_text_post":{"properties":{"content":{"type":"string","title":"Content","description":"Plain text content to index","examples":["Meeting agenda:\n\n1. Review Q4 results\n2. Plan Q1 goals"]},"title":{"type":"string","title":"Title","description":"Document title (used as markdown header)","default":"Document","examples":["Meeting Notes","Research Summary","Email Thread"]},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID for storage (auto-created if omitted)","examples":["550e8400-e29b-41d4-a716-446655440000"]},"model":{"type":"string","title":"Model","description":"LLM model for summarization","default":"claude-sonnet-4-6","examples":["claude-sonnet-4-6"]},"if_add_node_id":{"type":"string","title":"If Add Node Id","description":"Add unique node IDs","default":"yes","examples":["yes","no"]},"if_add_node_summary":{"type":"string","title":"If Add Node Summary","description":"Generate node summaries","default":"yes","examples":["yes","no"]},"if_add_doc_description":{"type":"string","title":"If Add Doc Description","description":"Generate document description","default":"no","examples":["yes","no"]}},"type":"object","required":["content"],"title":"Body_index_text_ocr_index_text_post"},"cbindex__Body_index_youtube_intel_index_youtube_post":{"properties":{"youtube_url":{"type":"string","title":"Youtube Url","description":"YouTube video URL","examples":["https://www.youtube.com/watch?v=dQw4w9WgXcQ","https://youtu.be/dQw4w9WgXcQ"]},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID for storage (auto-created if omitted)","examples":["550e8400-e29b-41d4-a716-446655440000"]},"model":{"type":"string","title":"Model","description":"LLM model for summarization","default":"claude-sonnet-4-6","examples":["claude-sonnet-4-6"]},"if_add_node_id":{"type":"string","title":"If Add Node Id","description":"Add unique node IDs","default":"yes","examples":["yes","no"]},"if_add_node_summary":{"type":"string","title":"If Add Node Summary","description":"Generate node summaries","default":"yes","examples":["yes","no"]},"if_add_doc_description":{"type":"string","title":"If Add Doc Description","description":"Generate document description","default":"no","examples":["yes","no"]}},"type":"object","required":["youtube_url"],"title":"Body_index_youtube_intel_index_youtube_post"},"cbindex__Body_perform_ocr_ocr_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"Image file for OCR (PNG, JPEG, GIF, WebP, TIFF)"},"url":{"type":"string","title":"Url","description":"Public URL of image for OCR","examples":["https://example.com/document.png"]},"base64_data":{"type":"string","title":"Base64 Data","description":"Base64-encoded image data (without data URI prefix)"}},"type":"object","title":"Body_perform_ocr_ocr_post"},"cbindex__Body_submit_transcript_job_intel_transcript_post":{"properties":{"youtube_url":{"type":"string","title":"Youtube Url","description":"YouTube video URL","examples":["https://www.youtube.com/watch?v=dQw4w9WgXcQ","https://youtu.be/dQw4w9WgXcQ"]}},"type":"object","required":["youtube_url"],"title":"Body_submit_transcript_job_intel_transcript_post"},"cbindex__Body_upload_file_workspaces__workspace_id__files_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"File to upload (PDF, Markdown, Image, or Text)"}},"type":"object","required":["file"],"title":"Body_upload_file_workspaces__workspace_id__files_post"},"cbindex__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbindex__Conversation":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id","description":"Unique conversation identifier"},"workspace_id":{"type":"string","title":"Workspace Id","description":"Workspace this conversation belongs to"},"title":{"type":"string","title":"Title","description":"Conversation title (can be auto-generated)","default":"New Conversation"},"messages":{"items":{"$ref":"#/components/schemas/cbindex__Message"},"type":"array","title":"Messages","description":"Ordered list of messages in the conversation"},"document_scope":{"items":{"type":"string"},"type":"array","title":"Document Scope","description":"File IDs to limit queries to (empty = all docs)"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Conversation creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last activity timestamp"}},"type":"object","required":["workspace_id"],"title":"Conversation","description":"A multi-turn conversation with a workspace.\n\nConversations maintain context across multiple exchanges\nand can be scoped to specific documents within a workspace.","examples":[{"conversation_id":"conv_abc123","created_at":"2025-01-22T10:00:00Z","document_scope":[],"messages":[],"title":"Research Paper Questions","updated_at":"2025-01-22T10:30:00Z","workspace_id":"550e8400-e29b-41d4-a716-446655440000"}]},"cbindex__ConversationCreateRequest":{"properties":{"title":{"type":"string","title":"Title","description":"Initial conversation title","default":"New Conversation"},"document_scope":{"items":{"type":"string"},"type":"array","title":"Document Scope","description":"File IDs to scope this conversation to"}},"type":"object","title":"ConversationCreateRequest","description":"Request to create a new conversation."},"cbindex__ConversationCreateResponse":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id","description":"New conversation ID"},"title":{"type":"string","title":"Title","description":"Conversation title"},"workspace_id":{"type":"string","title":"Workspace Id","description":"Workspace ID"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp"}},"type":"object","required":["conversation_id","title","workspace_id","created_at"],"title":"ConversationCreateResponse","description":"Response after creating a conversation."},"cbindex__ConversationListResponse":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id","description":"Workspace identifier"},"conversations":{"items":{"$ref":"#/components/schemas/cbindex__Conversation"},"type":"array","title":"Conversations","description":"List of conversations (without full message history)"}},"type":"object","required":["workspace_id"],"title":"ConversationListResponse","description":"Response listing all conversations in a workspace."},"cbindex__DeleteResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Operation status","default":"deleted"},"id":{"type":"string","title":"Id","description":"ID of deleted resource"}},"type":"object","required":["id"],"title":"DeleteResponse","description":"Generic delete operation response."},"cbindex__DocumentIndex":{"properties":{"file_id":{"type":"string","title":"File Id","description":"File identifier from the workspace (16-char hash prefix)","examples":["a1b2c3d4e5f67890"]},"doc_name":{"type":"string","title":"Doc Name","description":"Original document filename","examples":["research_paper.pdf","api_docs.md"]},"doc_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Doc Description","description":"LLM-generated document description","examples":["A technical research paper on neural network optimization"]},"structure":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Structure","description":"Hierarchical tree structure from indexing"},"indexed_at":{"type":"string","format":"date-time","title":"Indexed At","description":"UTC timestamp when document was indexed"},"total_nodes":{"type":"integer","minimum":0.0,"title":"Total Nodes","description":"Total number of nodes in the tree","default":0},"max_depth":{"type":"integer","minimum":0.0,"title":"Max Depth","description":"Maximum depth of the tree structure","default":0}},"type":"object","required":["file_id","doc_name","structure"],"title":"DocumentIndex","description":"Persisted index for a document.\n\nStores the hierarchical tree structure generated by indexing\nalong with metadata about the indexing operation.\n\n## Storage Location\n\nIndexes are stored at:\n`workspaces/{workspace_id}/indexes/{file_id}.json`\n\n## Tree Structure\n\nThe `structure` field contains the complete document tree,\nidentical to what is returned from the indexing endpoints.","examples":[{"doc_description":"A research paper on machine learning","doc_name":"research_paper.pdf","file_id":"a1b2c3d4e5f67890","indexed_at":"2025-01-22T10:30:00Z","max_depth":3,"structure":[{"end_index":1,"node_id":"0001","nodes":[],"start_index":1,"summary":"Paper overview...","title":"Abstract"}],"total_nodes":15}]},"cbindex__ErrorDetail":{"properties":{"field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field","description":"Name of the field that caused the error","examples":["file","workspace_id","model"]},"message":{"type":"string","title":"Message","description":"Human-readable error description","examples":["File type not supported","Workspace not found"]},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code","description":"Machine-readable error code","examples":["INVALID_FILE_TYPE","NOT_FOUND","VALIDATION_ERROR"]}},"type":"object","required":["message"],"title":"ErrorDetail","description":"Detailed error information for debugging.\n\nProvides structured error details including field-level validation\nerrors and optional context for troubleshooting."},"cbindex__ErrorResponse":{"properties":{"error":{"type":"string","title":"Error","description":"Primary error message","examples":["Invalid file type","Workspace not found","API key required"]},"details":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbindex__ErrorDetail"},"type":"array"},{"type":"null"}],"title":"Details","description":"Additional error details for debugging"},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id","description":"Unique request identifier for support","examples":["req_abc123xyz"]}},"type":"object","required":["error"],"title":"ErrorResponse","description":"Standardized error response format.\n\nAll API errors return this structure for consistent error handling.\n\n## Error Categories\n\n| Status Code | Category | Description |\n|-------------|----------|-------------|\n| 400 | Bad Request | Invalid input or validation failure |\n| 401 | Unauthorized | Missing or invalid API key |\n| 404 | Not Found | Resource does not exist |\n| 422 | Validation Error | Request body validation failed |\n| 500 | Internal Error | Server-side processing failure |\n\n## Response Interpretation for LLMs\n\nUse the `error` field for user-facing messages and `details` for\nprogrammatic error handling and field-specific fixes.","examples":[{"details":[{"code":"INVALID_FILE_TYPE","field":"file","message":"Expected PDF file, got text/plain"}],"error":"Invalid file type","request_id":"req_abc123xyz"},{"details":[{"code":"NOT_FOUND","field":"workspace_id","message":"No workspace with ID 'invalid-uuid'"}],"error":"Workspace not found"},{"details":[{"code":"UNAUTHORIZED","field":"X-API-Key","message":"Missing or invalid API key header"}],"error":"Authentication required"}]},"cbindex__FileDeleteResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Operation status","default":"deleted","examples":["deleted"]},"file_id":{"type":"string","title":"File Id","description":"ID of the deleted file","examples":["a1b2c3d4e5f67890"]},"workspace_id":{"type":"string","title":"Workspace Id","description":"Workspace the file was deleted from","examples":["550e8400-e29b-41d4-a716-446655440000"]}},"type":"object","required":["file_id","workspace_id"],"title":"FileDeleteResponse","description":"Response after successfully deleting a file."},"cbindex__FileMetadata":{"properties":{"file_id":{"type":"string","maxLength":16,"minLength":16,"title":"File Id","description":"Unique file identifier (16-char hash prefix)","examples":["a1b2c3d4e5f67890"]},"original_filename":{"type":"string","title":"Original Filename","description":"Original uploaded filename as provided by client","examples":["quarterly_report.pdf","README.md","diagram.png"]},"stored_filename":{"type":"string","title":"Stored Filename","description":"Actual filename on disk (hash prefix + sanitized name)","examples":["a1b2c3d4e5f67890_quarterly_report.pdf"]},"file_hash":{"type":"string","maxLength":64,"minLength":64,"title":"File Hash","description":"Full SHA-256 hash of file content (64 hex characters)","examples":["a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890"]},"file_type":{"$ref":"#/components/schemas/cbindex__FileType","description":"Detected file type based on extension","examples":["pdf","markdown","image"]},"size_bytes":{"type":"integer","minimum":0.0,"title":"Size Bytes","description":"File size in bytes","examples":[1024,1048576,52428800]},"uploaded_at":{"type":"string","format":"date-time","title":"Uploaded At","description":"UTC timestamp when file was uploaded","examples":["2025-01-22T10:30:00Z"]},"index_processed":{"type":"boolean","title":"Index Processed","description":"Whether the file has been processed by the indexer","default":false,"examples":[true,false]}},"type":"object","required":["file_id","original_filename","stored_filename","file_hash","file_type","size_bytes"],"title":"FileMetadata","description":"Metadata for a file stored in a workspace.\n\nTracks all file information including content hash for deduplication,\nstorage location, and indexing status.\n\n## File Identification\n\nThe `file_id` is derived from the first 16 characters of the SHA-256\nhash, providing a unique short identifier for API operations.\n\n## Storage Naming Convention\n\nFiles are stored as: `{hash_prefix}_{sanitized_original_name}.{ext}`\n\nThis ensures:\n- Unique filenames (via hash prefix)\n- Human-readable identification (via original name)\n- Correct file extension preservation","examples":[{"file_hash":"a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890","file_id":"a1b2c3d4e5f67890","file_type":"pdf","index_processed":true,"original_filename":"research_paper.pdf","size_bytes":2048576,"stored_filename":"a1b2c3d4e5f67890_research_paper.pdf","uploaded_at":"2025-01-22T10:30:00Z"}]},"cbindex__FileType":{"type":"string","enum":["pdf","markdown","image","text"],"title":"FileType","description":"Supported file types for workspace storage.\n\n## Type Detection\n\nFile type is determined by extension during upload:\n\n| Extension | FileType |\n|-----------|----------|\n| `.pdf` | PDF |\n| `.md`, `.markdown` | MARKDOWN |\n| `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp` | IMAGE |\n| `.txt` | TEXT |"},"cbindex__FileUploadResponse":{"properties":{"file_id":{"type":"string","title":"File Id","description":"Unique file identifier for API operations","examples":["a1b2c3d4e5f67890"]},"original_filename":{"type":"string","title":"Original Filename","description":"Original filename as uploaded","examples":["quarterly_report.pdf"]},"stored_filename":{"type":"string","title":"Stored Filename","description":"Filename as stored on disk","examples":["a1b2c3d4e5f67890_quarterly_report.pdf"]},"file_type":{"$ref":"#/components/schemas/cbindex__FileType","description":"Detected file type","examples":["pdf"]},"size_bytes":{"type":"integer","minimum":0.0,"title":"Size Bytes","description":"File size in bytes","examples":[2048576]},"workspace_id":{"type":"string","title":"Workspace Id","description":"Workspace containing the file","examples":["550e8400-e29b-41d4-a716-446655440000"]}},"type":"object","required":["file_id","original_filename","stored_filename","file_type","size_bytes","workspace_id"],"title":"FileUploadResponse","description":"Response after successfully uploading a file.\n\n## Duplicate Detection\n\nIf the uploaded file content matches an existing file (by SHA-256 hash),\nthe response returns the existing file's metadata without creating\na duplicate. The `file_id` will be the same as the original upload.","examples":[{"file_id":"a1b2c3d4e5f67890","file_type":"pdf","original_filename":"research_paper.pdf","size_bytes":2048576,"stored_filename":"a1b2c3d4e5f67890_research_paper.pdf","workspace_id":"550e8400-e29b-41d4-a716-446655440000"}]},"cbindex__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbindex__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbindex__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbindex__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbindex__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbindex__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbindex__ImageSource":{"type":"string","enum":["file","url","base64"],"title":"ImageSource","description":"Source type for image input.\n\nDetermines how the image data is provided to the OCR endpoint.\n\n## Source Selection Guide\n\n| Source | Use When |\n|--------|----------|\n| `file` | Uploading from local filesystem |\n| `url` | Image is publicly accessible online |\n| `base64` | Embedding image in JSON payload |"},"cbindex__IndexListResponse":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id","description":"Workspace identifier"},"indexes":{"items":{"$ref":"#/components/schemas/cbindex__DocumentIndex"},"type":"array","title":"Indexes","description":"List of indexed documents"}},"type":"object","required":["workspace_id"],"title":"IndexListResponse","description":"Response listing all indexed documents in a workspace."},"cbindex__IndexResult":{"properties":{"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"UUID of the workspace containing this document","examples":["550e8400-e29b-41d4-a716-446655440000"]},"doc_name":{"type":"string","title":"Doc Name","description":"Document filename or identifier","examples":["research_paper.pdf","api_documentation.md"]},"doc_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Doc Description","description":"LLM-generated description of the entire document","examples":["A technical research paper on neural network optimization techniques"]},"structure":{"items":{"$ref":"#/components/schemas/cbindex__TreeNode"},"type":"array","title":"Structure","description":"Hierarchical tree structure of document sections"}},"type":"object","required":["doc_name","structure"],"title":"IndexResult","description":"Result from indexing a document.\n\nContains the hierarchical tree structure extracted from the document\nalong with optional workspace tracking and document-level metadata.\n\n## Response Interpretation for LLMs\n\nThe `structure` field contains the complete document tree. To navigate:\n\n1. **Top-level nodes** represent major sections (chapters, parts)\n2. **Nested nodes** represent subsections at increasing depth\n3. **Leaf nodes** are the most granular sections\n\nUse `node_id` values to reference specific sections in queries.\nUse `summary` fields for semantic understanding without reading full text.\n\n## Workspace Integration\n\nWhen `workspace_id` is present, the indexed document has been stored\nin a workspace for future retrieval and multi-document operations.","examples":[{"doc_description":"Research paper on machine learning","doc_name":"research_paper.pdf","structure":[{"end_index":1,"node_id":"0001","nodes":[],"start_index":1,"summary":"Summarizes key findings","title":"Abstract"}],"workspace_id":"550e8400-e29b-41d4-a716-446655440000"}]},"cbindex__JobStatus":{"type":"string","enum":["pending","processing","completed","failed"],"title":"JobStatus","description":"Status of an Intel job.\n\nJobs progress through states linearly:\n\n```\npending -> processing -> completed\n                      |-> failed\n```\n\n## Status Descriptions\n\n| Status | Description |\n|--------|-------------|\n| `pending` | Job submitted, waiting to start |\n| `processing` | Actively extracting transcript |\n| `completed` | Transcript ready for retrieval |\n| `failed` | Extraction failed (see error_message) |"},"cbindex__JobStatusResponse":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Job identifier","examples":["job_abc123xyz"]},"status":{"$ref":"#/components/schemas/cbindex__JobStatus","description":"Current job status","examples":["pending","processing","completed","failed"]},"youtube_url":{"type":"string","title":"Youtube Url","description":"YouTube video URL being processed","examples":["https://www.youtube.com/watch?v=dQw4w9WgXcQ"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when job was created","examples":["2025-01-22T10:00:00Z"]},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"UTC timestamp when job completed","examples":["2025-01-22T10:01:30Z"]},"result":{"anyOf":[{"$ref":"#/components/schemas/cbindex__TranscriptResult"},{"type":"null"}],"description":"Transcript result (only when status=completed)"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Error message (only when status=failed)","examples":["No captions available for this video"]}},"type":"object","required":["job_id","status","youtube_url","created_at"],"title":"JobStatusResponse","description":"Response for job status check.\n\nProvides complete job information including the result when completed.\n\n## Status-Dependent Fields\n\n| Status | `result` | `error_message` |\n|--------|----------|-----------------|\n| pending | null | null |\n| processing | null | null |\n| completed | TranscriptResult | null |\n| failed | null | Error description |\n\n## Response Interpretation for LLMs\n\nCheck `status` first:\n- If `completed`: Use `result.transcript` for the content\n- If `failed`: Report `error_message` to user\n- Otherwise: Continue polling","examples":[{"completed_at":"2025-01-22T10:01:30Z","created_at":"2025-01-22T10:00:00Z","job_id":"job_abc123xyz","result":{"duration_seconds":1845.0,"job_id":"job_abc123xyz","transcript":"Hello everyone, welcome...","video_title":"Introduction to Machine Learning","youtube_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"},"status":"completed","youtube_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"},{"completed_at":"2025-01-22T11:00:02Z","created_at":"2025-01-22T11:00:00Z","error_message":"Video not found or is private","job_id":"job_def456uvw","status":"failed","youtube_url":"https://www.youtube.com/watch?v=invalid"}]},"cbindex__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbindex__Message":{"properties":{"message_id":{"type":"string","title":"Message Id","description":"Unique message identifier"},"role":{"type":"string","enum":["user","assistant"],"title":"Role","description":"Message sender role"},"content":{"type":"string","title":"Content","description":"Message text content"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp","description":"UTC timestamp when message was created"},"references":{"items":{"$ref":"#/components/schemas/cbindex__NodeReference"},"type":"array","title":"References","description":"Source references for assistant messages"}},"type":"object","required":["role","content"],"title":"Message","description":"A single message in a conversation.\n\nMessages track the full dialog history including\nsource references for assistant responses.","examples":[{"content":"What are the main findings of this paper?","message_id":"msg_123","references":[],"role":"user","timestamp":"2025-01-22T10:30:00Z"},{"content":"The main findings include...","message_id":"msg_124","references":[{"doc_name":"paper.pdf","end_index":18,"file_id":"a1b2c3d4e5f67890","node_id":"0005","start_index":12,"title":"4. Results"}],"role":"assistant","timestamp":"2025-01-22T10:30:05Z"}]},"cbindex__NodeReference":{"properties":{"file_id":{"type":"string","title":"File Id","description":"Document file identifier","examples":["a1b2c3d4e5f67890"]},"doc_name":{"type":"string","title":"Doc Name","description":"Document filename for display","examples":["research_paper.pdf"]},"node_id":{"type":"string","title":"Node Id","description":"Node identifier within the document","examples":["0001","0042"]},"title":{"type":"string","title":"Title","description":"Node/section title","examples":["Chapter 1: Introduction","3.2 Methods"]},"start_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Start Index","description":"Starting page/line number"},"end_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"End Index","description":"Ending page/line number"},"relevance_score":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Relevance Score","description":"Relevance score from retrieval (0.0-1.0)"}},"type":"object","required":["file_id","doc_name","node_id","title"],"title":"NodeReference","description":"Reference to a specific node in a document tree.\n\nUsed to cite sources when generating answers.","examples":[{"doc_name":"research_paper.pdf","end_index":8,"file_id":"a1b2c3d4e5f67890","node_id":"0003","relevance_score":0.95,"start_index":5,"title":"2.1 Related Work"}]},"cbindex__OCRResult":{"properties":{"text":{"type":"string","title":"Text","description":"Extracted text content from the image","examples":["INVOICE\n\nInvoice #: 12345\nDate: January 22, 2025\n\nItems:\n..."]},"source_type":{"$ref":"#/components/schemas/cbindex__ImageSource","description":"Source type that was used for OCR","examples":["file","url","base64"]},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename","description":"Original filename if provided","examples":["receipt.png"]},"confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence","description":"OCR confidence score (0.0 to 1.0) if available","examples":[0.95,0.87,0.72]}},"type":"object","required":["text","source_type"],"title":"OCRResult","description":"Result from OCR text extraction.\n\nContains the extracted text and metadata about the OCR operation.\n\n## Text Quality\n\nThe `confidence` field (when available) indicates OCR accuracy:\n\n| Range | Quality |\n|-------|---------|\n| 0.95+ | Excellent - clear printed text |\n| 0.85-0.95 | Good - minor issues possible |\n| 0.70-0.85 | Fair - review recommended |\n| <0.70 | Poor - significant errors likely |\n\n## Response Interpretation for LLMs\n\nThe `text` field contains the raw extracted text with layout\npreserved as much as possible. For structured analysis, consider\nusing `/ocr/index/image` to get a hierarchical tree structure.","examples":[{"confidence":0.94,"filename":"receipt.png","source_type":"file","text":"RECEIPT\n\nStore: Example Shop\nDate: 2025-01-22\n\nItem 1: $10.00\nItem 2: $25.00\n\nTotal: $35.00"},{"confidence":0.88,"filename":"whiteboard.jpg","source_type":"url","text":"Meeting Notes\n\nAttendees: Alice, Bob\n\nDiscussion points:\n- Budget review\n- Timeline update"}]},"cbindex__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbindex__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbindex__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbindex__QueryRequest":{"properties":{"query":{"type":"string","minLength":1,"title":"Query","description":"Natural language question to answer","examples":["What are the main findings?","Can you elaborate on the methodology?","What data was used in the experiments?"]},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"Existing conversation ID for context. If not provided, creates a new conversation."},"document_scope":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Document Scope","description":"Limit query to specific file IDs. If not provided, searches all indexed documents."},"model":{"type":"string","title":"Model","description":"LLM model for retrieval and answer generation","default":"claude-sonnet-4-6"}},"type":"object","required":["query"],"title":"QueryRequest","description":"Request to query indexed documents.\n\nSupports both new queries and follow-up questions\nwithin an existing conversation context.","examples":[{"query":"What are the main findings of this paper?"},{"conversation_id":"conv_abc123","document_scope":["a1b2c3d4e5f67890"],"query":"Can you explain point 2 in more detail?"}]},"cbindex__QueryResponse":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id","description":"Conversation ID (new or existing)"},"answer":{"type":"string","title":"Answer","description":"Generated answer based on document content"},"references":{"items":{"$ref":"#/components/schemas/cbindex__NodeReference"},"type":"array","title":"References","description":"Source nodes cited in the answer"}},"type":"object","required":["conversation_id","answer"],"title":"QueryResponse","description":"Response from querying indexed documents.\n\nContains the generated answer along with source\nreferences and conversation tracking information.","examples":[{"answer":"The main findings of this paper include: 1) Neural networks can be optimized using... 2) The proposed method achieves 95% accuracy...","conversation_id":"conv_abc123","references":[{"doc_name":"research_paper.pdf","end_index":18,"file_id":"a1b2c3d4e5f67890","node_id":"0005","start_index":12,"title":"4. Results"}]}]},"cbindex__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbindex__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbindex__TranscriptJob":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Unique job identifier for status polling","examples":["job_abc123xyz"]},"youtube_url":{"type":"string","title":"Youtube Url","description":"YouTube video URL being processed","examples":["https://www.youtube.com/watch?v=dQw4w9WgXcQ"]},"status":{"$ref":"#/components/schemas/cbindex__JobStatus","description":"Current job status","examples":["pending","processing"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when job was created","examples":["2025-01-22T10:00:00Z"]},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"UTC timestamp when job completed (null if pending/processing)","examples":["2025-01-22T10:01:30Z"]},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Error message if job failed","examples":["No captions available for this video"]}},"type":"object","required":["job_id","youtube_url","status"],"title":"TranscriptJob","description":"Job information for transcript extraction.\n\nReturned immediately when submitting a transcript request.\nUse the `job_id` to poll for completion status.\n\n## Polling Strategy\n\nRecommended polling interval: 2-5 seconds\n\n```python\nwhile True:\n    status = await get_job_status(job_id)\n    if status.status in [\"completed\", \"failed\"]:\n        break\n    await asyncio.sleep(3)\n```","examples":[{"created_at":"2025-01-22T10:00:00Z","job_id":"job_abc123xyz","status":"pending","youtube_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"},{"completed_at":"2025-01-22T10:00:05Z","created_at":"2025-01-22T10:00:00Z","error_message":"No captions available for this video","job_id":"job_abc123xyz","status":"failed","youtube_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"}]},"cbindex__TranscriptResult":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Job identifier that produced this result","examples":["job_abc123xyz"]},"youtube_url":{"type":"string","title":"Youtube Url","description":"Original YouTube video URL","examples":["https://www.youtube.com/watch?v=dQw4w9WgXcQ"]},"video_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Title","description":"Video title from YouTube","examples":["How to Build a RAG System - Complete Tutorial"]},"transcript":{"type":"string","title":"Transcript","description":"Full transcript text content","examples":["Welcome to this tutorial on building RAG systems. Today we'll cover the fundamentals of retrieval augmented generation..."]},"duration_seconds":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Duration Seconds","description":"Video duration in seconds","examples":[1234.5,3600.0]}},"type":"object","required":["job_id","youtube_url","transcript"],"title":"TranscriptResult","description":"Result from successful transcript extraction.\n\nContains the full transcript text along with video metadata.\n\n## Transcript Format\n\nThe `transcript` field contains plain text with natural paragraph\nbreaks. Timestamps are not included in the raw text but may be\nadded during markdown conversion.\n\n## Response Interpretation for LLMs\n\nUse `video_title` for document naming and `duration_seconds` to\nestimate content length. Long videos (>30 min) may have extensive\ntranscripts suitable for hierarchical indexing.","examples":[{"duration_seconds":1845.0,"job_id":"job_abc123xyz","transcript":"Hello everyone, welcome to this introduction to machine learning. In this video, we'll cover the basics...","video_title":"Introduction to Machine Learning","youtube_url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"}]},"cbindex__TreeNode":{"properties":{"title":{"type":"string","title":"Title","description":"Section title extracted from document structure","examples":["Chapter 1: Introduction","Executive Summary","3.2 Methods"]},"node_id":{"anyOf":[{"type":"string","pattern":"^\\d{4}$"},{"type":"null"}],"title":"Node Id","description":"Unique identifier within the document tree (4-digit zero-padded)","examples":["0001","0042","0100"]},"start_index":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Start Index","description":"Starting page number (1-indexed) for PDF documents","examples":[1,15,42]},"end_index":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"End Index","description":"Ending page number (inclusive) for PDF documents","examples":[5,20,50]},"line_num":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Line Num","description":"Source line number for Markdown documents","examples":[1,100,500]},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"LLM-generated summary of this section's content","examples":["Introduces the research problem and outlines the paper structure","Presents experimental results comparing three approaches"]},"prefix_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prefix Summary","description":"Cumulative summary including parent context","examples":["Chapter 1 > Section 1.2: Background on machine learning fundamentals"]},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Full text content of this section (when if_add_node_text=yes)","examples":["This section describes the methodology used in our experiments..."]},"nodes":{"items":{"$ref":"#/components/schemas/cbindex__TreeNode"},"type":"array","title":"Nodes","description":"Child nodes representing subsections"}},"type":"object","required":["title"],"title":"TreeNode","description":"A node in the PageIndex hierarchical document tree.\n\nTreeNode represents a logical section of a document (chapter, section,\nsubsection, etc.) with optional LLM-generated metadata. Nodes form a\nrecursive tree structure via the `nodes` field.\n\n## Node Identification\n\nEach node has a unique `node_id` within its document, formatted as a\nzero-padded 4-digit string (e.g., \"0001\", \"0002\"). The ID ordering\nfollows document structure traversal.\n\n## Page/Line Positioning\n\nFor PDFs, `start_index` and `end_index` indicate page numbers.\nFor Markdown, `line_num` indicates the source line number.\n\n## LLM-Generated Content\n\nThe `summary` and `prefix_summary` fields contain AI-generated\ndescriptions of the section content, useful for retrieval and navigation.","examples":[{"end_index":5,"node_id":"0001","nodes":[{"end_index":3,"node_id":"0002","nodes":[],"start_index":2,"summary":"Provides historical context","title":"1.1 Background"}],"start_index":1,"summary":"Introduces the research problem and motivation","title":"Chapter 1: Introduction"}]},"cbindex__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbindex__WorkspaceCreateResponse":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id","description":"UUID of the newly created workspace","examples":["550e8400-e29b-41d4-a716-446655440000"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp of workspace creation","examples":["2025-01-22T10:00:00Z"]}},"type":"object","required":["workspace_id","created_at"],"title":"WorkspaceCreateResponse","description":"Response after successfully creating a workspace.\n\nThe workspace is immediately ready for file uploads after creation.","examples":[{"created_at":"2025-01-22T10:00:00Z","workspace_id":"550e8400-e29b-41d4-a716-446655440000"}]},"cbindex__WorkspaceDeleteResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Operation status","default":"deleted","examples":["deleted"]},"workspace_id":{"type":"string","title":"Workspace Id","description":"UUID of the deleted workspace","examples":["550e8400-e29b-41d4-a716-446655440000"]}},"type":"object","required":["workspace_id"],"title":"WorkspaceDeleteResponse","description":"Response after successfully deleting a workspace."},"cbindex__WorkspaceInfo":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id","description":"Unique workspace identifier (UUID v4)","examples":["550e8400-e29b-41d4-a716-446655440000"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when workspace was created","examples":["2025-01-22T10:00:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp of last modification","examples":["2025-01-22T14:30:00Z"]},"file_count":{"type":"integer","minimum":0.0,"title":"File Count","description":"Number of files in the workspace","examples":[0,5,42]},"total_size_bytes":{"type":"integer","minimum":0.0,"title":"Total Size Bytes","description":"Total size of all files in bytes","examples":[0,1048576,104857600]}},"type":"object","required":["workspace_id","created_at","updated_at","file_count","total_size_bytes"],"title":"WorkspaceInfo","description":"Summary information about a workspace.\n\nLightweight representation for listing workspaces without\nloading full file metadata. Useful for dashboards and overview pages.","examples":[{"created_at":"2025-01-22T10:00:00Z","file_count":3,"total_size_bytes":5242880,"updated_at":"2025-01-22T14:30:00Z","workspace_id":"550e8400-e29b-41d4-a716-446655440000"}]},"cbindex__WorkspaceListResponse":{"properties":{"workspaces":{"items":{"$ref":"#/components/schemas/cbindex__WorkspaceInfo"},"type":"array","title":"Workspaces","description":"List of workspace summaries"}},"type":"object","required":["workspaces"],"title":"WorkspaceListResponse","description":"Response for listing all workspaces.\n\nReturns an array of workspace summaries sorted by creation date\n(newest first).","examples":[{"workspaces":[{"created_at":"2025-01-22T10:00:00Z","file_count":3,"total_size_bytes":5242880,"updated_at":"2025-01-22T14:30:00Z","workspace_id":"550e8400-e29b-41d4-a716-446655440000"},{"created_at":"2025-01-21T08:00:00Z","file_count":0,"total_size_bytes":0,"updated_at":"2025-01-21T08:00:00Z","workspace_id":"660e8400-e29b-41d4-a716-446655440001"}]}]},"cbindex__WorkspaceManifest":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id","description":"Unique workspace identifier (UUID v4)","examples":["550e8400-e29b-41d4-a716-446655440000"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when workspace was created","examples":["2025-01-22T10:00:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp of last modification","examples":["2025-01-22T14:30:00Z"]},"files":{"items":{"$ref":"#/components/schemas/cbindex__FileMetadata"},"type":"array","title":"Files","description":"List of all files in the workspace"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"User-defined metadata (arbitrary JSON object)","examples":[{"project":"Q4 Analysis","tags":["financial"]}]}},"type":"object","required":["workspace_id"],"title":"WorkspaceManifest","description":"Complete workspace state including metadata and file list.\n\nThe manifest is persisted as `manifest.json` in the workspace directory\nand serves as the source of truth for workspace contents.\n\n## Metadata Field\n\nThe `metadata` field allows storing arbitrary user-defined data:\n\n```json\n{\n  \"metadata\": {\n    \"project\": \"Q4 Analysis\",\n    \"owner\": \"data-team\",\n    \"tags\": [\"financial\", \"quarterly\"]\n  }\n}\n```\n\n## Response Interpretation for LLMs\n\nWhen retrieving a workspace, the `files` array contains all uploaded\ndocuments. Use `index_processed` to identify which files have been\nindexed and are ready for retrieval operations.","examples":[{"created_at":"2025-01-22T10:00:00Z","files":[{"file_hash":"a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890","file_id":"a1b2c3d4e5f67890","file_type":"pdf","index_processed":true,"original_filename":"report.pdf","size_bytes":1048576,"stored_filename":"a1b2c3d4e5f67890_report.pdf","uploaded_at":"2025-01-22T10:30:00Z"}],"metadata":{"project":"Research"},"updated_at":"2025-01-22T14:30:00Z","workspace_id":"550e8400-e29b-41d4-a716-446655440000"}]},"cbinfra__ArtifactInfo":{"properties":{"filename":{"type":"string","title":"Filename"},"sha256":{"type":"string","title":"Sha256","description":"Hex SHA-256 of the file; verify before installing"},"size":{"type":"integer","title":"Size"},"url":{"type":"string","title":"Url","description":"Path under this API; prepend the host you dialled"}},"type":"object","required":["filename","sha256","size","url"],"title":"ArtifactInfo"},"cbinfra__BootstrapRequest":{"properties":{"org":{"type":"string","title":"Org","description":"GitHub organization name"},"project":{"type":"string","title":"Project","description":"Project/repository name"},"flavor":{"$ref":"#/components/schemas/cbinfra__src__bootstrap__models__ProjectFlavor","description":"Project type: python, node, both","default":"python"},"parent_dir":{"type":"string","title":"Parent Dir","description":"Local parent directory for the project"},"pyenv":{"type":"string","title":"Pyenv","description":"Pyenv environment name (for Python projects)","default":"nominates"},"node_version":{"type":"string","title":"Node Version","description":"Node.js version (for Node projects)","default":"24"},"private":{"type":"boolean","title":"Private","description":"Create as private repository","default":true},"description":{"type":"string","title":"Description","description":"Repository description for GitHub","default":""},"service_type":{"$ref":"#/components/schemas/cbinfra__src__bootstrap__models__ServiceType","description":"Service type: api_only or api_frontend","default":"api_only"},"subdomain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subdomain","description":"Subdomain for campaignbrain.dev (e.g., 'pulse' for pulse.campaignbrain.dev). Defaults to project name without 'cb' prefix."},"api_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Api Port","description":"API port (auto-allocated if not specified)"},"frontend_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Frontend Port","description":"Frontend port (auto-allocated if not specified, only for api_frontend)"},"setup_nginx":{"type":"boolean","title":"Setup Nginx","description":"Create and enable NGINX configuration","default":true},"setup_systemd":{"type":"boolean","title":"Setup Systemd","description":"Create and enable systemd service files","default":true},"setup_hosts":{"type":"boolean","title":"Setup Hosts","description":"Add subdomain to /etc/hosts","default":true},"start_services":{"type":"boolean","title":"Start Services","description":"Start services after creation","default":true}},"type":"object","required":["org","project","parent_dir"],"title":"BootstrapRequest","description":"Request model for bootstrapping a new project.","example":{"description":"Test project for bootstrap API","flavor":"python","org":"Nominate-AI","parent_dir":"/home/bisenbek/projects/nominate","private":true,"project":"cbtest","pyenv":"nominates","service_type":"api_only","setup_nginx":true,"setup_systemd":true,"subdomain":"test"}},"cbinfra__BootstrapResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Status: success, partial, failed"},"repo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Repo Url","description":"GitHub repository URL"},"local_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Path","description":"Local project path"},"repo_created":{"type":"boolean","title":"Repo Created","description":"Whether repo was newly created","default":false},"repo_existed":{"type":"boolean","title":"Repo Existed","description":"Whether repo already existed","default":false},"cloned":{"type":"boolean","title":"Cloned","description":"Whether repo was cloned","default":false},"files_created":{"items":{"type":"string"},"type":"array","title":"Files Created","description":"List of files created"},"env_setup":{"type":"boolean","title":"Env Setup","description":"Whether environment was set up","default":false},"messages":{"items":{"type":"string"},"type":"array","title":"Messages","description":"Status messages from each step"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if failed"},"subdomain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subdomain","description":"Subdomain configured"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Service URL (https://subdomain.campaignbrain.dev)"},"ports":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__PortAllocation"},{"type":"null"}],"description":"Allocated ports"},"nginx_config":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nginx Config","description":"NGINX config file path"},"systemd_services":{"items":{"type":"string"},"type":"array","title":"Systemd Services","description":"Systemd service file paths"},"services_started":{"type":"boolean","title":"Services Started","description":"Whether services were started","default":false}},"type":"object","required":["status"],"title":"BootstrapResponse","description":"Response model for bootstrap operation."},"cbinfra__CertInfo":{"properties":{"name":{"type":"string","title":"Name"},"domains":{"items":{"type":"string"},"type":"array","title":"Domains"},"expiry":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expiry"},"issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issuer"},"path":{"type":"string","title":"Path"}},"type":"object","required":["name","domains","path"],"title":"CertInfo","description":"SSL certificate information."},"cbinfra__ChannelInfo":{"properties":{"channel":{"type":"string","title":"Channel"},"version":{"type":"string","title":"Version"},"promoted_at":{"type":"string","title":"Promoted At"},"promoted_by":{"type":"string","title":"Promoted By"},"previous":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous","description":"The version this channel pointed at before"}},"type":"object","required":["channel","version","promoted_at","promoted_by"],"title":"ChannelInfo"},"cbinfra__ChannelRelease":{"properties":{"channel":{"type":"string","title":"Channel"},"version":{"type":"string","title":"Version"},"promoted_at":{"type":"string","title":"Promoted At"},"promoted_by":{"type":"string","title":"Promoted By"},"previous":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous","description":"The version this channel pointed at before"},"release":{"$ref":"#/components/schemas/cbinfra__ReleaseManifest"}},"type":"object","required":["channel","version","promoted_at","promoted_by","release"],"title":"ChannelRelease","description":"A channel resolved to the release it points at — the one document an\nupdater needs: what is current, and how to fetch and verify it."},"cbinfra__Comment":{"properties":{"id":{"type":"integer","title":"Id"},"body":{"type":"string","title":"Body"},"author":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Author"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","required":["id","body"],"title":"Comment","description":"GitHub issue comment."},"cbinfra__CommentCreate":{"properties":{"body":{"type":"string","title":"Body"}},"type":"object","required":["body"],"title":"CommentCreate","description":"Request model for adding a comment."},"cbinfra__CommentListResponse":{"properties":{"project":{"type":"string","title":"Project"},"issue_number":{"type":"integer","title":"Issue Number"},"total":{"type":"integer","title":"Total"},"comments":{"items":{"$ref":"#/components/schemas/cbinfra__Comment"},"type":"array","title":"Comments"}},"type":"object","required":["project","issue_number","total","comments"],"title":"CommentListResponse","description":"Response for listing comments."},"cbinfra__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbinfra__DNSRecord":{"properties":{"name":{"type":"string","title":"Name"},"record_type":{"type":"string","title":"Record Type","default":"A"},"value":{"type":"string","title":"Value"},"ttl":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttl"}},"type":"object","required":["name","value"],"title":"DNSRecord","description":"DNS record entry."},"cbinfra__DailyStat":{"properties":{"site":{"type":"string","title":"Site"},"day":{"type":"string","title":"Day"},"requests":{"type":"integer","title":"Requests"},"unique_ips":{"type":"integer","title":"Unique Ips"},"page_views":{"type":"integer","title":"Page Views"},"bot_requests":{"type":"integer","title":"Bot Requests"},"bytes_sent":{"type":"integer","title":"Bytes Sent"},"status_2xx":{"type":"integer","title":"Status 2Xx"},"status_3xx":{"type":"integer","title":"Status 3Xx"},"status_4xx":{"type":"integer","title":"Status 4Xx"},"status_5xx":{"type":"integer","title":"Status 5Xx"},"top_paths":{"items":{"$ref":"#/components/schemas/cbinfra__TopPath"},"type":"array","title":"Top Paths"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["site","day","requests","unique_ips","page_views","bot_requests","bytes_sent","status_2xx","status_3xx","status_4xx","status_5xx","top_paths"],"title":"DailyStat"},"cbinfra__DeployRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Service name (e.g., cbtest)"},"subdomain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subdomain","description":"Subdomain (defaults to name)"},"port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Port","description":"Port (auto-allocated if not set)"},"project_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Path","description":"Project path (defaults to /home/.../nominate/{name})"},"service_type":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__src__config__models__ServiceType"},{"type":"null"}],"description":"Service type (auto-detected if not set)"},"use_pin_gate":{"type":"boolean","title":"Use Pin Gate","description":"Enable PIN gate authentication","default":true},"ssl_mode":{"$ref":"#/components/schemas/cbinfra__SSLMode","description":"SSL certificate mode","default":"wildcard"},"description":{"type":"string","title":"Description","description":"Service description","default":""},"api_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Api Port","description":"API port (for dual-port configs)"},"workers":{"type":"integer","title":"Workers","description":"Number of uvicorn workers","default":1},"module":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Module","description":"Python module (defaults to src.main:app)"}},"type":"object","required":["name"],"title":"DeployRequest","description":"Request to deploy a new service."},"cbinfra__DeployResponse":{"properties":{"status":{"type":"string","title":"Status"},"name":{"type":"string","title":"Name"},"subdomain":{"type":"string","title":"Subdomain"},"url":{"type":"string","title":"Url"},"port":{"type":"integer","title":"Port"},"api_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Api Port"},"project_path":{"type":"string","title":"Project Path"},"service_type":{"$ref":"#/components/schemas/cbinfra__ServiceType-Output"},"systemd_unit":{"type":"string","title":"Systemd Unit"},"nginx_config":{"type":"string","title":"Nginx Config"},"messages":{"items":{"type":"string"},"type":"array","title":"Messages"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["status","name","subdomain","url","port","project_path","service_type","systemd_unit","nginx_config"],"title":"DeployResponse","description":"Response from deploy operation."},"cbinfra__DevTool":{"properties":{"name":{"type":"string","title":"Name"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path"}},"type":"object","required":["name"],"title":"DevTool","description":"Information about a development tool."},"cbinfra__DevToolsInfo":{"properties":{"git":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"docker":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"python":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"node":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"npm":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"yarn":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"pnpm":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"pip":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"poetry":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"cargo":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"go":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"ruff":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"pytest":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"playwright":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"gcc":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"gpp":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"make":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]},"cmake":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__DevTool"},{"type":"null"}]}},"type":"object","title":"DevToolsInfo","description":"Collection of detected development tools."},"cbinfra__EffectiveResponse":{"properties":{"project":{"type":"string","title":"Project"},"slug":{"type":"string","title":"Slug"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"files":{"items":{"$ref":"#/components/schemas/cbinfra__FileSummary"},"type":"array","title":"Files"}},"type":"object","required":["project","slug","scopes","files"],"title":"EffectiveResponse"},"cbinfra__Family":{"properties":{"label":{"type":"string","title":"Label"},"description":{"type":"string","title":"Description","default":""},"paths":{"items":{"type":"string"},"type":"array","title":"Paths","description":"Path globs (~ ok). A project is a member if its physical dir matches one."},"docs":{"items":{"type":"string"},"type":"array","title":"Docs"},"id":{"type":"string","title":"Id"},"files":{"type":"integer","title":"Files"},"members":{"items":{"type":"string"},"type":"array","title":"Members","description":"Claude Code project slugs, resolved from `paths` on disk now"},"member_paths":{"items":{"type":"string"},"type":"array","title":"Member Paths"}},"type":"object","required":["label","paths","id","files","members","member_paths"],"title":"Family"},"cbinfra__FamilyDef":{"properties":{"label":{"type":"string","title":"Label"},"description":{"type":"string","title":"Description","default":""},"paths":{"items":{"type":"string"},"type":"array","title":"Paths","description":"Path globs (~ ok). A project is a member if its physical dir matches one."},"docs":{"items":{"type":"string"},"type":"array","title":"Docs"}},"type":"object","required":["label","paths"],"title":"FamilyDef"},"cbinfra__FileCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Filename, lowercase + hyphens/underscores, .md suffix"},"content":{"type":"string","title":"Content"},"scope":{"type":"string","title":"Scope","description":"`fleet` (default) or an existing family id","default":"fleet"}},"type":"object","required":["name","content"],"title":"FileCreate"},"cbinfra__FileDeleteResponse":{"properties":{"service":{"type":"string","title":"Service"},"path":{"type":"string","title":"Path"},"deleted":{"type":"boolean","title":"Deleted"}},"type":"object","required":["service","path","deleted"],"title":"FileDeleteResponse","description":"Result of DELETE /api/files/{slug}/{path}."},"cbinfra__FileDetail":{"properties":{"name":{"type":"string","title":"Name"},"scope":{"type":"string","title":"Scope","description":"`fleet` or a family id","default":"fleet"},"size":{"type":"integer","title":"Size"},"mtime":{"type":"number","title":"Mtime"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"content":{"type":"string","title":"Content"},"frontmatter":{"additionalProperties":true,"type":"object","title":"Frontmatter"},"last_commit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Commit"}},"type":"object","required":["name","size","mtime","content","frontmatter"],"title":"FileDetail"},"cbinfra__FileEntry":{"properties":{"path":{"type":"string","title":"Path","description":"Relative file name (no slashes in v1)"},"content_type":{"type":"string","title":"Content Type","description":"MIME type recorded at upload time"},"size_bytes":{"type":"integer","title":"Size Bytes","description":"Plaintext size in bytes (pre-encryption)"},"sops_mode":{"type":"string","title":"Sops Mode","description":"'structured' or 'binary'"},"sha256":{"type":"string","title":"Sha256","description":"SHA-256 of the plaintext (integrity check)"},"updated_at":{"type":"string","title":"Updated At","description":"ISO 8601 UTC timestamp of last write"}},"type":"object","required":["path","content_type","size_bytes","sops_mode","sha256","updated_at"],"title":"FileEntry","description":"Metadata for one encrypted file in a service's `files/` dir.\n\nReturned by `GET /api/files/{slug}` (list) and as part of upload responses.\nNever carries plaintext content — that's the dedicated GET-by-path route."},"cbinfra__FileListResponse":{"properties":{"service":{"type":"string","title":"Service"},"files":{"items":{"$ref":"#/components/schemas/cbinfra__FileEntry"},"type":"array","title":"Files"}},"type":"object","required":["service","files"],"title":"FileListResponse","description":"List of encrypted files for one slug. Manifest-backed — no decryption."},"cbinfra__FileSummary":{"properties":{"name":{"type":"string","title":"Name"},"scope":{"type":"string","title":"Scope","description":"`fleet` or a family id","default":"fleet"},"size":{"type":"integer","title":"Size"},"mtime":{"type":"number","title":"Mtime"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},"type":"object","required":["name","size","mtime"],"title":"FileSummary"},"cbinfra__FileUpdate":{"properties":{"content":{"type":"string","title":"Content"},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"Move the file to this scope (fleet or a family id)"}},"type":"object","required":["content"],"title":"FileUpdate"},"cbinfra__FileUploadResponse":{"properties":{"service":{"type":"string","title":"Service"},"entry":{"$ref":"#/components/schemas/cbinfra__FileEntry"},"action":{"type":"string","title":"Action","description":"'created' for new files, 'updated' for overwrites"}},"type":"object","required":["service","entry","action"],"title":"FileUploadResponse","description":"Result of POST /api/files/{slug}/{path}."},"cbinfra__GenerateClaudeMdRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Project name"},"seed":{"type":"string","title":"Seed","description":"Natural language project description"},"flavor":{"$ref":"#/components/schemas/cbinfra__src__orchestrate__models__ProjectFlavor","description":"Project type","default":"python"},"existing_structure":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Existing Structure","description":"Directory tree if project exists"}},"type":"object","required":["name","seed"],"title":"GenerateClaudeMdRequest","description":"Request to generate CLAUDE.md."},"cbinfra__GenerateClaudeMdResponse":{"properties":{"content":{"type":"string","title":"Content","description":"Generated CLAUDE.md content"},"tokens_used":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tokens Used","description":"Tokens used for generation"}},"type":"object","required":["content"],"title":"GenerateClaudeMdResponse","description":"Response from CLAUDE.md generation."},"cbinfra__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbinfra__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbinfra__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbinfra__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbinfra__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbinfra__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbinfra__HostRequest":{"properties":{"domain":{"type":"string","title":"Domain","description":"Base domain (e.g. 'campaignbrain.dev', 'meatball.ai')"},"subdomain":{"type":"string","title":"Subdomain","description":"Subdomain to provision (e.g. 'analytics')"},"service_type":{"$ref":"#/components/schemas/cbinfra__HostServiceType","description":"Service type: api, web, api_web, proxy","default":"api"},"port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Port","description":"API/service port (auto-allocated if None)"},"frontend_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Frontend Port","description":"Frontend port for api_web type (auto-allocated if None)"},"target":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target","description":"Remote target for proxy type (e.g. '192.168.1.182:8080')"},"description":{"type":"string","title":"Description","description":"Service description for NGINX config comment","default":""},"pin_gate":{"type":"boolean","title":"Pin Gate","description":"Enable PIN gate authentication","default":true},"upstream_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upstream Name","description":"NGINX upstream name (auto-derived from subdomain if None)"}},"type":"object","required":["domain","subdomain"],"title":"HostRequest","description":"Request model for provisioning a new subdomain on any managed TLD.","example":{"description":"Analytics dashboard API","domain":"campaignbrain.dev","pin_gate":true,"service_type":"api","subdomain":"analytics"}},"cbinfra__HostResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Status: success or failed"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Service URL (e.g. https://analytics.campaignbrain.dev)"},"port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Port","description":"Allocated API/service port"},"frontend_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Frontend Port","description":"Allocated frontend port (api_web only)"},"config_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Config Path","description":"Path to generated NGINX config"},"nginx_config":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nginx Config","description":"Generated NGINX config content"},"messages":{"items":{"type":"string"},"type":"array","title":"Messages","description":"Status messages"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if failed"}},"type":"object","required":["status"],"title":"HostResponse","description":"Response model for host provisioning."},"cbinfra__HostServiceType":{"type":"string","enum":["api","web","api_web","proxy"],"title":"HostServiceType","description":"Host provisioning service type."},"cbinfra__InventoryResponse":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"system":{"$ref":"#/components/schemas/cbinfra__SystemInfo"},"devtools":{"$ref":"#/components/schemas/cbinfra__DevToolsInfo"},"projects":{"items":{"$ref":"#/components/schemas/cbinfra__ProjectInventory"},"type":"array","title":"Projects","default":[]}},"type":"object","required":["timestamp","system","devtools"],"title":"InventoryResponse","description":"Full inventory response."},"cbinfra__Issue":{"properties":{"number":{"type":"integer","title":"Number"},"title":{"type":"string","title":"Title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"state":{"type":"string","title":"State"},"stateReason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Statereason"},"author":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Author"},"assignees":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Assignees","default":[]},"labels":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Labels","default":[]},"milestone":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Milestone"},"isPinned":{"type":"boolean","title":"Ispinned","default":false},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat"},"closedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Closedat"},"closed":{"type":"boolean","title":"Closed","default":false},"comments":{"type":"integer","title":"Comments","default":0},"url":{"type":"string","title":"Url"},"project":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project"}},"type":"object","required":["number","title","state","url"],"title":"Issue","description":"GitHub issue response model."},"cbinfra__IssueClose":{"properties":{"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","title":"IssueClose","description":"Request model for closing an issue."},"cbinfra__IssueCreate":{"properties":{"title":{"type":"string","title":"Title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"labels":{"items":{"type":"string"},"type":"array","title":"Labels","default":[]},"assignees":{"items":{"type":"string"},"type":"array","title":"Assignees","default":[]},"milestone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Milestone"}},"type":"object","required":["title"],"title":"IssueCreate","description":"Request model for creating a new issue."},"cbinfra__IssueListResponse":{"properties":{"project":{"type":"string","title":"Project"},"total":{"type":"integer","title":"Total"},"issues":{"items":{"$ref":"#/components/schemas/cbinfra__Issue"},"type":"array","title":"Issues"}},"type":"object","required":["project","total","issues"],"title":"IssueListResponse","description":"Response for listing issues."},"cbinfra__IssueReopen":{"properties":{"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment"}},"type":"object","title":"IssueReopen","description":"Request model for reopening an issue."},"cbinfra__IssueUpdate":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"add_labels":{"items":{"type":"string"},"type":"array","title":"Add Labels","default":[]},"remove_labels":{"items":{"type":"string"},"type":"array","title":"Remove Labels","default":[]},"add_assignees":{"items":{"type":"string"},"type":"array","title":"Add Assignees","default":[]},"remove_assignees":{"items":{"type":"string"},"type":"array","title":"Remove Assignees","default":[]},"milestone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Milestone"},"remove_milestone":{"type":"boolean","title":"Remove Milestone","default":false}},"type":"object","title":"IssueUpdate","description":"Request model for updating an issue."},"cbinfra__LogoutResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"masquerade_user":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Masquerade User"},"existed":{"type":"boolean","title":"Existed"}},"type":"object","required":["slug","existed"],"title":"LogoutResponse"},"cbinfra__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbinfra__NginxConfig":{"properties":{"name":{"type":"string","title":"Name"},"filename":{"type":"string","title":"Filename"},"subdomain":{"type":"string","title":"Subdomain"},"port":{"type":"integer","title":"Port"},"api_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Api Port"},"use_pin_gate":{"type":"boolean","title":"Use Pin Gate","default":true},"ssl_mode":{"$ref":"#/components/schemas/cbinfra__SSLMode","default":"wildcard"},"enabled":{"type":"boolean","title":"Enabled","default":true},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"}},"type":"object","required":["name","filename","subdomain","port"],"title":"NginxConfig","description":"NGINX configuration details."},"cbinfra__NodeEnv":{"properties":{"node_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Node Version"},"package_manager":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Package Manager"},"package_json":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Package Json"},"has_node_modules":{"type":"boolean","title":"Has Node Modules","default":false},"has_lockfile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Has Lockfile"}},"type":"object","title":"NodeEnv","description":"Node.js environment information for a project."},"cbinfra__OrchestrateRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Project name (e.g., cbcc)"},"seed":{"type":"string","title":"Seed","description":"Natural language project description"},"flavor":{"$ref":"#/components/schemas/cbinfra__src__orchestrate__models__ProjectFlavor","description":"Project type","default":"python"},"parent_dir":{"type":"string","title":"Parent Dir","description":"Parent directory for project","default":"/home/bisenbek/projects/nominate"},"auto_mode":{"type":"boolean","title":"Auto Mode","description":"No stoplights - approve all","default":false},"skip_bootstrap":{"type":"boolean","title":"Skip Bootstrap","description":"Project already exists","default":false},"plan_only":{"type":"boolean","title":"Plan Only","description":"Generate CLAUDE.md only, don't build","default":false},"deploy":{"type":"boolean","title":"Deploy","description":"Deploy after build","default":false},"pyenv":{"type":"string","title":"Pyenv","description":"pyenv virtualenv name","default":"nominates"},"org":{"type":"string","title":"Org","description":"GitHub organization","default":"Nominate-AI"},"private":{"type":"boolean","title":"Private","description":"Create private repo","default":true},"description":{"type":"string","title":"Description","description":"Project description for GitHub","default":""}},"type":"object","required":["name","seed"],"title":"OrchestrateRequest","description":"Request to orchestrate project creation."},"cbinfra__OrchestrateResponse":{"properties":{"status":{"type":"string","title":"Status","description":"pending, running, complete, failed"},"session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id","description":"cbos session ID"},"project_path":{"type":"string","title":"Project Path","description":"Local project path"},"claude_md":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claude Md","description":"Generated CLAUDE.md content"},"cbos_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbos Url","description":"WebSocket URL for monitoring"},"messages":{"items":{"type":"string"},"type":"array","title":"Messages","description":"Status messages"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if failed"}},"type":"object","required":["status","project_path"],"title":"OrchestrateResponse","description":"Response from orchestration."},"cbinfra__PortAllocation":{"properties":{"api_port":{"type":"integer","title":"Api Port"},"frontend_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Frontend Port"}},"type":"object","required":["api_port"],"title":"PortAllocation","description":"Port allocation result."},"cbinfra__PortInfo":{"properties":{"port":{"type":"integer","title":"Port"},"service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service"},"in_use":{"type":"boolean","title":"In Use","default":false},"available":{"type":"boolean","title":"Available","default":true}},"type":"object","required":["port"],"title":"PortInfo","description":"Information about a port allocation."},"cbinfra__PortRange":{"properties":{"start":{"type":"integer","title":"Start"},"end":{"type":"integer","title":"End"},"purpose":{"type":"string","title":"Purpose"},"allocated":{"items":{"$ref":"#/components/schemas/cbinfra__PortInfo"},"type":"array","title":"Allocated"},"next_available":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Available"}},"type":"object","required":["start","end","purpose"],"title":"PortRange","description":"Information about a port range."},"cbinfra__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbinfra__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbinfra__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbinfra__ProductSummary":{"properties":{"product":{"type":"string","title":"Product"},"channels":{"additionalProperties":{"$ref":"#/components/schemas/cbinfra__ChannelInfo"},"type":"object","title":"Channels"},"versions":{"items":{"$ref":"#/components/schemas/cbinfra__ReleaseManifest"},"type":"array","title":"Versions"}},"type":"object","required":["product","channels","versions"],"title":"ProductSummary"},"cbinfra__Project":{"properties":{"slug":{"type":"string","title":"Slug"},"repo":{"type":"string","title":"Repo"},"description":{"type":"string","title":"Description"}},"type":"object","required":["slug","repo","description"],"title":"Project","description":"CB project metadata."},"cbinfra__ProjectInventory":{"properties":{"path":{"type":"string","title":"Path"},"name":{"type":"string","title":"Name"},"is_git_repo":{"type":"boolean","title":"Is Git Repo","default":false},"git_branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Git Branch"},"python_env":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__PythonEnv"},{"type":"null"}]},"node_env":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__NodeEnv"},{"type":"null"}]}},"type":"object","required":["path","name"],"title":"ProjectInventory","description":"Inventory of a single project directory."},"cbinfra__ProjectListResponse":{"properties":{"total":{"type":"integer","title":"Total"},"projects":{"items":{"$ref":"#/components/schemas/cbinfra__Project"},"type":"array","title":"Projects"}},"type":"object","required":["total","projects"],"title":"ProjectListResponse","description":"Response for listing projects."},"cbinfra__PromoteRequest":{"properties":{"channel":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,15}$","title":"Channel","examples":["stable"]},"version":{"type":"string","pattern":"^\\d+\\.\\d+\\.\\d+$","title":"Version","examples":["0.23.1"]}},"type":"object","required":["channel","version"],"title":"PromoteRequest"},"cbinfra__PythonEnv":{"properties":{"python_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Python Version"},"virtualenv":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Virtualenv"},"pyenv_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pyenv Version"},"pyproject_toml":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Pyproject Toml"},"requirements":{"items":{"type":"string"},"type":"array","title":"Requirements","default":[]}},"type":"object","title":"PythonEnv","description":"Python environment information for a project."},"cbinfra__ReleaseManifest":{"properties":{"product":{"type":"string","title":"Product"},"version":{"type":"string","title":"Version"},"tag":{"type":"string","title":"Tag"},"git_sha":{"type":"string","title":"Git Sha"},"git_sha_short":{"type":"string","title":"Git Sha Short","description":"What `<product> --version` reports as `rev`; compare THIS, not version"},"built_at":{"type":"string","title":"Built At"},"wheel":{"$ref":"#/components/schemas/cbinfra__ArtifactInfo"},"boot_script":{"anyOf":[{"$ref":"#/components/schemas/cbinfra__ArtifactInfo"},{"type":"null"}],"description":"The updater shipped with this release, if the tag carried one"}},"type":"object","required":["product","version","tag","git_sha","git_sha_short","built_at","wheel"],"title":"ReleaseManifest"},"cbinfra__RollupResponse":{"properties":{"recorded":{"type":"boolean","title":"Recorded"},"stat":{"$ref":"#/components/schemas/cbinfra__DailyStat"}},"type":"object","required":["recorded","stat"],"title":"RollupResponse"},"cbinfra__SSLMode":{"type":"string","enum":["wildcard","individual","none"],"title":"SSLMode","description":"SSL certificate mode."},"cbinfra__ScopeTree":{"properties":{"fleet":{"type":"integer","title":"Fleet","description":"fleet canonicals"},"families":{"items":{"$ref":"#/components/schemas/cbinfra__Family"},"type":"array","title":"Families"}},"type":"object","required":["fleet","families"],"title":"ScopeTree"},"cbinfra__SecretAddRequest":{"properties":{"key":{"type":"string","maxLength":255,"minLength":1,"pattern":"^[A-Z][A-Z0-9_]*$","title":"Key","description":"Environment variable name (e.g. CBFOO_API_KEY)"},"value":{"type":"string","minLength":1,"title":"Value","description":"Secret value to encrypt and store"}},"type":"object","required":["key","value"],"title":"SecretAddRequest","description":"Request to add or update a secret."},"cbinfra__SecretAddResponse":{"properties":{"service":{"type":"string","title":"Service"},"key":{"type":"string","title":"Key"},"action":{"type":"string","title":"Action","description":"'created' if new file, 'updated' if appended"}},"type":"object","required":["service","key","action"],"title":"SecretAddResponse","description":"Result of adding or updating a secret."},"cbinfra__SecretFile":{"properties":{"path":{"type":"string","title":"Path","description":"Relative path within /opt/cb/secrets/"},"service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service","description":"Service name (None for shared.enc.env)"},"size_bytes":{"type":"integer","title":"Size Bytes","description":"File size in bytes"}},"type":"object","required":["path","size_bytes"],"title":"SecretFile","description":"A SOPS-encrypted secret file."},"cbinfra__SecretKeys":{"properties":{"service":{"type":"string","title":"Service"},"path":{"type":"string","title":"Path"},"keys":{"items":{"type":"string"},"type":"array","title":"Keys","description":"Environment variable names in the file"},"key_count":{"type":"integer","title":"Key Count"}},"type":"object","required":["service","path","keys","key_count"],"title":"SecretKeys","description":"Key names (not values) in a decrypted secret file."},"cbinfra__SecretRemoveResponse":{"properties":{"service":{"type":"string","title":"Service"},"key":{"type":"string","title":"Key"},"action":{"type":"string","title":"Action","description":"Always 'removed'"}},"type":"object","required":["service","key","action"],"title":"SecretRemoveResponse","description":"Result of removing a secret key."},"cbinfra__SecretValues":{"properties":{"service":{"type":"string","title":"Service"},"path":{"type":"string","title":"Path"},"entries":{"additionalProperties":{"type":"string"},"type":"object","title":"Entries","description":"Decrypted KEY=VALUE pairs"},"key_count":{"type":"integer","title":"Key Count"}},"type":"object","required":["service","path","entries","key_count"],"title":"SecretValues","description":"Decrypted key-value pairs. Returned only by the explicit decrypt endpoint."},"cbinfra__SecretVerifyResponse":{"properties":{"total":{"type":"integer","title":"Total"},"passed":{"type":"integer","title":"Passed"},"failed":{"type":"integer","title":"Failed"},"results":{"items":{"$ref":"#/components/schemas/cbinfra__SecretVerifyResult"},"type":"array","title":"Results"}},"type":"object","required":["total","passed","failed","results"],"title":"SecretVerifyResponse","description":"Aggregate verification results."},"cbinfra__SecretVerifyResult":{"properties":{"path":{"type":"string","title":"Path"},"ok":{"type":"boolean","title":"Ok"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["path","ok"],"title":"SecretVerifyResult","description":"Verification result for a single secret file."},"cbinfra__ServiceStatus":{"properties":{"name":{"type":"string","title":"Name"},"subdomain":{"type":"string","title":"Subdomain"},"url":{"type":"string","title":"Url"},"port":{"type":"integer","title":"Port"},"systemd_status":{"type":"string","title":"Systemd Status"},"active_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Active State"},"sub_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sub State"},"pid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pid"},"memory":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Memory"},"uptime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uptime"}},"type":"object","required":["name","subdomain","url","port","systemd_status"],"title":"ServiceStatus","description":"Status of a deployed service."},"cbinfra__ServiceType-Output":{"type":"string","enum":["fastapi","fasthtml","node","static"],"title":"ServiceType","description":"Type of service being deployed."},"cbinfra__Session":{"properties":{"slug":{"type":"string","title":"Slug"},"masquerade_user":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Masquerade User"},"cookies":{"additionalProperties":{"type":"string"},"type":"object","title":"Cookies"},"headers":{"additionalProperties":{"type":"string"},"type":"object","title":"Headers"},"minted_at":{"type":"string","format":"date-time","title":"Minted At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"actor":{"type":"string","title":"Actor","description":"Who minted the session (audit trail)."}},"type":"object","required":["slug","minted_at","expires_at","actor"],"title":"Session","description":"A minted session handed back to the caller. Carries the login *result*\nand provenance — never the raw credential."},"cbinfra__SessionStatus":{"properties":{"session_id":{"type":"string","title":"Session Id"},"status":{"type":"string","title":"Status"},"project_path":{"type":"string","title":"Project Path"},"last_output":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Output"},"pending_question":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pending Question"}},"type":"object","required":["session_id","status","project_path"],"title":"SessionStatus","description":"Status of a cbos session."},"cbinfra__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbinfra__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbinfra__SummaryResponse":{"properties":{"site":{"type":"string","title":"Site"},"days":{"type":"integer","title":"Days"},"totals":{"additionalProperties":{"type":"integer"},"type":"object","title":"Totals"},"daily":{"items":{"$ref":"#/components/schemas/cbinfra__DailyStat"},"type":"array","title":"Daily"}},"type":"object","required":["site","days","totals","daily"],"title":"SummaryResponse"},"cbinfra__SyncResponse":{"properties":{"output":{"type":"string","title":"Output"},"returncode":{"type":"integer","title":"Returncode"}},"type":"object","required":["output","returncode"],"title":"SyncResponse"},"cbinfra__SystemInfo":{"properties":{"hostname":{"type":"string","title":"Hostname"},"os_name":{"type":"string","title":"Os Name"},"os_version":{"type":"string","title":"Os Version"},"kernel":{"type":"string","title":"Kernel"},"architecture":{"type":"string","title":"Architecture"}},"type":"object","required":["hostname","os_name","os_version","kernel","architecture"],"title":"SystemInfo","description":"System and OS information."},"cbinfra__SystemdUnit":{"properties":{"name":{"type":"string","title":"Name"},"filename":{"type":"string","title":"Filename"},"service_type":{"$ref":"#/components/schemas/cbinfra__ServiceType-Output"},"port":{"type":"integer","title":"Port"},"project_path":{"type":"string","title":"Project Path"},"module":{"type":"string","title":"Module"},"workers":{"type":"integer","title":"Workers","default":1},"active_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Active State"},"sub_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sub State"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"}},"type":"object","required":["name","filename","service_type","port","project_path","module"],"title":"SystemdUnit","description":"Systemd unit file details."},"cbinfra__TopPath":{"properties":{"path":{"type":"string","title":"Path"},"hits":{"type":"integer","title":"Hits"}},"type":"object","required":["path","hits"],"title":"TopPath"},"cbinfra__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbinfra__WriteResponse":{"properties":{"name":{"type":"string","title":"Name"},"scope":{"type":"string","title":"Scope"},"bytes_written":{"type":"integer","title":"Bytes Written"},"commit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Commit"},"synced":{"type":"boolean","title":"Synced"}},"type":"object","required":["name","scope","bytes_written","commit","synced"],"title":"WriteResponse"},"cbinfra__src__bootstrap__models__ProjectFlavor":{"type":"string","enum":["python","node","both"],"title":"ProjectFlavor","description":"Project type/flavor."},"cbinfra__src__bootstrap__models__ServiceType":{"type":"string","enum":["api_only","api_frontend"],"title":"ServiceType","description":"Service deployment type."},"cbinfra__src__config__models__ServiceType":{"type":"string","enum":["fastapi","fasthtml","node","static"],"title":"ServiceType","description":"Type of service being deployed."},"cbinfra__src__orchestrate__models__ProjectFlavor":{"type":"string","enum":["python","node","both"],"title":"ProjectFlavor","description":"Type of project to create."},"cbingest__BatchDocument":{"properties":{"id":{"type":"string","title":"Id","description":"Caller-provided document ID. Echoed back in the result so the caller can correlate inputs with outputs.","examples":["invoice-001","doc-2026-06-21-a"]},"content_base64":{"type":"string","title":"Content Base64","description":"Base64-encoded document content.","examples":["JVBERi0xLjQKJ..."]},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type","description":"MIME type of the document. Used for provider routing.","examples":["application/pdf","image/png"]},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename","description":"Original filename with extension. Fallback for type detection.","examples":["invoice.pdf","receipt.png"]}},"type":"object","required":["id","content_base64"],"title":"BatchDocument","description":"A single document in a batch extraction request.\n\nEach document carries its own bytes (base64) and an identifier the caller\nuses to correlate the document with its entry in the batch response. Only\ninline base64 content is supported in batch mode (no ``content_url``)."},"cbingest__BatchRequest":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/cbingest__BatchDocument"},"type":"array","maxItems":20,"title":"Documents","description":"Documents to process (max 20).","examples":[[{"content_base64":"JVBERi0xLjQK...","content_type":"application/pdf","filename":"invoice-001.pdf","id":"invoice-001"}]]},"extraction_schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Extraction Schema","description":"Shared extraction schema applied to every document.","examples":[{"amount":{"description":"Total amount","type":"number"},"vendor_name":{"description":"Vendor name","type":"string"}}]},"entity_keys":{"anyOf":[{"additionalProperties":{"additionalProperties":{"type":"string"},"type":"object"},"type":"object"},{"type":"null"}],"title":"Entity Keys","description":"Shared entity resolution mappings applied to every document.","examples":[{"vendor_name":{"MERCHANTS MRKT":"Merchants Market (Sysco)"}}]},"context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Context","description":"Shared context to improve extraction accuracy for all documents.","examples":["USVI hospitality vendor invoices"]},"tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id","description":"Tenant the batch is for — propagated to cbai so the AI spend bills to this tenant (else 'cbingest-internal')."}},"type":"object","required":["documents"],"title":"BatchRequest","description":"Request to extract multiple documents concurrently.\n\nUp to 20 documents are processed in parallel (max 5 at a time). The\n``extraction_schema``, ``entity_keys``, and ``context`` are shared across\nevery document in the batch — each document is otherwise independent and\na per-document failure does not abort the rest."},"cbingest__ClassifyRequest":{"properties":{"text":{"type":"string","minLength":10,"title":"Text","description":"Raw OCR text from the document (or first ~2000 chars)."},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Override the classification model. Defaults to Opus."},"tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id","description":"Tenant the classification is for — propagated to cbai for billing attribution (else 'cbingest-internal')."}},"type":"object","required":["text"],"title":"ClassifyRequest","description":"Request to classify a document and generate a custom extraction prompt."},"cbingest__ClassifyResponse":{"properties":{"doc_type":{"type":"string","title":"Doc Type","description":"Document type: invoice, statement, receipt, order, quote, letter, unknown"},"vendor":{"type":"string","title":"Vendor","description":"Detected vendor or supplier name."},"format_description":{"type":"string","title":"Format Description","description":"Human-readable description of the document layout."},"extraction_prompt":{"type":"string","title":"Extraction Prompt","description":"Custom extraction instructions for this specific document format."},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence","description":"Classification confidence (0.0-1.0)."},"complexity":{"type":"string","title":"Complexity","description":"Document complexity: low, medium, high"},"model":{"type":"string","title":"Model","description":"Model used for classification."},"processing_ms":{"type":"integer","minimum":0.0,"title":"Processing Ms","description":"Processing time in milliseconds."}},"type":"object","required":["doc_type","vendor","format_description","extraction_prompt","confidence","complexity","model","processing_ms"],"title":"ClassifyResponse","description":"Classification result with custom extraction prompt."},"cbingest__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbingest__ExtractClassification":{"properties":{"doc_type":{"type":"string","title":"Doc Type","description":"Document type: invoice, statement, receipt, order, quote, letter, unknown"},"vendor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor","description":"Detected vendor or supplier name."},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence","description":"Classification confidence (0.0-1.0)."},"complexity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Complexity","description":"Document complexity: low, medium, high"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Model used for classification."}},"type":"object","required":["doc_type","confidence"],"title":"ExtractClassification","description":"Classification result included in ExtractResponse when classify_first=True.\n\nLets callers route documents based on doc_type without having to call\n/api/v1/classify separately. The classifier is invoked internally during\nthe extract pipeline; this field surfaces its output to the caller."},"cbingest__ExtractRequest":{"properties":{"tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id","description":"Tenant the ingest is for — propagated to cbai so the AI spend bills to this tenant. Omit and it books to 'cbingest-internal'."},"content_base64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Base64","description":"Base64-encoded document content. Provide this or content_url."},"content_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Url","description":"Public URL to the document. Provide this or content_base64.","examples":["https://example.com/invoice.pdf"]},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type","description":"MIME type of the document. Used for provider routing.","examples":["application/pdf","image/png","text/plain"]},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename","description":"Original filename with extension. Used as fallback for type detection.","examples":["invoice.pdf","receipt.png"]},"extraction_schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Extraction Schema","description":"Caller-defined fields to extract. Keys are field names, values define type and description.","examples":[{"amount":{"description":"Total amount","type":"number"},"vendor_name":{"description":"Vendor name","type":"string"}}]},"entity_keys":{"anyOf":[{"additionalProperties":{"additionalProperties":{"type":"string"},"type":"object"},"type":"object"},{"type":"null"}],"title":"Entity Keys","description":"Entity resolution mappings. Keys are field names, values map fuzzy names to canonical names.","examples":[{"vendor_name":{"MERCHANTS MRKT":"Merchants Market (Sysco)"}}]},"context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Context","description":"Additional context to improve extraction accuracy.","examples":["USVI hospitality vendor invoice"]},"max_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Tokens","description":"Override max tokens for extraction (default: config value). Use higher values for complex documents.","examples":[4096,16384]},"fetch_headers":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Fetch Headers","description":"Optional HTTP headers for fetching content_url (e.g. Authorization).","examples":[{"Authorization":"Bearer token123"}]},"classify_first":{"type":"boolean","title":"Classify First","description":"Run document classification (Opus) before extraction. Generates a custom extraction prompt for this specific document format.","default":false},"return_images":{"type":"boolean","title":"Return Images","description":"If true, include base64-encoded page images from the OCR provider in the response (currently supported by cbai-ocr and cbai-unstructured). Leaves this service stateless — caller keeps the bytes.","default":false}},"type":"object","title":"ExtractRequest","description":"Request to extract text and/or structured data from a document.\n\nProvide document content as base64 or URL. Optionally include an\nextraction schema for AI-powered structured data extraction and\nentity keys for fuzzy name normalization."},"cbingest__ExtractResponse":{"properties":{"text":{"type":"string","title":"Text","description":"Raw extracted text from the document."},"structured":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Structured","description":"Structured data extracted per the caller's schema. Null if no schema was provided."},"confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence","description":"AI confidence score (0.0-1.0) for the extraction. Null if no schema was provided."},"classification":{"anyOf":[{"$ref":"#/components/schemas/cbingest__ExtractClassification"},{"type":"null"}],"description":"Classifier output when classify_first=True was set on the request. Null if classify_first was not requested or if classification failed."},"provider":{"type":"string","title":"Provider","description":"Which OCR provider processed the document.","examples":["cbai-ocr","cbai-unstructured","office-direct","direct"]},"degraded":{"type":"boolean","title":"Degraded","description":"True when the OCR text came from a DEGRADED path — cbai fell back to local vision models because the paid provider was definitively unavailable (a 402). The text is real but transcription quality is materially lower than the vendor's, and a wrong figure looks exactly like a right one. Callers that PERSIST extracted values (rate cards, prices, dates) should carry this through and surface it for review rather than trusting the result silently. It is a flag, not a block: the pipeline keeps flowing.","default":false},"ocr_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ocr Model","description":"The specific model that produced the text, when the provider reports one (e.g. 'gemma3:4b' on the degraded local path). Null when the provider does not name a model."},"processing_ms":{"type":"integer","minimum":0.0,"title":"Processing Ms","description":"Total processing time in milliseconds."},"images":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Images","description":"Base64-encoded page images from the OCR provider — populated only when return_images=true was set on the request and the provider supports it."},"content_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Hash","description":"SHA-256 hex digest of the raw document bytes the service processed (post-URL-fetch if content_url was used). Lets dedup callers reuse this canonical hash instead of re-hashing the same bytes."}},"type":"object","required":["text","provider","processing_ms"],"title":"ExtractResponse","description":"Response from document extraction.\n\nAlways includes raw OCR text and provider info. When an extraction_schema\nwas provided, also includes structured data and confidence score. When\nclassify_first=True was set, also includes the classification result."},"cbingest__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbingest__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbingest__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbingest__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbingest__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbingest__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbingest__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbingest__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbingest__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbingest__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbingest__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbingest__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbingest__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbintel__AddArtifactsRequest":{"properties":{"artifacts":{"items":{"$ref":"#/components/schemas/cbintel__ArtifactRow"},"type":"array","title":"Artifacts"}},"type":"object","required":["artifacts"],"title":"AddArtifactsRequest"},"cbintel__AddJobRequest":{"properties":{"job_id":{"type":"string","title":"Job Id"}},"type":"object","required":["job_id"],"title":"AddJobRequest","description":"Request to add a job to a workspace."},"cbintel__Airing":{"properties":{"callsign":{"type":"string","title":"Callsign"},"ts":{"type":"string","title":"Ts"},"score":{"type":"number","title":"Score"},"egress":{"type":"string","title":"Egress"},"snippet":{"type":"string","title":"Snippet"}},"type":"object","required":["callsign","ts","score","egress","snippet"],"title":"Airing"},"cbintel__AlertInfo":{"properties":{"reach":{"type":"integer","title":"Reach"},"baseline":{"type":"number","title":"Baseline"},"ratio":{"type":"number","title":"Ratio"},"t":{"type":"string","title":"T"}},"type":"object","required":["reach","baseline","ratio","t"],"title":"AlertInfo","description":"A reach-jump: the latest hour spiked above the trailing baseline."},"cbintel__AnalyzeRequest":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"URL to fetch document from"},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path","description":"Local file path to process"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Pre-extracted text to analyze (skips processing)"},"extract_entities":{"type":"boolean","title":"Extract Entities","default":true},"extract_summary":{"type":"boolean","title":"Extract Summary","default":true},"extract_topics":{"type":"boolean","title":"Extract Topics","default":true},"entity_types":{"items":{"type":"string"},"type":"array","title":"Entity Types","default":["person","organization","location","event","topic"]},"max_summary_length":{"type":"integer","maximum":500.0,"minimum":50.0,"title":"Max Summary Length","default":200},"max_topics":{"type":"integer","maximum":20.0,"minimum":3.0,"title":"Max Topics","default":10}},"type":"object","title":"AnalyzeRequest","description":"Request for document analysis.\n\nCan analyze from URL, file path, or pre-extracted text."},"cbintel__AnalyzeResponse":{"properties":{"title":{"type":"string","title":"Title"},"summary":{"type":"string","title":"Summary"},"entities":{"items":{"$ref":"#/components/schemas/cbintel__Entity"},"type":"array","title":"Entities"},"topics":{"items":{"type":"string"},"type":"array","title":"Topics"},"document_type":{"type":"string","title":"Document Type"},"key_sections":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Key Sections"},"file_path":{"type":"string","title":"File Path"},"word_count":{"type":"integer","title":"Word Count"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["title","summary","document_type","file_path","word_count"],"title":"AnalyzeResponse","description":"Response from document analysis."},"cbintel__ArtifactListResponse":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id","description":"Workspace these artifacts belong to"},"artifacts":{"items":{"$ref":"#/components/schemas/cbintel__ArtifactResponse"},"type":"array","title":"Artifacts","description":"List of artifact details"},"total":{"type":"integer","title":"Total","description":"Total number of artifacts returned"}},"type":"object","required":["workspace_id","artifacts","total"],"title":"ArtifactListResponse","description":"List of workspace artifacts."},"cbintel__ArtifactResponse":{"properties":{"artifact_id":{"type":"string","title":"Artifact Id","description":"Unique artifact identifier"},"artifact_type":{"type":"string","title":"Artifact Type","description":"Artifact type: json, screenshot, pdf, html, markdown"},"filename":{"type":"string","title":"Filename","description":"Original filename of the artifact"},"title":{"type":"string","title":"Title","description":"Human-readable title","default":""},"size_bytes":{"type":"integer","title":"Size Bytes","description":"File size in bytes","default":0},"created_at":{"type":"string","title":"Created At","description":"ISO 8601 creation timestamp","default":""},"run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Id","description":"Job/run ID that produced this artifact"},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage","description":"Pipeline stage that produced this artifact"},"content_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Url","description":"Stable public URL to download the artifact"},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preview Url","description":"URL for a preview/thumbnail version"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Additional artifact metadata"}},"type":"object","required":["artifact_id","artifact_type","filename"],"title":"ArtifactResponse","description":"Single artifact detail."},"cbintel__ArtifactRow":{"properties":{"kind":{"type":"string","pattern":"^(code|catalog|dataset|figure|markdown|pdf|html|link|memo)$","title":"Kind"},"ref":{"type":"string","minLength":1,"title":"Ref"},"sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha256"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"run":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Run"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","required":["kind","ref"],"title":"ArtifactRow"},"cbintel__BindAssetRequest":{"properties":{"slug":{"type":"string","minLength":1,"title":"Slug"},"kind":{"anyOf":[{"type":"string","pattern":"^(scan|deck|notes|seed|external|dataset)$"},{"type":"null"}],"title":"Kind"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","required":["slug"],"title":"BindAssetRequest"},"cbintel__BrowserJobRequest":{"properties":{"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID to associate this job with. Artifacts are saved under workspace path.","examples":["ws_abc123"]},"script":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Script","description":"Raw JSON AST script definition. See ferret action documentation for schema.","examples":[{"actions":[{"type":"navigate","url":"https://example.com"},{"full_page":true,"type":"screenshot"}],"type":"sequence"}]},"natural_language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Natural Language","description":"Natural language instruction to compile into script. Examples: 'Search for python on google.com', 'Login to example.com with user test'","examples":["Search for 'AI regulation' on google.com","Extract all links from https://example.com"]},"template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template","description":"Name of built-in browser template. Available: fec_filing_check, social_profile_archive.","examples":["fec_filing_check","social_profile_archive"]},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Parameters for template substitution. Keys like {{param}} are replaced.","examples":[{"committee_id":"C00401224"},{"bio_selector":".bio","profile_url":"https://twitter.com/example"}]},"proxy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proxy","description":"Direct proxy URL override, taking precedence over 'geo'. Use 'geo' for ordinary VPN routing; use this when a SPECIFIC egress is required and a region is not specific enough — notably a residential exit, which 'geo' cannot select because it resolves to the datacenter VPN bank.","examples":["http://10.8.0.33:3129"]},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic filter for VPN routing. Routes browser traffic through specified region.","examples":["us","us:ca","de","gb"]},"timeout":{"type":"integer","maximum":300.0,"minimum":10.0,"title":"Timeout","description":"Script execution timeout in seconds.","default":120,"examples":[60,120,180]},"capture_screenshots":{"type":"boolean","title":"Capture Screenshots","description":"Capture screenshots at key points during execution.","default":true},"capture_network":{"type":"boolean","title":"Capture Network","description":"Capture network requests/responses during execution.","default":false},"viewport_width":{"type":"integer","maximum":3840.0,"minimum":320.0,"title":"Viewport Width","description":"Browser viewport width in pixels.","default":1920,"examples":[1280,1920,2560]},"viewport_height":{"type":"integer","maximum":2160.0,"minimum":240.0,"title":"Viewport Height","description":"Browser viewport height in pixels.","default":1080,"examples":[720,1080,1440]}},"type":"object","title":"BrowserJobRequest","description":"Request parameters for browser automation with ferret.\n\nUse browser jobs when you need to:\n- Execute complex browser automation scripts\n- Automate form submissions, data extraction, or monitoring\n- Run headless browser tasks with proxy/VPN routing"},"cbintel__CohortRow":{"properties":{"vehicle":{"type":"string","title":"Vehicle"},"state":{"type":"string","title":"State","description":"One of ingesting/armed/unreadable/no_identifier/dark"},"n":{"type":"integer","title":"N"},"reason":{"type":"string","title":"Reason","description":"Why this cohort is in this state, in one line"},"counties":{"items":{"type":"string"},"type":"array","title":"Counties","description":"Named so the count is auditable"}},"type":"object","required":["vehicle","state","n","reason"],"title":"CohortRow","description":"One vehicle × state cohort. The unit an operator acts on."},"cbintel__ColorRecord":{"properties":{"hex":{"type":"string","pattern":"^#[0-9a-f]{6}$","title":"Hex"},"coverage_pct":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Coverage Pct"}},"type":"object","required":["hex","coverage_pct"],"title":"ColorRecord"},"cbintel__ColorsRequest":{"properties":{"image_b64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image B64"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"top_n":{"type":"integer","maximum":32.0,"minimum":1.0,"title":"Top N","default":8}},"type":"object","title":"ColorsRequest","description":"Either supply raw bytes via ``image_b64`` or a publicly-fetchable\nURL via ``image_url``. Exactly one MUST be set."},"cbintel__ColorsResponse":{"properties":{"colors":{"items":{"$ref":"#/components/schemas/cbintel__ColorRecord"},"type":"array","title":"Colors"},"sampled_pixels":{"type":"integer","title":"Sampled Pixels"},"deterministic":{"type":"boolean","title":"Deterministic","default":true}},"type":"object","required":["colors","sampled_pixels"],"title":"ColorsResponse"},"cbintel__CompleteRequest":{"properties":{"memorize":{"type":"boolean","title":"Memorize","description":"Whether to save to Scout","default":true}},"type":"object","title":"CompleteRequest","description":"Request to complete the session."},"cbintel__CompleteResponse":{"properties":{"session":{"$ref":"#/components/schemas/cbintel__ResearchSession"},"message":{"type":"string","title":"Message","default":""},"memory_result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Memory Result"}},"type":"object","required":["session"],"title":"CompleteResponse","description":"Response from completing session."},"cbintel__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbintel__CorpusStats":{"properties":{"total_captures":{"type":"integer","title":"Total Captures"},"transcribed_ok":{"type":"integer","title":"Transcribed Ok"},"success_rate":{"type":"number","title":"Success Rate"},"distinct_ticks":{"type":"integer","title":"Distinct Ticks"},"n_stations":{"type":"integer","title":"N Stations"},"span_start":{"type":"string","title":"Span Start"},"span_end":{"type":"string","title":"Span End"}},"type":"object","required":["total_captures","transcribed_ok","success_rate","distinct_ticks","n_stations","span_start","span_end"],"title":"CorpusStats"},"cbintel__CountyResource":{"properties":{"id":{"type":"integer","title":"Id"},"state":{"type":"string","title":"State"},"county":{"type":"string","title":"County"},"county_fips":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County Fips"},"resource_kind":{"type":"string","title":"Resource Kind"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"vendor_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Type"},"raw_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Type"},"status":{"type":"string","title":"Status"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"verified_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verified By"},"last_verified_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Verified At"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","state","county","resource_kind","status","created_at","updated_at"],"title":"CountyResource","description":"A single county resource (e.g. where a county publishes election results)."},"cbintel__CountyResourceList":{"properties":{"total":{"type":"integer","title":"Total"},"resources":{"items":{"$ref":"#/components/schemas/cbintel__CountyResource"},"type":"array","title":"Resources"}},"type":"object","required":["total","resources"],"title":"CountyResourceList"},"cbintel__CountyRow":{"properties":{"county":{"type":"string","title":"County"},"fips":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fips"},"vehicle":{"type":"string","title":"Vehicle"},"state":{"type":"string","title":"State"},"reason":{"type":"string","title":"Reason"},"artifact_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Artifact Url","description":"This cycle's page/artifact if the registry has one"},"landing":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Landing"},"report_family":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Report Family"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"last_fetched_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Fetched At"},"contests":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Contests"},"votes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Votes"}},"type":"object","required":["county","vehicle","state","reason"],"title":"CountyRow"},"cbintel__CoverageOut":{"properties":{"channel":{"type":"string","title":"Channel"},"election_date":{"type":"string","title":"Election Date"},"polls_close_utc":{"type":"string","format":"date-time","title":"Polls Close Utc"},"generated_at":{"type":"string","format":"date-time","title":"Generated At"},"totals":{"$ref":"#/components/schemas/cbintel__CoverageTotals"},"cohorts":{"items":{"$ref":"#/components/schemas/cbintel__CohortRow"},"type":"array","title":"Cohorts"},"reconciles":{"type":"boolean","title":"Reconciles","description":"True when ingesting + armed + unreadable + no_identifier + dark == counties. False means this module's classification has drifted from the fanout's — surfaced rather than silently rebalanced."},"fanout_armed":{"type":"integer","title":"Fanout Armed","description":"Counties load_targets() would actually poll — the ingest's own number"},"runtime_available":{"type":"boolean","title":"Runtime Available","description":"False when the workspace DB could not be read (writer lock); counts still valid"},"county_fanout":{"type":"boolean","title":"County Fanout","description":"True when this channel assembles results by county fanout — i.e. it has a `feed.county_registry`. FALSE means county coverage is not a thing that exists for this event (a convention floor, a caucus), and every count above is a structural zero rather than a tally in progress. Consumers MUST branch on this and not on `totals.counties == 0`: a transient zero before the compute lands is a different fact, and rendering `0/0 counties on the board (0%)` for an event with no counties is the false-zero class this whole night was spent removing (cbintel#263, cbapex#37).","default":true}},"type":"object","required":["channel","election_date","polls_close_utc","generated_at","totals","cohorts","reconciles","fanout_armed","runtime_available"],"title":"CoverageOut"},"cbintel__CoverageTotals":{"properties":{"counties":{"type":"integer","title":"Counties","description":"Every county in the channel's registry (the denominator)"},"ingesting":{"type":"integer","title":"Ingesting","description":"Counties with rows on the board THIS cycle — a landed claim, not a wiring claim. When runtime_available is false this degrades to 0 with every wired county counted under `armed` (we cannot claim landed without reading the workspace)."},"armed":{"type":"integer","title":"Armed","description":"Wired with this cycle's identifier but no rows landed yet — the gap the county-drivers work is closing. Was conflated into `ingesting` until cbelections#46."},"unreadable":{"type":"integer","title":"Unreadable","description":"Armed but image-only — needs OCR, not fixable tonight"},"no_identifier":{"type":"integer","title":"No Identifier","description":"Live vehicle, missing this cycle's eid/page — needs a URL"},"dark":{"type":"integer","title":"Dark","description":"Vehicle not in the live set — excluded from the fanout entirely"},"registered":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Registered","description":"Registered voters covered. NULL — cbintel's county registry carries no registered-voter counts, and a 0 here would read as 'none covered'. Wire from cbdistricts before trusting a weighted number."}},"type":"object","required":["counties","ingesting","armed","unreadable","no_identifier","dark"],"title":"CoverageTotals","description":"Counts that must sum to ``counties`` — see ``reconciles``."},"cbintel__CrawlJobRequest":{"properties":{"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID to associate this job with. Artifacts are saved under workspace path.","examples":["ws_abc123"]},"query":{"type":"string","maxLength":500,"minLength":1,"title":"Query","description":"Search query for web discovery. Use natural language questions or keyword phrases.","examples":["AI regulation California 2024","How does Tesla handle vehicle recalls?","climate policy trends"]},"prompt_type":{"type":"string","enum":["investigative","question_answering","technical_research","competitive_analysis","trend_analysis","contact_search"],"title":"Prompt Type","description":"AI evaluation strategy. 'investigative' for deep research, 'question_answering' for fact-finding, 'technical_research' for documentation/code, 'competitive_analysis' for business intel, 'trend_analysis' for patterns, 'contact_search' when the query names one person and the goal is pages about them (every expansion carries the name).","default":"investigative"},"max_urls":{"type":"integer","maximum":1000.0,"minimum":1.0,"title":"Max Urls","description":"Maximum URLs to process. Higher values = longer runtime but more comprehensive results.","default":100,"examples":[50,100,250]},"skip_embeddings":{"type":"boolean","title":"Skip Embeddings","description":"Skip vector embedding generation. Set true for faster results when semantic search not needed.","default":false},"device":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device","description":"Direct network interface binding (advanced). Use 'geo' parameter instead for VPN routing.","examples":["eth0","wg1"]},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic filter for VPN routing. Routes traffic through exit nodes in specified region.","examples":["us","us:ca","de","gb","jp"]},"enhanced":{"type":"boolean","title":"Enhanced","description":"Enable enhanced crawl with multi-retriever, chunking, and embedding filtering. More thorough but slower.","default":false},"enhanced_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Enhanced Config","description":"Enhanced crawl configuration overrides. Keys: engines, chunk_size, chunk_overlap, quality_threshold, similarity_threshold, assemble_text. Uses defaults if None.","examples":[{"chunk_size":1000,"quality_threshold":0.7}]}},"type":"object","required":["query"],"title":"CrawlJobRequest","description":"Request parameters for AI-powered web crawling.\n\nUse crawl jobs when you need to:\n- Research a topic across multiple sources\n- Gather information from web pages matching a query\n- Discover and analyze related content iteratively"},"cbintel__CreateReleaseRequest":{"properties":{"audience":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","title":"CreateReleaseRequest"},"cbintel__CreateSessionRequest":{"properties":{"goal":{"type":"string","title":"Goal","description":"Research goal"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"}},"type":"object","required":["goal"],"title":"CreateSessionRequest","description":"Request to create a new planner session."},"cbintel__CreateSessionResponse":{"properties":{"session":{"$ref":"#/components/schemas/cbintel__ResearchSession"},"message":{"type":"string","title":"Message","default":""},"suggested_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suggested Action"}},"type":"object","required":["session"],"title":"CreateSessionResponse","description":"Response from creating a session."},"cbintel__CreateWorkspaceRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Workspace name"},"description":{"type":"string","maxLength":1000,"title":"Description","description":"Description","default":""},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tags for organization"},"index_enabled":{"type":"boolean","title":"Index Enabled","description":"Enable document indexing","default":false}},"type":"object","required":["name"],"title":"CreateWorkspaceRequest","description":"Request to create a workspace."},"cbintel__CropRequest":{"properties":{"image_b64":{"type":"string","title":"Image B64"},"bbox":{"prefixItems":[{"type":"integer"},{"type":"integer"},{"type":"integer"},{"type":"integer"}],"type":"array","maxItems":4,"minItems":4,"title":"Bbox","description":"Pixel bbox (x0, y0, x1, y1). Must lie within image bounds."},"rembg":{"type":"boolean","title":"Rembg","description":"If True, attempt background removal. V2.0 returns 501 — punted to V2.1.","default":false}},"type":"object","required":["image_b64","bbox"],"title":"CropRequest"},"cbintel__CropResponse":{"properties":{"image_b64":{"type":"string","title":"Image B64"},"mime":{"type":"string","const":"image/png","title":"Mime","default":"image/png"},"width":{"type":"integer","title":"Width"},"height":{"type":"integer","title":"Height"}},"type":"object","required":["image_b64","width","height"],"title":"CropResponse"},"cbintel__DOMElement":{"properties":{"tag":{"type":"string","title":"Tag"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"classes":{"items":{"type":"string"},"type":"array","title":"Classes"},"href":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Href"},"src":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Src"},"attributes":{"additionalProperties":{"type":"string"},"type":"object","title":"Attributes"},"x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"X"},"y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Y"},"width":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Width"},"height":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Height"}},"type":"object","required":["tag"],"title":"DOMElement","description":"DOM element with position information."},"cbintel__DetectRequest":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"URL to fetch document from (e.g., https://files.campaignbrain.dev/docs/report.pdf)"},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path","description":"Local file path to process"},"use_ai":{"type":"boolean","title":"Use Ai","description":"Use AI for unknown file types","default":true}},"type":"object","title":"DetectRequest","description":"Request for document type detection."},"cbintel__DetectResponse":{"properties":{"detected_type":{"type":"string","enum":["pdf","image","office","text","html","unknown"],"title":"Detected Type"},"mime_type":{"type":"string","title":"Mime Type"},"provider":{"type":"string","enum":["mistral","unstructured","direct"],"title":"Provider"},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence"},"detection_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detection Method"},"file_info":{"additionalProperties":true,"type":"object","title":"File Info"},"file_path":{"type":"string","title":"File Path"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["detected_type","mime_type","provider","confidence","file_path"],"title":"DetectResponse","description":"Response from document type detection."},"cbintel__DispositionRequest":{"properties":{"verdict":{"type":"string","pattern":"^(accept|reject)$","title":"Verdict"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["verdict"],"title":"DispositionRequest"},"cbintel__DocumentDetail":{"properties":{"document_id":{"type":"string","title":"Document Id","description":"Stable 32-char ID = sha256(state + content_hash)[:32]"},"state":{"type":"string","title":"State","description":"State code (MI, GA, ...)"},"source_url":{"type":"string","title":"Source Url"},"source_tier":{"type":"integer","title":"Source Tier","description":"1=SoS primary, 2=local-election-board, 3=press, 4=aggregator","default":1},"kind":{"type":"string","title":"Kind","description":"Classifier label: rule|regulation|form|contact|deadline|result|candidate|filing|press|other"},"kind_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind Hint","description":"Catalog seed's hint (high-trust when present)"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"confidence":{"type":"number","title":"Confidence","description":"Classifier confidence 0.0-1.0"},"fetched_at":{"type":"string","title":"Fetched At","description":"ISO-8601 UTC"},"classified_at":{"type":"string","title":"Classified At"},"classifier_model":{"type":"string","title":"Classifier Model","description":"ollama model name, e.g. gemma3:4b"},"content_hash":{"type":"string","title":"Content Hash"},"text":{"type":"string","title":"Text","description":"Cleaned markdown / extracted plain text"},"extracted_dates":{"items":{"type":"string"},"type":"array","title":"Extracted Dates"},"extracted_emails":{"items":{"type":"string"},"type":"array","title":"Extracted Emails"},"extracted_phones":{"items":{"type":"string"},"type":"array","title":"Extracted Phones"}},"type":"object","required":["document_id","state","source_url","kind","title","summary","confidence","fetched_at","classified_at","classifier_model","content_hash","text"],"title":"DocumentDetail","description":"Full document including extracted text + entities."},"cbintel__DocumentJobRequest":{"properties":{"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID to associate this job with. Artifacts are saved under workspace path.","examples":["ws_abc123"]},"urls":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Urls","description":"List of URLs to fetch and process. Supports https://files.campaignbrain.dev/docs/...","examples":[["https://files.campaignbrain.dev/docs/report.pdf"],["https://example.com/document.pdf","https://example.com/image.png"]]},"files":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Files","description":"List of local file paths to process.","examples":[["/data/reports/quarterly.pdf","/data/scans/receipt.jpg"]]},"extract_entities":{"type":"boolean","title":"Extract Entities","description":"Extract named entities (people, organizations, locations, etc.)","default":true},"extract_summary":{"type":"boolean","title":"Extract Summary","description":"Generate document summaries","default":true},"extract_topics":{"type":"boolean","title":"Extract Topics","description":"Extract main topics from documents","default":true},"extract_relationships":{"type":"boolean","title":"Extract Relationships","description":"Extract entity-to-entity relationships (slower, more detailed)","default":false},"extract_facts":{"type":"boolean","title":"Extract Facts","description":"Extract key facts and claims with evidence","default":false},"translate_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Translate To","description":"Target language code for translation (e.g., 'en', 'es', 'zh')","examples":["en","es","fr","de","zh"]},"index_locally":{"type":"boolean","title":"Index Locally","description":"Store vectors in local vectl index for semantic search","default":true},"index_remotely":{"type":"boolean","title":"Index Remotely","description":"Index documents via remote Index API (requires workspace_id)","default":false},"chunk_size":{"type":"integer","maximum":5000.0,"minimum":100.0,"title":"Chunk Size","description":"Chunk size for text splitting (affects embedding granularity)","default":1000},"chunk_overlap":{"type":"integer","maximum":500.0,"minimum":0.0,"title":"Chunk Overlap","description":"Overlap between chunks to preserve context","default":100}},"type":"object","title":"DocumentJobRequest","description":"Request parameters for document intelligence processing.\n\nUse document jobs when you need to:\n- Process uploaded documents (PDF, images, Office files)\n- Extract text, entities, and relationships from documents\n- Index documents for RAG/semantic search\n- Translate document content\n\nAccepts URLs (including files.campaignbrain.dev) or local file paths."},"cbintel__DocumentList":{"properties":{"total":{"type":"integer","title":"Total","description":"Total matching rows (before pagination)"},"offset":{"type":"integer","title":"Offset"},"limit":{"type":"integer","title":"Limit"},"items":{"items":{"$ref":"#/components/schemas/cbintel__DocumentRow"},"type":"array","title":"Items"}},"type":"object","required":["total","offset","limit","items"],"title":"DocumentList"},"cbintel__DocumentRow":{"properties":{"document_id":{"type":"string","title":"Document Id","description":"Stable 32-char ID = sha256(state + content_hash)[:32]"},"state":{"type":"string","title":"State","description":"State code (MI, GA, ...)"},"source_url":{"type":"string","title":"Source Url"},"source_tier":{"type":"integer","title":"Source Tier","description":"1=SoS primary, 2=local-election-board, 3=press, 4=aggregator","default":1},"kind":{"type":"string","title":"Kind","description":"Classifier label: rule|regulation|form|contact|deadline|result|candidate|filing|press|other"},"kind_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind Hint","description":"Catalog seed's hint (high-trust when present)"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"confidence":{"type":"number","title":"Confidence","description":"Classifier confidence 0.0-1.0"},"fetched_at":{"type":"string","title":"Fetched At","description":"ISO-8601 UTC"},"classified_at":{"type":"string","title":"Classified At"},"classifier_model":{"type":"string","title":"Classifier Model","description":"ollama model name, e.g. gemma3:4b"}},"type":"object","required":["document_id","state","source_url","kind","title","summary","confidence","fetched_at","classified_at","classifier_model"],"title":"DocumentRow","description":"Compact list-view row. Excludes full text to keep responses small.\n\nFetch ``/documents/{document_id}`` to get the full ``text`` payload."},"cbintel__EnrollRequest":{"properties":{"url":{"type":"string","title":"Url"}},"type":"object","required":["url"],"title":"EnrollRequest"},"cbintel__EnrollResponse":{"properties":{"account":{"type":"string","title":"Account"},"channel_id":{"type":"string","title":"Channel Id","default":""},"channel_title":{"type":"string","title":"Channel Title","default":""},"channel_url":{"type":"string","title":"Channel Url"},"already_enrolled":{"type":"boolean","title":"Already Enrolled"},"note":{"type":"string","title":"Note"}},"type":"object","required":["account","channel_url","already_enrolled","note"],"title":"EnrollResponse"},"cbintel__Entity":{"properties":{"text":{"type":"string","title":"Text"},"type":{"type":"string","title":"Type"},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence"},"context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Context"}},"type":"object","required":["text","type","confidence"],"title":"Entity","description":"Extracted entity from document."},"cbintel__ExecuteRequest":{"properties":{},"type":"object","title":"ExecuteRequest","description":"Request to execute the current graph."},"cbintel__ExecuteResponse":{"properties":{"session":{"$ref":"#/components/schemas/cbintel__ResearchSession"},"job_id":{"type":"string","title":"Job Id"},"message":{"type":"string","title":"Message","default":""}},"type":"object","required":["session","job_id"],"title":"ExecuteResponse","description":"Response from graph execution."},"cbintel__ExtractRequest":{"properties":{"name":{"type":"string","title":"Name","description":"HAR name (vault or already-synced local)"},"domain":{"type":"string","title":"Domain","description":"Domain whose parser to run, e.g. x.com"},"limit":{"type":"integer","title":"Limit","description":"Max records to return","default":200}},"type":"object","required":["name","domain"],"title":"ExtractRequest"},"cbintel__FetchAttempt":{"properties":{"engine":{"type":"string","title":"Engine"},"ok":{"type":"boolean","title":"Ok"},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code"},"bytes":{"type":"integer","title":"Bytes","default":0},"blocked":{"type":"boolean","title":"Blocked","default":false},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"failure_origin":{"anyOf":[{"type":"string","enum":["local","remote"]},{"type":"null"}],"title":"Failure Origin"},"duration_ms":{"type":"integer","title":"Duration Ms","default":0},"exit_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Ip"}},"type":"object","required":["engine","ok"],"title":"FetchAttempt","description":"One engine's one attempt, recorded so callers can see the whole ladder.\n\nWithout this a caller sees only the last verdict and cannot tell \"four\nengines each broke for four unrelated internal reasons\" from \"the host is\nblocking us\" — which is exactly how Montana and Arizona were carried as\nCloudflare-walled for a week (cbintel#253)."},"cbintel__FetchRequest":{"properties":{"url":{"type":"string","minLength":1,"title":"Url","description":"Target URL to fetch"},"engine":{"type":"string","enum":["auto","aiohttp","curl","lynx","guzl"],"title":"Engine","description":"Fetch engine: auto selects based on options","default":"auto"},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic filter: 'us', 'us:ca', 'de', etc."},"exits":{"type":"integer","maximum":16.0,"minimum":1.0,"title":"Exits","description":"How many DISTINCT cbvpn exits to spread this request's attempts across (requires `geo`). Attempts rotate round-robin, so a retry or an engine escalation leaves from a different IP than the one before it. Raise it to turn a fetch into a reachability probe: `exits=3` answers 'is this host walled, or is one exit burned?', which a single-exit request structurally cannot. Each attempt reports its own exit in `attempts[].exit_ip`.","default":2},"proxy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proxy","description":"Direct proxy URL override (http://host:port)"},"proxy_rotation":{"type":"string","enum":["none","random","rotate"],"title":"Proxy Rotation","description":"Proxy rotation: none=use proxy or default, random=pick random from pool, rotate=cycle sequentially","default":"none"},"proxy_pool":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Proxy Pool","description":"Custom pool of proxy URLs for rotation"},"timeout":{"type":"integer","maximum":300.0,"minimum":5.0,"title":"Timeout","description":"Request timeout in seconds","default":30},"wait_until":{"type":"string","enum":["load","domcontentloaded","networkidle"],"title":"Wait Until","description":"Page load event to wait for (browser only). Defaults to 'domcontentloaded': 'networkidle' never arrives on a page carrying analytics or a chat widget, so it failed closed on exactly the sites that are hardest to reach. Raising the timeout cannot help — the condition is never satisfied. Ask for 'networkidle' explicitly when a page genuinely needs it.","default":"domcontentloaded"},"retries":{"type":"integer","maximum":5.0,"minimum":0.0,"title":"Retries","description":"Extra attempts per engine before escalating. Failures here are measurably nondeterministic — the same URL, engine and minute has been seen to succeed on attempt 4 of 15 — so a single attempt is not evidence a host is unreachable. Retries are spent only on fast, non-definitive failures; a timeout or a 404 escalates or stops immediately rather than being hammered.","default":2},"escalate":{"type":"boolean","title":"Escalate","description":"With engine='auto', fall through to the next engine when one fails or returns a challenge page. Set false to pin the first choice and see its raw verdict.","default":true},"wait_delay":{"type":"integer","maximum":30000.0,"minimum":0.0,"title":"Wait Delay","description":"Extra wait after page load in milliseconds (browser only)","default":0},"browser":{"type":"string","enum":["chromium","firefox","webkit"],"title":"Browser","description":"Browser to use for rendering","default":"chromium"},"viewport_width":{"type":"integer","maximum":3840.0,"minimum":320.0,"title":"Viewport Width","default":1920},"viewport_height":{"type":"integer","maximum":2160.0,"minimum":240.0,"title":"Viewport Height","default":1080},"headless":{"type":"boolean","title":"Headless","description":"Run browser in headless mode","default":true},"user_agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent","description":"Custom user agent string"},"user_agent_rotation":{"type":"string","enum":["none","random","rotate"],"title":"User Agent Rotation","description":"UA rotation: none=use user_agent or default, random=pick random from pool, rotate=cycle sequentially","default":"none"},"user_agent_pool":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"User Agent Pool","description":"Custom pool of user agents for rotation (uses built-in pool if not provided)"},"include_html":{"type":"boolean","title":"Include Html","description":"Include raw HTML","default":true},"include_markdown":{"type":"boolean","title":"Include Markdown","description":"Include markdown conversion","default":true},"include_text":{"type":"boolean","title":"Include Text","description":"Include plain text extraction","default":true},"include_screenshot":{"type":"boolean","title":"Include Screenshot","description":"Include PNG screenshot","default":false},"include_pdf":{"type":"boolean","title":"Include Pdf","description":"Include PDF rendering","default":false},"include_dom":{"type":"boolean","title":"Include Dom","description":"Include DOM elements with positions","default":false},"include_links":{"type":"boolean","title":"Include Links","description":"Include extracted links","default":true},"include_metadata":{"type":"boolean","title":"Include Metadata","description":"Include page metadata","default":true},"screenshot_full_page":{"type":"boolean","title":"Screenshot Full Page","description":"Capture full page or viewport","default":true},"screenshot_selector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot Selector","description":"CSS selector for targeted element capture"},"pdf_format":{"type":"string","enum":["A4","Letter","Legal"],"title":"Pdf Format","default":"A4"},"pdf_landscape":{"type":"boolean","title":"Pdf Landscape","description":"PDF in landscape orientation","default":false},"dom_selector":{"type":"string","title":"Dom Selector","description":"CSS selector for DOM extraction","default":"a, button, input, [onclick]"},"dom_include_positions":{"type":"boolean","title":"Dom Include Positions","description":"Include element bounding box positions","default":true},"delivery":{"type":"string","enum":["inline","files"],"title":"Delivery","description":"inline=JSON with base64, files=upload to files.campaignbrain.dev","default":"inline"},"max_inline_html":{"type":"integer","title":"Max Inline Html","description":"Max HTML size for inline delivery (bytes)","default":1000000},"max_inline_screenshot":{"type":"integer","title":"Max Inline Screenshot","description":"Max screenshot size for inline delivery (bytes)","default":5000000},"cbfiles_api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbfiles Api Key","description":"Client's CBFILES API key for uploads. If not provided, uses cbintel's internal key."},"cbfiles_bucket":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbfiles Bucket","description":"Target bucket for file uploads. If not provided, uses cbintel's owned default bucket (FILES_DEFAULT_BUCKET = cbintel-jobs). cbfiles write is owner-only — a client key must own the bucket it names."}},"additionalProperties":false,"type":"object","required":["url"],"title":"FetchRequest","description":"Swiss-army knife fetch request.\n\nComprehensive URL retrieval with all possible options.\n\nUser Agent Rotation:\n    Set ``user_agent_rotation`` to control UA selection:\n    - \"none\" (default): uses ``user_agent`` if set, otherwise engine default.\n    - \"random\": picks a random UA from ``user_agent_pool`` (or built-in pool).\n    - \"rotate\": deterministic per-URL pick from pool (hash-based, same URL = same UA).\n    When rotation is \"random\" or \"rotate\", the ``user_agent`` field is ignored.\n\nProxy Rotation:\n    Set ``proxy_rotation`` to control proxy selection:\n    - \"none\" (default): uses ``proxy`` if set, otherwise no proxy.\n    - \"random\": picks a random proxy from ``proxy_pool``.\n    - \"rotate\": deterministic per-URL pick from pool (hash-based, same URL = same proxy).\n    When rotation is \"random\" or \"rotate\", the ``proxy`` field is ignored.\n    A ``proxy_pool`` must be provided for rotation to work (no built-in pool)."},"cbintel__FetchResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"url":{"type":"string","title":"Url"},"final_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Final Url"},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"},"engine_used":{"type":"string","title":"Engine Used"},"geo_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo Used"},"exit_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Ip"},"egress_used":{"type":"string","title":"Egress Used","default":"direct"},"blocked":{"type":"boolean","title":"Blocked","default":false},"failure_origin":{"anyOf":[{"type":"string","enum":["local","remote"]},{"type":"null"}],"title":"Failure Origin"},"attempts":{"items":{"$ref":"#/components/schemas/cbintel__FetchAttempt"},"type":"array","title":"Attempts"},"duration_ms":{"type":"integer","title":"Duration Ms"},"html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Html"},"markdown":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Markdown"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"screenshot_b64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot B64"},"pdf_b64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pdf B64"},"dom_elements":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbintel__DOMElement"},"type":"array"},{"type":"null"}],"title":"Dom Elements"},"links":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Links"},"html_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Html Url"},"markdown_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Markdown Url"},"screenshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot Url"},"pdf_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pdf Url"},"dom_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dom Url"},"metadata":{"anyOf":[{"$ref":"#/components/schemas/cbintel__PageMetadata"},{"type":"null"}]}},"type":"object","required":["success","url","engine_used","duration_ms"],"title":"FetchResponse","description":"Comprehensive fetch result."},"cbintel__FollowupQuestion":{"properties":{"question_id":{"type":"string","title":"Question Id","description":"Unique identifier for this question"},"question":{"type":"string","title":"Question","description":"The question to ask"},"context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Context","description":"Context for why this is asked"},"options":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Options","description":"Suggested options if applicable"},"required":{"type":"boolean","title":"Required","description":"Whether answer is required","default":false},"answer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer","description":"User's answer if provided"}},"type":"object","required":["question_id","question"],"title":"FollowupQuestion","description":"A follow-up question to ask the user."},"cbintel__GapPriority":{"type":"string","enum":["high","medium","low"],"title":"GapPriority","description":"Priority level for identified gaps."},"cbintel__GateRequest":{"properties":{"value":{"type":"boolean","title":"Value"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["value"],"title":"GateRequest"},"cbintel__GeoRoutingResponse":{"properties":{"requested":{"type":"string","title":"Requested","description":"Geographic filter that was requested (e.g., 'us:ca')"},"allocated":{"type":"boolean","title":"Allocated","description":"Whether a VPN worker was successfully allocated"},"exit_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Ip","description":"Verified public IP of the VPN exit node"},"worker_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Worker Id","description":"WireGuard worker ID used for routing"},"device":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device","description":"Network device used (e.g., wg1, wg2)"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if allocation failed"}},"type":"object","required":["requested","allocated"],"title":"GeoRoutingResponse","description":"Geographic routing information in job response."},"cbintel__GraphJobRequest":{"properties":{"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID to associate this job with. Artifacts are saved under workspace path.","examples":["ws_abc123"]},"template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template","description":"Name of built-in graph template. Available: opponent_deep_dive, simple_search, geo_news_comparison, position_tracker.","examples":["opponent_deep_dive","simple_search","geo_news_comparison"]},"graph_yaml":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Graph Yaml","description":"Inline YAML graph definition. Use for custom pipelines. See graph documentation for schema.","examples":["name: my_graph\nstages:\n  - name: search\n    operations:\n      - type: crawl"]},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Input parameters for the graph. Required params depend on template/graph definition.","examples":[{"opponent_name":"John Smith","state":"Kentucky"},{"max_results":50,"query":"renewable energy policy"}]},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic filter for VPN routing. All fetch operations route through specified region.","examples":["us:ky","us:ca","de"]},"max_parallel":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":1.0},{"type":"null"}],"title":"Max Parallel","description":"Override graph's max parallel operations. Lower values = less resource usage.","examples":[3,5]},"timeout":{"anyOf":[{"type":"integer","maximum":3600.0,"minimum":60.0},{"type":"null"}],"title":"Timeout","description":"Override graph timeout in seconds. Increase for complex graphs.","examples":[300,600,1200]}},"type":"object","title":"GraphJobRequest","description":"Request parameters for Research Graph execution.\n\nUse graph jobs when you need to:\n- Execute multi-stage research pipelines\n- Combine multiple data sources into structured output\n- Run complex workflows with parallel operations"},"cbintel__GraphSpec":{"properties":{"template_name":{"type":"string","title":"Template Name","description":"Template to use"},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Template parameters"},"description":{"type":"string","title":"Description","description":"Description of what this graph does","default":""}},"type":"object","required":["template_name"],"title":"GraphSpec","description":"Specification for a research graph to execute."},"cbintel__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbintel__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbintel__HarEntry":{"properties":{"path":{"type":"string","title":"Path"},"size_bytes":{"type":"integer","title":"Size Bytes","default":0},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["path"],"title":"HarEntry"},"cbintel__HarInspect":{"properties":{"name":{"type":"string","title":"Name"},"domain":{"type":"string","title":"Domain"},"entries":{"type":"integer","title":"Entries"},"cookies":{"type":"integer","title":"Cookies"},"has_auth":{"type":"boolean","title":"Has Auth"},"json_bodies":{"type":"integer","title":"Json Bodies"},"parser":{"type":"boolean","title":"Parser","description":"A response-body parser is registered"}},"type":"object","required":["name","domain","entries","cookies","has_auth","json_bodies","parser"],"title":"HarInspect"},"cbintel__HarRecords":{"properties":{"name":{"type":"string","title":"Name"},"domain":{"type":"string","title":"Domain"},"count":{"type":"integer","title":"Count"},"records":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Records"}},"type":"object","required":["name","domain","count","records"],"title":"HarRecords"},"cbintel__HarSession":{"properties":{"domain":{"type":"string","title":"Domain"},"cookies":{"type":"integer","title":"Cookies"},"headers":{"type":"integer","title":"Headers"},"has_auth":{"type":"boolean","title":"Has Auth"}},"type":"object","required":["domain","cookies","headers","has_auth"],"title":"HarSession"},"cbintel__HarStatus":{"properties":{"local_hars":{"type":"integer","title":"Local Hars"},"vault_hars":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Vault Hars","description":"None when vault unreachable"},"vault_reachable":{"type":"boolean","title":"Vault Reachable"},"parsers":{"items":{"type":"string"},"type":"array","title":"Parsers"}},"type":"object","required":["local_hars","vault_reachable","parsers"],"title":"HarStatus"},"cbintel__HarSyncResult":{"properties":{"synced":{"items":{"type":"string"},"type":"array","title":"Synced"}},"type":"object","required":["synced"],"title":"HarSyncResult"},"cbintel__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbintel__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbintel__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbintel__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbintel__IdentifiedGap":{"properties":{"gap_id":{"type":"string","title":"Gap Id","description":"Unique identifier for this gap"},"description":{"type":"string","title":"Description","description":"Description of missing information"},"priority":{"$ref":"#/components/schemas/cbintel__GapPriority","default":"medium"},"category":{"type":"string","title":"Category","description":"Category of information","default":"general"},"suggested_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suggested Action","description":"Suggested action to fill this gap"},"addressed":{"type":"boolean","title":"Addressed","description":"Whether this gap has been addressed","default":false}},"type":"object","required":["gap_id","description"],"title":"IdentifiedGap","description":"A gap or missing information identified during analysis."},"cbintel__InspectRequest":{"properties":{"name":{"type":"string","title":"Name","description":"HAR name in the vault, e.g. x.com.har"}},"type":"object","required":["name"],"title":"InspectRequest"},"cbintel__JobProgress":{"properties":{"current_step":{"type":"string","title":"Current Step","description":"Name of the current execution step.","examples":["fetching","analyzing","synthesizing"]},"current":{"type":"integer","minimum":0.0,"title":"Current","description":"Current item number being processed.","default":0},"total":{"type":"integer","minimum":0.0,"title":"Total","description":"Total items to process. 0 if unknown.","default":0},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Human-readable status message for display.","examples":["Fetching page 5 of 20","Analyzing content quality"]},"percent":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Percent","description":"Completion percentage (0-100).","default":0.0,"examples":[0.0,25.5,50.0,100.0]}},"type":"object","required":["current_step"],"title":"JobProgress","description":"Progress information for a running job.\n\nAvailable when job status is PROCESSING. Shows current\nexecution step and completion percentage.\n\n## For LLMs\n\nUse `message` for user-friendly status updates.\nUse `percent` for progress bars or time estimates."},"cbintel__JobResult":{"properties":{"succeeded":{"type":"boolean","title":"Succeeded","default":true},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"result_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Url"},"summary":{"additionalProperties":true,"type":"object","title":"Summary"},"output_paths":{"items":{"type":"string"},"type":"array","title":"Output Paths"},"metrics":{"additionalProperties":true,"type":"object","title":"Metrics"}},"type":"object","title":"JobResult","description":"Result of a completed job."},"cbintel__JobStatus":{"type":"string","enum":["pending","queued","processing","completed","failed","cancelled"],"title":"JobStatus","description":"Job lifecycle status.\n\nStatus progresses through these states:\n```\nPENDING → QUEUED → PROCESSING → COMPLETED\n                              ↘ FAILED\n               ↓\n           CANCELLED (if cancelled while queued)\n```\n\n## For LLMs\n\n**Polling strategy:**\n- Poll GET /api/v1/jobs/{job_id} every 5 seconds\n- Stop polling when status is COMPLETED, FAILED, or CANCELLED\n\n**Decision logic:**\n```python\nif job.status == JobStatus.COMPLETED:\n    # Success - access job.result.result_url\n    return job.result.summary\nelif job.status == JobStatus.FAILED:\n    # Error - surface job.error to user\n    return f\"Job failed: {job.error}\"\nelif job.status == JobStatus.PROCESSING:\n    # Still working - show progress\n    return f\"Working: {job.progress.message} ({job.progress.percent}%)\"\nelif job.status in [JobStatus.PENDING, JobStatus.QUEUED]:\n    # Waiting - poll again\n    return \"Job queued, please wait...\"\nelif job.status == JobStatus.CANCELLED:\n    # User cancelled\n    return \"Job was cancelled\"\n```"},"cbintel__JobStatusResponse":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Unique job identifier"},"job_type":{"type":"string","title":"Job Type","description":"Job type: crawl, lazarus, vectl, screenshot, transcript, graph, browser, report, document"},"status":{"$ref":"#/components/schemas/cbintel__JobStatus","description":"Current job lifecycle status"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the job was created (UTC)"},"queued_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Queued At","description":"Timestamp when the job entered the queue"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At","description":"Timestamp when a worker began processing"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"Timestamp when the job finished (success or failure)"},"progress":{"anyOf":[{"$ref":"#/components/schemas/cbintel__JobProgress"},{"type":"null"}],"description":"Progress info available during 'processing' status"},"result":{"anyOf":[{"$ref":"#/components/schemas/cbintel__JobResult"},{"type":"null"}],"description":"Result payload available when status is 'completed'"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message available when status is 'failed'"},"geo_routing":{"anyOf":[{"$ref":"#/components/schemas/cbintel__GeoRoutingResponse"},{"type":"null"}],"description":"VPN routing details if geo parameter was used"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"User who launched the job (None = automated/system)"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace this job is associated with, if any"}},"type":"object","required":["job_id","job_type","status","created_at"],"title":"JobStatusResponse","description":"Response when polling job status.","example":{"completed_at":"2026-02-27T10:35:12Z","created_at":"2026-02-27T10:30:00Z","job_id":"job_a1b2c3d4e5f67890","job_type":"crawl","queued_at":"2026-02-27T10:30:00Z","result":{"metrics":{"duration_s":311,"pages_fetched":42},"output_paths":["crawl_result.json","pages/"],"result_url":"https://files.campaignbrain.dev/store/cbintel-jobs/job_a1b2c3d4e5f67890/crawl_result.json","succeeded":true,"summary":{"quality_score":0.87,"urls_processed":42}},"started_at":"2026-02-27T10:30:01Z","status":"completed"}},"cbintel__JobSubmitResponse":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Unique job identifier for polling and cancellation"},"job_type":{"type":"string","title":"Job Type","description":"Job type: crawl, lazarus, vectl, screenshot, transcript, graph, browser, report, document"},"status":{"$ref":"#/components/schemas/cbintel__JobStatus","description":"Initial job status (always 'queued' on submission)"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the job was created (UTC)"},"poll_url":{"type":"string","title":"Poll Url","description":"Relative URL to poll for job status updates"}},"type":"object","required":["job_id","job_type","status","created_at","poll_url"],"title":"JobSubmitResponse","description":"Response when submitting a job.","example":{"created_at":"2026-02-27T10:30:00Z","job_id":"job_a1b2c3d4e5f67890","job_type":"crawl","poll_url":"/api/v1/jobs/job_a1b2c3d4e5f67890","status":"queued"}},"cbintel__LayoutProfile":{"type":"string","enum":["research_dashboard","media_gallery","document_vault","geo_intel","pipeline_monitor","archive","mixed"],"title":"LayoutProfile","description":"Layout profile for workspace visualization.\n\nEach profile optimizes the UI for specific artifact compositions."},"cbintel__LazarusJobRequest":{"properties":{"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID to associate this job with. Artifacts are saved under workspace path.","examples":["ws_abc123"]},"domain":{"type":"string","maxLength":253,"minLength":1,"title":"Domain","description":"Domain to search archives for. Do not include protocol (http/https).","examples":["example.com","news.ycombinator.com","blog.company.io"]},"providers":{"items":{"type":"string"},"type":"array","title":"Providers","description":"Archive sources to query. 'wayback' = Internet Archive, 'commoncrawl' = Common Crawl.","examples":[["wayback"],["wayback","commoncrawl"]]},"from_date":{"anyOf":[{"type":"string","pattern":"^\\d{8}$"},{"type":"null"}],"title":"From Date","description":"Start date for archive search in YYYYMMDD format.","examples":["20200101","20230615"]},"to_date":{"anyOf":[{"type":"string","pattern":"^\\d{8}$"},{"type":"null"}],"title":"To Date","description":"End date for archive search in YYYYMMDD format.","examples":["20231231","20240101"]},"sample_size":{"type":"integer","maximum":10000.0,"minimum":1.0,"title":"Sample Size","description":"Maximum number of snapshots to retrieve. Higher values = more data but longer runtime.","default":100,"examples":[50,100,500]},"dedupe":{"type":"boolean","title":"Dedupe","description":"Remove duplicate content across snapshots. Recommended for most use cases.","default":true},"use_pipeline_v2":{"type":"boolean","title":"Use Pipeline V2","description":"Use fast WARC-based pipeline. Set False only for legacy compatibility.","default":true},"timeout":{"type":"integer","maximum":1800.0,"minimum":60.0,"title":"Timeout","description":"Pipeline timeout in seconds. Increase for large domains.","default":300,"examples":[300,600,1200]},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic filter for VPN routing. Useful for geo-blocked archive access.","examples":["us","us:ca","de"]}},"type":"object","required":["domain"],"title":"LazarusJobRequest","description":"Request parameters for historical archive retrieval.\n\nUse Lazarus jobs when you need to:\n- Find deleted or modified web content\n- Track how a website changed over time\n- Recover historical snapshots from Internet Archive"},"cbintel__LocalSearchRequest":{"properties":{"query":{"type":"string","minLength":1,"title":"Query","description":"Search query text"},"store":{"type":"string","title":"Store","description":"Vector store name (typically a workspace_id)","default":"default"},"k":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"K","description":"Number of results","default":10},"min_score":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Min Score","description":"Minimum similarity","default":0.0}},"type":"object","required":["query"],"title":"LocalSearchRequest","description":"Local semantic-search request."},"cbintel__LocalSearchResponse":{"properties":{"query":{"type":"string","title":"Query"},"store":{"type":"string","title":"Store"},"backend":{"type":"string","title":"Backend"},"embed_ms":{"type":"integer","title":"Embed Ms"},"search_ms":{"type":"integer","title":"Search Ms"},"results":{"items":{"$ref":"#/components/schemas/cbintel__LocalSearchResult"},"type":"array","title":"Results"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["query","store","backend","embed_ms","search_ms","results","total"],"title":"LocalSearchResponse","description":"Local search response with timings."},"cbintel__LocalSearchResult":{"properties":{"id":{"type":"string","title":"Id"},"score":{"type":"number","title":"Score"},"text":{"type":"string","title":"Text"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"workspace":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace"},"job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Id"},"chunk_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Chunk Index"}},"type":"object","required":["id","score","text"],"title":"LocalSearchResult","description":"Single hit from the local store."},"cbintel__LocalStoreInfo":{"properties":{"name":{"type":"string","title":"Name"},"vectors":{"type":"integer","title":"Vectors"},"path":{"type":"string","title":"Path"},"size_bytes":{"type":"integer","title":"Size Bytes"},"backend":{"type":"string","title":"Backend"}},"type":"object","required":["name","vectors","path","size_bytes","backend"],"title":"LocalStoreInfo","description":"Per-store inventory entry."},"cbintel__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbintel__ModelCard":{"properties":{"id":{"type":"string","title":"Id","description":"Model identifier (e.g. 'qwen3:8b', 'claude-sonnet-4-6')"},"name":{"type":"string","title":"Name","description":"Human-readable name"},"provider":{"type":"string","enum":["ollama","claude","mistral"],"title":"Provider","description":"Provider backend"},"tags":{"items":{"$ref":"#/components/schemas/cbintel__ModelTag"},"type":"array","title":"Tags","description":"Filterable tags"},"context_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Context Length","description":"Max context window"},"size":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Size","description":"Parameter count (e.g. '8B', '70B')"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Short description"},"available":{"type":"boolean","title":"Available","description":"Currently pulled / accessible","default":false}},"type":"object","required":["id","name","provider"],"title":"ModelCard","description":"A single model with its metadata and tags."},"cbintel__ModelListResponse":{"properties":{"models":{"items":{"$ref":"#/components/schemas/cbintel__ModelCard"},"type":"array","title":"Models"},"total":{"type":"integer","title":"Total"},"filters_applied":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Filters Applied"}},"type":"object","required":["models","total"],"title":"ModelListResponse","description":"Response for GET /api/v1/models."},"cbintel__ModelRecommendation":{"properties":{"task":{"$ref":"#/components/schemas/cbintel__TaskType"},"recommended":{"items":{"$ref":"#/components/schemas/cbintel__ModelCard"},"type":"array","title":"Recommended"},"reason":{"type":"string","title":"Reason"}},"type":"object","required":["task","recommended","reason"],"title":"ModelRecommendation","description":"Response for GET /api/v1/models/recommend."},"cbintel__ModelTag":{"type":"string","enum":["local","cloud","ollama","claude","mistral","chat","thinking","coding","vision","embedding","tools","ocr","translation","tiny","small","medium","large"],"title":"ModelTag","description":"Canonical tags for model filtering."},"cbintel__OperationType":{"type":"string","enum":["bank_create","bank_delete","bank_update","bank_list","bank_get","worker_list","worker_get","worker_vpn_start","worker_vpn_stop","worker_proxy_start","worker_proxy_stop","cluster_status","cluster_reset","device_command","job_crawl","job_lazarus","job_screenshot","job_transcript","job_vectl","job_status","job_cancel","fetch","health_check","root","usage_query"],"title":"OperationType","description":"Types of tracked API operations."},"cbintel__PageMetadata":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"og_image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Og Image"},"og_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Og Title"},"og_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Og Description"},"canonical_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Url"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type"}},"type":"object","title":"PageMetadata","description":"Extracted page metadata."},"cbintel__PipelineListItem":{"properties":{"pipeline_id":{"type":"string","title":"Pipeline Id","description":"Unique pipeline identifier"},"status":{"type":"string","title":"Status","description":"running, completed, failed, or cancelled"},"current_step":{"type":"integer","title":"Current Step","description":"Zero-based index of current step"},"total_steps":{"type":"integer","title":"Total Steps","description":"Total number of steps"},"created_at":{"type":"string","title":"Created At","description":"ISO-format creation timestamp"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At","description":"ISO-format completion timestamp"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if failed"}},"type":"object","required":["pipeline_id","status","current_step","total_steps","created_at"],"title":"PipelineListItem","description":"Summary of a pipeline for list responses."},"cbintel__PipelineListResponse":{"properties":{"pipelines":{"items":{"$ref":"#/components/schemas/cbintel__PipelineListItem"},"type":"array","title":"Pipelines","description":"Pipeline summaries"},"total":{"type":"integer","title":"Total","description":"Number of pipelines returned"}},"type":"object","required":["pipelines","total"],"title":"PipelineListResponse","description":"Response for listing pipelines."},"cbintel__PipelineResponse":{"properties":{"detected_type":{"type":"string","title":"Detected Type"},"mime_type":{"type":"string","title":"Mime Type"},"provider":{"type":"string","title":"Provider"},"confidence":{"type":"number","title":"Confidence"},"text":{"type":"string","title":"Text"},"images":{"items":{"type":"string"},"type":"array","title":"Images"},"pages":{"type":"integer","title":"Pages"},"word_count":{"type":"integer","title":"Word Count"},"processing_strategy":{"type":"string","title":"Processing Strategy"},"title":{"type":"string","title":"Title"},"summary":{"type":"string","title":"Summary"},"entities":{"items":{"$ref":"#/components/schemas/cbintel__Entity"},"type":"array","title":"Entities"},"topics":{"items":{"type":"string"},"type":"array","title":"Topics"},"document_type":{"type":"string","title":"Document Type"},"key_sections":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Key Sections"},"file_path":{"type":"string","title":"File Path"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["detected_type","mime_type","provider","confidence","text","pages","word_count","processing_strategy","title","summary","document_type","file_path"],"title":"PipelineResponse","description":"Response from full document pipeline."},"cbintel__PipelineRetryRequest":{"properties":{"override_request":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Override Request","description":"Override params for the failed step's request (merged with existing)"}},"type":"object","title":"PipelineRetryRequest","description":"Optional request body for retrying a failed pipeline."},"cbintel__PipelineStatusResponse":{"properties":{"pipeline_id":{"type":"string","title":"Pipeline Id","description":"Unique pipeline identifier"},"status":{"type":"string","title":"Status","description":"Pipeline status: running, completed, failed, cancelled"},"current_step":{"type":"integer","title":"Current Step","description":"Zero-based index of the currently executing step"},"total_steps":{"type":"integer","title":"Total Steps","description":"Total number of steps in the pipeline"},"steps":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Steps","description":"Per-step status: each has type, job_id, and status"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the pipeline was created (UTC)"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"Timestamp when the pipeline finished"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if pipeline failed"}},"type":"object","required":["pipeline_id","status","current_step","total_steps","steps","created_at"],"title":"PipelineStatusResponse","description":"Response when polling pipeline status.","example":{"created_at":"2026-02-27T10:30:00Z","current_step":1,"pipeline_id":"pipe_abc123def456","status":"running","steps":[{"job_id":"job_a1b2c3d4e5f67890","status":"completed","type":"crawl"},{"job_id":"job_f8e7d6c5b4a39012","status":"processing","type":"report"}],"total_steps":2}},"cbintel__PipelineStep":{"properties":{"type":{"type":"string","title":"Type","description":"Job type: crawl, report, lazarus, screenshot, transcript, document, graph, browser, vectl"},"request":{"additionalProperties":true,"type":"object","title":"Request","description":"Request params. Use $previous.result_url / $previous.summary.KEY for sequential substitution, or $steps[N].result_url / $steps[N].summary.KEY to reference any prior step by index."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Optional human-readable title for this step"},"group":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Group","description":"Parallel execution group. Steps with the same group number run simultaneously; the next group waits for all prior groups to complete. If omitted, each step gets its own sequential group."}},"type":"object","required":["type","request"],"title":"PipelineStep","description":"A single step in a pipeline."},"cbintel__PipelineSubmitResponse":{"properties":{"pipeline_id":{"type":"string","title":"Pipeline Id","description":"Unique pipeline identifier for polling"},"status":{"type":"string","title":"Status","description":"Pipeline status: running, completed, failed, cancelled"},"first_job_id":{"type":"string","title":"First Job Id","description":"Job ID of the first step (currently executing)"},"total_steps":{"type":"integer","title":"Total Steps","description":"Total number of steps in the pipeline"},"poll_url":{"type":"string","title":"Poll Url","description":"Relative URL to poll for pipeline status"}},"type":"object","required":["pipeline_id","status","first_job_id","total_steps","poll_url"],"title":"PipelineSubmitResponse","description":"Response when creating a pipeline.","example":{"first_job_id":"job_a1b2c3d4e5f67890","pipeline_id":"pipe_abc123def456","poll_url":"/api/v1/jobs/pipeline/pipe_abc123def456","status":"running","total_steps":2}},"cbintel__PlannerPhase":{"type":"string","enum":["goal_capture","initial_plan","execution","analysis","followup","expansion","confirmation","scheduling","complete"],"title":"PlannerPhase","description":"Current phase in the research planning workflow."},"cbintel__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbintel__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbintel__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbintel__PriorKnowledge":{"properties":{"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"entity_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Type"},"entity_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Name"},"total_facts":{"type":"integer","title":"Total Facts","default":0},"categories":{"items":{"type":"string"},"type":"array","title":"Categories"},"context_summary":{"type":"string","title":"Context Summary","default":""},"found":{"type":"boolean","title":"Found","default":false}},"type":"object","title":"PriorKnowledge","description":"Prior knowledge recalled from Scout."},"cbintel__ProcessRequest":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"URL to fetch document from (e.g., https://files.campaignbrain.dev/docs/report.pdf)"},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path","description":"Local file path to process"},"extract_images":{"type":"boolean","title":"Extract Images","description":"Extract embedded images as base64","default":true},"extract_tables":{"type":"boolean","title":"Extract Tables","description":"Extract table data (reserved for future)","default":true},"ocr_provider":{"anyOf":[{"type":"string","enum":["mistral","unstructured"]},{"type":"null"}],"title":"Ocr Provider","description":"Override OCR provider selection"}},"type":"object","title":"ProcessRequest","description":"Request for document content extraction."},"cbintel__ProcessResponse":{"properties":{"text":{"type":"string","title":"Text"},"images":{"items":{"type":"string"},"type":"array","title":"Images"},"tables":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Tables"},"pages":{"type":"integer","title":"Pages"},"word_count":{"type":"integer","title":"Word Count"},"processing_strategy":{"type":"string","title":"Processing Strategy"},"detected_type":{"type":"string","title":"Detected Type"},"file_path":{"type":"string","title":"File Path"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["text","pages","word_count","processing_strategy","detected_type","file_path"],"title":"ProcessResponse","description":"Response from document content extraction."},"cbintel__ReasonRequest":{"properties":{"reason":{"type":"string","minLength":1,"title":"Reason"}},"type":"object","required":["reason"],"title":"ReasonRequest"},"cbintel__RegisterAssetRequest":{"properties":{"origin":{"type":"string","pattern":"^(cbscan|email|links-share|manual|crawl)$","title":"Origin"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"kind":{"anyOf":[{"type":"string","pattern":"^(scan|deck|notes|seed|external|dataset)$"},{"type":"null"}],"title":"Kind"},"file_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Ref"},"sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha256"},"sensitivity":{"type":"string","pattern":"^(restricted|public)$","title":"Sensitivity","default":"restricted"},"supplied_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Supplied By"},"ingest_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ingest Job Id"},"received_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Received At"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","required":["origin"],"title":"RegisterAssetRequest"},"cbintel__ReportJobRequest":{"properties":{"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID to associate this job with. Artifacts are saved under workspace path.","examples":["ws_abc123"]},"source_url":{"type":"string","minLength":1,"title":"Source Url","description":"URL to crawl result JSON or raw content (typically files.campaignbrain.dev).","examples":["https://files.campaignbrain.dev/store/cbintel-jobs/job_xxx/crawl_result.json"]},"title":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Title","description":"Report title. Defaults to the crawl query if not specified.","examples":["2026 Michigan Governor's Race","AI Regulation Briefing"]},"template":{"type":"string","enum":["briefing","timeline","comparison"],"title":"Template","description":"Report template. 'briefing' for structured intel, 'timeline' for chronological, 'comparison' for side-by-side analysis.","default":"briefing"},"provider":{"type":"string","enum":["claude","ollama"],"title":"Provider","description":"AI provider for report generation.","default":"claude"},"max_tokens":{"type":"integer","maximum":16384.0,"minimum":1024.0,"title":"Max Tokens","description":"Maximum tokens for the AI-generated report body.","default":8192}},"type":"object","required":["source_url"],"title":"ReportJobRequest","description":"Request parameters for generating a polished report from crawl results.\n\nUse report jobs when you need to:\n- Turn raw crawl results into a readable Markdown document\n- Generate briefings, timelines, or comparison reports\n- Produce human-readable summaries from research data"},"cbintel__ResearchSession":{"properties":{"session_id":{"type":"string","title":"Session Id","description":"Unique session identifier"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Associated workspace"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"original_goal":{"type":"string","title":"Original Goal","description":"User's original research goal"},"entity_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Name","description":"Primary entity extracted"},"entity_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Type","description":"Type: person, org, etc."},"prior_knowledge":{"anyOf":[{"$ref":"#/components/schemas/cbintel__PriorKnowledge"},{"type":"null"}],"description":"Prior knowledge from Scout recall"},"current_phase":{"$ref":"#/components/schemas/cbintel__PlannerPhase","default":"goal_capture"},"turns":{"items":{"$ref":"#/components/schemas/cbintel__ResearchTurn"},"type":"array","title":"Turns"},"coverage_score":{"type":"number","title":"Coverage Score","description":"Overall coverage 0-100","default":0.0},"total_facts_discovered":{"type":"integer","title":"Total Facts Discovered","default":0},"total_sources_analyzed":{"type":"integer","title":"Total Sources Analyzed","default":0},"schedule_requested":{"type":"boolean","title":"Schedule Requested","default":false},"cron_expression":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cron Expression"},"schedule_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Schedule Id"},"final_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Final Summary"},"memorized":{"type":"boolean","title":"Memorized","description":"Whether findings saved to Scout","default":false},"memory_result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Memory Result"}},"type":"object","required":["session_id","original_goal"],"title":"ResearchSession","description":"A complete research planning session."},"cbintel__ResearchTurn":{"properties":{"turn_id":{"type":"string","title":"Turn Id","description":"Unique identifier for this turn"},"turn_number":{"type":"integer","title":"Turn Number","description":"Sequential turn number"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"graph":{"anyOf":[{"$ref":"#/components/schemas/cbintel__GraphSpec"},{"type":"null"}],"description":"Graph executed this turn"},"result":{"anyOf":[{"$ref":"#/components/schemas/cbintel__TurnResult"},{"type":"null"}],"description":"Execution result"},"gaps_identified":{"items":{"$ref":"#/components/schemas/cbintel__IdentifiedGap"},"type":"array","title":"Gaps Identified"},"coverage_score":{"type":"number","title":"Coverage Score","description":"0-100 coverage percentage","default":0.0},"questions_asked":{"items":{"$ref":"#/components/schemas/cbintel__FollowupQuestion"},"type":"array","title":"Questions Asked"},"user_input":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Input","description":"User's response this turn"},"expanded_from_previous":{"type":"boolean","title":"Expanded From Previous","default":false},"expansion_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expansion Reason"}},"type":"object","required":["turn_id","turn_number"],"title":"ResearchTurn","description":"A single turn in the research conversation."},"cbintel__ResourceCreate":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Source URL for this resource"},"vendor_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Type","description":"Normalized vendor (enhanced_voting/clarity/pdf/custom/...)"},"raw_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Type","description":"Original publish-Type label"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Override status (live/pdf/missing/unverified)"},"confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"county_fips":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County Fips"},"verified_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verified By","description":"Who set this — 'human' | 'agent' | a username"},"state":{"type":"string","title":"State","description":"Two-letter state code, e.g. 'MI'"},"county":{"type":"string","title":"County","description":"County name, e.g. 'WAYNE'"},"resource_kind":{"type":"string","title":"Resource Kind","default":"election_results_source"}},"type":"object","required":["state","county"],"title":"ResourceCreate"},"cbintel__ResourcePatch":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Source URL for this resource"},"vendor_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Type","description":"Normalized vendor (enhanced_voting/clarity/pdf/custom/...)"},"raw_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Type","description":"Original publish-Type label"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Override status (live/pdf/missing/unverified)"},"confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"county_fips":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County Fips"},"verified_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verified By","description":"Who set this — 'human' | 'agent' | a username"}},"type":"object","title":"ResourcePatch","description":"Partial update — only provided fields change. Setting ``url`` on a\n'missing' gap flips its status to live/pdf automatically."},"cbintel__RespondRequest":{"properties":{"user_input":{"type":"string","title":"User Input","description":"Free text input","default":""},"answers":{"additionalProperties":{"type":"string"},"type":"object","title":"Answers","description":"Answers to specific questions by question_id"},"action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action","description":"Explicit action: execute, expand, skip, schedule, complete"}},"type":"object","title":"RespondRequest","description":"Request to respond to planner questions."},"cbintel__RespondResponse":{"properties":{"session":{"$ref":"#/components/schemas/cbintel__ResearchSession"},"message":{"type":"string","title":"Message","default":""},"questions":{"items":{"$ref":"#/components/schemas/cbintel__FollowupQuestion"},"type":"array","title":"Questions"},"suggested_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suggested Action"}},"type":"object","required":["session"],"title":"RespondResponse","description":"Response from processing user input."},"cbintel__RunDetail":{"properties":{"run_id":{"type":"string","title":"Run Id"},"workspace_id":{"type":"string","title":"Workspace Id"},"graph_name":{"type":"string","title":"Graph Name"},"graph_version":{"type":"string","title":"Graph Version","default":"1.0.0"},"status":{"$ref":"#/components/schemas/cbintel__RunStatus"},"started_at":{"type":"string","format":"date-time","title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"duration_seconds":{"type":"number","title":"Duration Seconds","default":0.0},"input_params":{"additionalProperties":true,"type":"object","title":"Input Params","default":{}},"input_artifact_ids":{"items":{"type":"string"},"type":"array","title":"Input Artifact Ids","default":[]},"output_artifact_ids":{"items":{"type":"string"},"type":"array","title":"Output Artifact Ids","default":[]},"stages_completed":{"type":"integer","title":"Stages Completed","default":0},"stages_total":{"type":"integer","title":"Stages Total","default":0},"operations_completed":{"type":"integer","title":"Operations Completed","default":0},"operations_failed":{"type":"integer","title":"Operations Failed","default":0},"cache_hits":{"type":"integer","title":"Cache Hits","default":0},"incremental_skips":{"type":"integer","title":"Incremental Skips","default":0},"errors":{"items":{"type":"string"},"type":"array","title":"Errors","default":[]},"address":{"type":"string","title":"Address","default":""}},"type":"object","required":["run_id","workspace_id","graph_name","status","started_at"],"title":"RunDetail","description":"Detailed run information."},"cbintel__RunListResponse":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"runs":{"items":{"$ref":"#/components/schemas/cbintel__RunSummary"},"type":"array","title":"Runs"}},"type":"object","required":["workspace_id","runs"],"title":"RunListResponse","description":"Response for run listing within a workspace."},"cbintel__RunStatus":{"type":"string","enum":["pending","running","completed","failed","cancelled"],"title":"RunStatus","description":"Status of an execution run."},"cbintel__RunSummary":{"properties":{"run_id":{"type":"string","title":"Run Id"},"workspace_id":{"type":"string","title":"Workspace Id"},"graph_name":{"type":"string","title":"Graph Name"},"status":{"$ref":"#/components/schemas/cbintel__RunStatus"},"started_at":{"type":"string","format":"date-time","title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"duration_seconds":{"type":"number","title":"Duration Seconds","default":0.0},"stages_completed":{"type":"integer","title":"Stages Completed","default":0},"stages_total":{"type":"integer","title":"Stages Total","default":0},"artifact_count":{"type":"integer","title":"Artifact Count","default":0},"address":{"type":"string","title":"Address","default":""}},"type":"object","required":["run_id","workspace_id","graph_name","status","started_at"],"title":"RunSummary","description":"Summary of a run for listing views."},"cbintel__Schedule":{"properties":{"schedule_id":{"type":"string","title":"Schedule Id","description":"Unique schedule identifier"},"name":{"type":"string","title":"Name","description":"Human-readable name"},"description":{"type":"string","title":"Description","default":""},"cron":{"type":"string","title":"Cron","description":"Cron expression"},"graph_name":{"type":"string","title":"Graph Name","description":"Graph to execute"},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Graph parameters"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Associated workspace"},"status":{"$ref":"#/components/schemas/cbintel__ScheduleStatus","default":"enabled"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"last_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Run At"},"last_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Job Id"},"next_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Run At"},"run_count":{"type":"integer","title":"Run Count","description":"Total number of runs","default":0},"failure_count":{"type":"integer","title":"Failure Count","description":"Number of failed runs","default":0},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["schedule_id","name","cron","graph_name","created_at","updated_at"],"title":"Schedule","description":"A scheduled graph execution.\n\nSchedules define when and how graphs should be executed automatically.\nThe cron expression follows standard cron format:\n    minute (0-59)\n    hour (0-23)\n    day of month (1-31)\n    month (1-12)\n    day of week (0-6, 0=Sunday)\n\nExamples:\n    \"0 * * * *\"     - Every hour at minute 0\n    \"*/15 * * * *\"  - Every 15 minutes\n    \"0 9 * * 1-5\"   - 9 AM on weekdays\n    \"0 0 1 * *\"     - Midnight on 1st of each month"},"cbintel__ScheduleCreateRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Unique schedule name"},"description":{"type":"string","title":"Description","default":""},"cron":{"type":"string","title":"Cron","description":"Cron expression (minute hour day month weekday)"},"graph_name":{"type":"string","title":"Graph Name","description":"Name of graph to execute"},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Graph input parameters"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["name","cron","graph_name"],"title":"ScheduleCreateRequest","description":"Request to create a schedule."},"cbintel__ScheduleListResponse":{"properties":{"schedules":{"items":{"$ref":"#/components/schemas/cbintel__ScheduleSummary"},"type":"array","title":"Schedules"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["schedules","total"],"title":"ScheduleListResponse","description":"Response for schedule list."},"cbintel__ScheduleRequest":{"properties":{"cron":{"type":"string","title":"Cron","description":"Cron expression"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Optional schedule name"}},"type":"object","required":["cron"],"title":"ScheduleRequest","description":"Request to schedule periodic execution."},"cbintel__ScheduleResponse":{"properties":{"session":{"$ref":"#/components/schemas/cbintel__ResearchSession"},"schedule_id":{"type":"string","title":"Schedule Id"},"message":{"type":"string","title":"Message","default":""}},"type":"object","required":["session","schedule_id"],"title":"ScheduleResponse","description":"Response from scheduling."},"cbintel__ScheduleStatus":{"type":"string","enum":["enabled","disabled","paused"],"title":"ScheduleStatus","description":"Status of a schedule."},"cbintel__ScheduleStatusUpdate":{"properties":{"status":{"$ref":"#/components/schemas/cbintel__ScheduleStatus"}},"type":"object","required":["status"],"title":"ScheduleStatusUpdate","description":"Request to update schedule status."},"cbintel__ScheduleSummary":{"properties":{"schedule_id":{"type":"string","title":"Schedule Id"},"name":{"type":"string","title":"Name"},"cron":{"type":"string","title":"Cron"},"graph_name":{"type":"string","title":"Graph Name"},"status":{"$ref":"#/components/schemas/cbintel__ScheduleStatus"},"next_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Run At"},"last_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Run At"},"run_count":{"type":"integer","title":"Run Count"}},"type":"object","required":["schedule_id","name","cron","graph_name","status","next_run_at","last_run_at","run_count"],"title":"ScheduleSummary","description":"Summary view of a schedule for listing."},"cbintel__ScheduleUpdateRequest":{"properties":{"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"cron":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cron"},"params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Params"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"}},"type":"object","title":"ScheduleUpdateRequest","description":"Request to update a schedule."},"cbintel__ScreenshotJobRequest":{"properties":{"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID to associate this job with. Artifacts are saved under workspace path.","examples":["ws_abc123"]},"urls":{"items":{"type":"string"},"type":"array","maxItems":50,"minItems":1,"title":"Urls","description":"URLs to capture. Each URL will be visited and captured separately.","examples":[["https://example.com"],["https://site1.com","https://site2.com"]]},"full_page":{"type":"boolean","title":"Full Page","description":"Capture entire scrollable page. Set false for viewport-only capture.","default":true},"capture_dom":{"type":"boolean","title":"Capture Dom","description":"Also extract DOM structure with element positions. Useful for analysis.","default":false},"viewport_width":{"type":"integer","maximum":3840.0,"minimum":320.0,"title":"Viewport Width","description":"Browser viewport width in pixels.","default":1920,"examples":[1280,1920,2560]},"viewport_height":{"type":"integer","maximum":2160.0,"minimum":240.0,"title":"Viewport Height","description":"Browser viewport height in pixels.","default":1080,"examples":[720,1080,1440]},"format":{"type":"string","enum":["png","jpeg","pdf"],"title":"Format","description":"Output format. 'png' for lossless, 'jpeg' for smaller files, 'pdf' for documents.","default":"png"},"proxy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proxy","description":"Direct proxy URL override. Use 'geo' parameter instead for VPN routing.","examples":["http://proxy.example.com:8080"]},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic filter for VPN routing. Captures page as seen from specified region.","examples":["us","us:ca","de","jp"]}},"type":"object","required":["urls"],"title":"ScreenshotJobRequest","description":"Request parameters for browser screenshot capture.\n\nUse screenshot jobs when you need to:\n- Capture visual snapshots of web pages\n- Generate PDFs from websites\n- Archive how pages appear at a point in time"},"cbintel__ScreenshotSyncRequest":{"properties":{"url":{"type":"string","title":"Url","description":"Public URL to capture."},"color_scheme":{"type":"string","enum":["light","dark"],"title":"Color Scheme","default":"light"},"viewport_width":{"type":"integer","maximum":3840.0,"minimum":320.0,"title":"Viewport Width","default":1920},"viewport_height":{"type":"integer","maximum":4320.0,"minimum":240.0,"title":"Viewport Height","default":1080},"full_page":{"type":"boolean","title":"Full Page","default":false},"timeout_ms":{"type":"integer","maximum":60000.0,"minimum":1000.0,"title":"Timeout Ms","default":20000}},"type":"object","required":["url"],"title":"ScreenshotSyncRequest"},"cbintel__ScreenshotSyncResponse":{"properties":{"image_b64":{"type":"string","title":"Image B64"},"mime":{"type":"string","const":"image/png","title":"Mime","default":"image/png"},"width":{"type":"integer","title":"Width"},"height":{"type":"integer","title":"Height"},"url":{"type":"string","title":"Url"},"color_scheme":{"type":"string","title":"Color Scheme"},"duration_ms":{"type":"integer","title":"Duration Ms"}},"type":"object","required":["image_b64","width","height","url","color_scheme","duration_ms"],"title":"ScreenshotSyncResponse"},"cbintel__SearchHitModel":{"properties":{"score":{"type":"number","title":"Score","description":"Cosine similarity (0-1)."},"doc_id":{"type":"string","title":"Doc Id"},"version_id":{"type":"string","title":"Version Id"},"flavor":{"type":"string","title":"Flavor","description":"law | profile | resource | ..."},"title":{"type":"string","title":"Title","default":""},"source_url":{"type":"string","title":"Source Url","default":""},"state":{"type":"string","title":"State","default":""},"county":{"type":"string","title":"County","default":""},"municipality":{"type":"string","title":"Municipality","default":""},"snippet":{"type":"string","title":"Snippet","description":"The matching chunk text.","default":""},"archive_url":{"type":"string","title":"Archive Url","description":"Wayback snapshot, if captured.","default":""},"is_current":{"type":"boolean","title":"Is Current","description":"False if the matched chunk is from a superseded version.","default":true}},"type":"object","required":["score","doc_id","version_id","flavor"],"title":"SearchHitModel","description":"One ranked search result, resolved to its document of record."},"cbintel__SearchRequest":{"properties":{"query":{"type":"string","minLength":1,"title":"Query","description":"Search query text"},"buckets":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Buckets","description":"Buckets to search (default: all indexed)"},"k":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"K","description":"Number of results","default":10},"min_score":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Min Score","description":"Minimum similarity","default":0.0}},"type":"object","required":["query"],"title":"SearchRequest","description":"Vector similarity search request."},"cbintel__SearchResultRow":{"properties":{"document_id":{"type":"string","title":"Document Id","description":"Stable 32-char ID = sha256(state + content_hash)[:32]"},"state":{"type":"string","title":"State","description":"State code (MI, GA, ...)"},"source_url":{"type":"string","title":"Source Url"},"source_tier":{"type":"integer","title":"Source Tier","description":"1=SoS primary, 2=local-election-board, 3=press, 4=aggregator","default":1},"kind":{"type":"string","title":"Kind","description":"Classifier label: rule|regulation|form|contact|deadline|result|candidate|filing|press|other"},"kind_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind Hint","description":"Catalog seed's hint (high-trust when present)"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"confidence":{"type":"number","title":"Confidence","description":"Classifier confidence 0.0-1.0"},"fetched_at":{"type":"string","title":"Fetched At","description":"ISO-8601 UTC"},"classified_at":{"type":"string","title":"Classified At"},"classifier_model":{"type":"string","title":"Classifier Model","description":"ollama model name, e.g. gemma3:4b"},"score":{"type":"number","title":"Score","description":"Cosine similarity 0..1 (higher = more similar to the query)"}},"type":"object","required":["document_id","state","source_url","kind","title","summary","confidence","fetched_at","classified_at","classifier_model","score"],"title":"SearchResultRow","description":"A semantic-search result — DocumentRow + similarity score."},"cbintel__SearchResults":{"properties":{"query":{"type":"string","title":"Query"},"scope":{"type":"string","title":"Scope"},"total":{"type":"integer","title":"Total","default":0},"results":{"items":{"$ref":"#/components/schemas/cbintel__cbintel__web__explorer__SearchResult"},"type":"array","title":"Results","default":[]},"search_time_ms":{"type":"number","title":"Search Time Ms","default":0.0}},"type":"object","required":["query","scope"],"title":"SearchResults","description":"Search results container."},"cbintel__ShipRequest":{"properties":{"audience":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audience"},"shipped_via":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shipped Via"},"force":{"type":"boolean","title":"Force","default":false},"force_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Force Reason"}},"type":"object","title":"ShipRequest"},"cbintel__StationCoverage":{"properties":{"callsign":{"type":"string","title":"Callsign"},"captures":{"type":"integer","title":"Captures"},"ok":{"type":"integer","title":"Ok"},"errors":{"type":"integer","title":"Errors"},"success_rate":{"type":"number","title":"Success Rate"},"egress":{"type":"string","title":"Egress"},"last_seen":{"type":"string","title":"Last Seen"},"last_text":{"type":"string","title":"Last Text"}},"type":"object","required":["callsign","captures","ok","errors","success_rate","egress","last_seen","last_text"],"title":"StationCoverage"},"cbintel__StatsResponse":{"properties":{"by_state_kind":{"items":{"$ref":"#/components/schemas/cbintel__StatsRow"},"type":"array","title":"By State Kind"},"total_documents":{"type":"integer","title":"Total Documents"},"states":{"items":{"type":"string"},"type":"array","title":"States"},"kinds":{"items":{"type":"string"},"type":"array","title":"Kinds"}},"type":"object","required":["by_state_kind","total_documents","states","kinds"],"title":"StatsResponse"},"cbintel__StatsRow":{"properties":{"state":{"type":"string","title":"State"},"kind":{"type":"string","title":"Kind"},"count":{"type":"integer","title":"Count"},"avg_confidence":{"type":"number","title":"Avg Confidence"}},"type":"object","required":["state","kind","count","avg_confidence"],"title":"StatsRow"},"cbintel__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbintel__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbintel__SweepCandidate":{"properties":{"url":{"type":"string","title":"Url"},"label":{"type":"string","title":"Label","default":""},"score":{"type":"integer","title":"Score","default":0},"verdict":{"type":"string","title":"Verdict","default":"not checked"},"contests":{"type":"integer","title":"Contests","default":0},"detected_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detected Date"},"detail":{"type":"string","title":"Detail","default":""}},"type":"object","required":["url"],"title":"SweepCandidate"},"cbintel__SweepCounty":{"properties":{"county":{"type":"string","title":"County"},"status":{"type":"string","title":"Status","description":"INGESTIBLE / IMAGE PDF (no text layer) / BLOCKED HTTP n / none posted yet / ..."},"http":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Http"},"candidates":{"items":{"$ref":"#/components/schemas/cbintel__SweepCandidate"},"type":"array","title":"Candidates"}},"type":"object","required":["county","status"],"title":"SweepCounty"},"cbintel__SweepOut":{"properties":{"path":{"type":"string","title":"Path","description":"Which sweep file this came from"},"recorded_at":{"type":"string","format":"date-time","title":"Recorded At"},"counties":{"items":{"$ref":"#/components/schemas/cbintel__SweepCounty"},"type":"array","title":"Counties"},"ingestible":{"items":{"type":"string"},"type":"array","title":"Ingestible","description":"Counties with a parseable artifact NOW — act on these"},"image_only":{"items":{"type":"string"},"type":"array","title":"Image Only","description":"Counties that HAVE posted, whose artifacts are all image PDFs. Unrecoverable tonight no matter what else they post — there is no OCR step in the elections path. Called out separately because the county-level status for these is the same 'nothing ingestible' as a county that may still post something usable, and those two need opposite responses at 8:30 PM."},"unreached":{"items":{"type":"string"},"type":"array","title":"Unreached","description":"Counties the sweep never actually assessed — the fetch failed, was blocked, or there was no landing url. NOT a verdict about the county: re-run before treating these as unpublished. `ingestible` is only a statement about `len(counties) - len(unreached)`."}},"type":"object","required":["path","recorded_at","counties"],"title":"SweepOut"},"cbintel__TaskType":{"type":"string","enum":["chat","research","code","summarize","embed","ocr","vision","translate","agent"],"title":"TaskType","description":"High-level task types that map to recommended tag sets."},"cbintel__TenantListResponse":{"properties":{"tenants":{"items":{"$ref":"#/components/schemas/cbintel__TenantSummary"},"type":"array","title":"Tenants"}},"type":"object","required":["tenants"],"title":"TenantListResponse","description":"Response for tenant listing."},"cbintel__TenantSummary":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"workspace_count":{"type":"integer","title":"Workspace Count"},"run_count":{"type":"integer","title":"Run Count"},"artifact_count":{"type":"integer","title":"Artifact Count"},"active_job_count":{"type":"integer","title":"Active Job Count"}},"type":"object","required":["tenant_id","workspace_count","run_count","artifact_count","active_job_count"],"title":"TenantSummary","description":"Summary statistics for a tenant."},"cbintel__TickRecord":{"properties":{"tick_id":{"type":"string","title":"Tick Id","description":"Unique tick identifier"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp","description":"When tick occurred"},"schedules_due":{"type":"integer","title":"Schedules Due","description":"Number of schedules that were due","default":0},"jobs_spawned":{"type":"integer","title":"Jobs Spawned","description":"Number of jobs successfully spawned","default":0},"errors":{"items":{"type":"string"},"type":"array","title":"Errors","description":"Any errors encountered"}},"type":"object","required":["tick_id","timestamp"],"title":"TickRecord","description":"Record of a tick execution.\n\nEach minute, the tick process runs and potentially spawns jobs."},"cbintel__TickResponse":{"properties":{"tick_id":{"type":"string","title":"Tick Id"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"schedules_due":{"type":"integer","title":"Schedules Due"},"jobs_spawned":{"type":"integer","title":"Jobs Spawned"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"success":{"type":"boolean","title":"Success"}},"type":"object","required":["tick_id","timestamp","schedules_due","jobs_spawned","errors","success"],"title":"TickResponse","description":"Response from tick endpoint."},"cbintel__TrafficStats":{"properties":{"bytes_sent":{"type":"integer","title":"Bytes Sent","default":0},"bytes_received":{"type":"integer","title":"Bytes Received","default":0},"packets_sent":{"type":"integer","title":"Packets Sent","default":0},"packets_received":{"type":"integer","title":"Packets Received","default":0},"interface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interface"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","title":"TrafficStats","description":"Network traffic statistics from OpenWRT device."},"cbintel__TranscriptJobRequest":{"properties":{"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID to associate this job with. Artifacts are saved under workspace path.","examples":["ws_abc123"]},"url":{"type":"string","maxLength":2000,"minLength":1,"title":"Url","description":"YouTube video URL, ID, shortened URL, or page with embedded video. Supports youtube.com, youtu.be, bit.ly, and pages with embedded players.","examples":["https://www.youtube.com/watch?v=dQw4w9WgXcQ","dQw4w9WgXcQ","https://youtu.be/dQw4w9WgXcQ","https://bit.ly/3xyzabc"]},"content_type":{"anyOf":[{"type":"string","enum":["debate","lecture","tutorial","interview","conversation","general"]},{"type":"null"}],"title":"Content Type","description":"Force content type for structured output. Auto-detected if not specified. 'debate' = speaker arguments, 'lecture' = chapters/concepts, 'tutorial' = steps, 'interview' = Q&A pairs, 'conversation' = highlights, 'general' = summary."},"skip_classification":{"type":"boolean","title":"Skip Classification","description":"Skip AI classification and use keyword-based detection. Faster but less accurate.","default":false}},"type":"object","required":["url"],"title":"TranscriptJobRequest","description":"Request parameters for YouTube transcript processing.\n\nUse transcript jobs when you need to:\n- Summarize video content with structured output\n- Extract key points from lectures or tutorials\n- Generate text versions of video content"},"cbintel__TranscriptRow":{"properties":{"ts":{"type":"string","title":"Ts"},"callsign":{"type":"string","title":"Callsign"},"egress":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Egress"},"status":{"type":"string","title":"Status"},"model_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Used"},"text":{"type":"string","title":"Text"}},"type":"object","required":["ts","callsign","status","text"],"title":"TranscriptRow"},"cbintel__TrendPoint":{"properties":{"t":{"type":"string","title":"T"},"spots":{"type":"integer","title":"Spots","default":0},"total_reach":{"type":"integer","title":"Total Reach","default":0},"top_reach":{"type":"integer","title":"Top Reach","default":0}},"type":"object","required":["t"],"title":"TrendPoint"},"cbintel__TriggerBuildRequest":{"properties":{"force":{"type":"boolean","title":"Force","default":false},"force_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Force Reason"}},"type":"object","title":"TriggerBuildRequest"},"cbintel__TurnResult":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Job ID from execution"},"status":{"type":"string","title":"Status","description":"Execution status","default":"pending"},"facts_discovered":{"type":"integer","title":"Facts Discovered","default":0},"summary":{"type":"string","title":"Summary","default":""},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["job_id"],"title":"TurnResult","description":"Result from executing a research turn."},"cbintel__UnenrollResponse":{"properties":{"account":{"type":"string","title":"Account"},"was_enrolled":{"type":"boolean","title":"Was Enrolled"},"note":{"type":"string","title":"Note"}},"type":"object","required":["account","was_enrolled","note"],"title":"UnenrollResponse"},"cbintel__UsageRecord":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Unique request identifier (UUID)"},"operation":{"$ref":"#/components/schemas/cbintel__OperationType"},"endpoint":{"type":"string","title":"Endpoint"},"method":{"type":"string","title":"Method","default":"GET"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"api_key_prefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Prefix"},"source_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Ip"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"latency_ms":{"type":"number","title":"Latency Ms","default":0.0},"request_params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Request Params"},"response_status":{"type":"integer","title":"Response Status","default":200},"success":{"type":"boolean","title":"Success","default":true},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"vpn_info":{"anyOf":[{"$ref":"#/components/schemas/cbintel__VPNConnectionInfo"},{"type":"null"}]},"traffic":{"anyOf":[{"$ref":"#/components/schemas/cbintel__TrafficStats"},{"type":"null"}]},"tokens_used":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tokens Used"},"bytes_processed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bytes Processed"}},"type":"object","required":["request_id","operation","endpoint"],"title":"UsageRecord","description":"Individual API usage record.\n\nTracks each API request with operation type, timing, user info,\nand optional VPN/traffic context."},"cbintel__UsageResponse":{"properties":{"records":{"items":{"$ref":"#/components/schemas/cbintel__UsageRecord"},"type":"array","title":"Records"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page","default":1},"page_size":{"type":"integer","title":"Page Size","default":50},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["records","total"],"title":"UsageResponse","description":"Paginated usage records response."},"cbintel__UsageSummary":{"properties":{"period_start":{"type":"string","format":"date-time","title":"Period Start"},"period_end":{"type":"string","format":"date-time","title":"Period End"},"period_type":{"type":"string","title":"Period Type","default":"day"},"total_requests":{"type":"integer","title":"Total Requests","default":0},"successful_requests":{"type":"integer","title":"Successful Requests","default":0},"failed_requests":{"type":"integer","title":"Failed Requests","default":0},"success_rate":{"type":"number","title":"Success Rate","default":0.0},"avg_latency_ms":{"type":"number","title":"Avg Latency Ms","default":0.0},"min_latency_ms":{"type":"number","title":"Min Latency Ms","default":0.0},"max_latency_ms":{"type":"number","title":"Max Latency Ms","default":0.0},"p95_latency_ms":{"type":"number","title":"P95 Latency Ms","default":0.0},"operations":{"additionalProperties":{"type":"integer"},"type":"object","title":"Operations"},"unique_users":{"type":"integer","title":"Unique Users","default":0},"user_breakdown":{"additionalProperties":{"type":"integer"},"type":"object","title":"User Breakdown"},"vpn_requests":{"type":"integer","title":"Vpn Requests","default":0},"vpn_countries":{"additionalProperties":{"type":"integer"},"type":"object","title":"Vpn Countries"},"active_vpn_connections":{"type":"integer","title":"Active Vpn Connections","default":0},"total_bytes_sent":{"type":"integer","title":"Total Bytes Sent","default":0},"total_bytes_received":{"type":"integer","title":"Total Bytes Received","default":0}},"type":"object","required":["period_start","period_end"],"title":"UsageSummary","description":"Aggregated usage statistics."},"cbintel__UserInfo":{"properties":{"user_id":{"type":"string","title":"User Id"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"api_key_prefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Prefix"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_seen":{"type":"string","format":"date-time","title":"Last Seen"},"total_requests":{"type":"integer","title":"Total Requests","default":0},"total_bytes_transferred":{"type":"integer","title":"Total Bytes Transferred","default":0},"active_vpn_connections":{"type":"integer","title":"Active Vpn Connections","default":0}},"type":"object","required":["user_id"],"title":"UserInfo","description":"API user information for tracking and reporting."},"cbintel__VPNConnectionInfo":{"properties":{"connected":{"type":"boolean","title":"Connected","default":false},"worker_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Worker Id"},"bank_tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bank Tag"},"vpn_profile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Profile"},"exit_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Ip"},"exit_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Country"},"exit_region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Region"},"exit_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit City"},"vpn_interface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Interface"},"connected_since":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Connected Since"}},"type":"object","title":"VPNConnectionInfo","description":"VPN connection status at time of request."},"cbintel__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbintel__VectlJobRequest":{"properties":{"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID to associate this job with. Artifacts are saved under workspace path.","examples":["ws_abc123"]},"operation":{"type":"string","enum":["embed","search","batch_embed","index"],"title":"Operation","description":"Vector operation type. 'embed' = single text, 'batch_embed' = multiple texts, 'search' = find similar, 'index' = build searchable index."},"texts":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Texts","description":"Texts to embed. Required for 'embed' and 'batch_embed' operations.","examples":[["This is a sample document about AI"],["Document 1","Document 2","Document 3"]]},"query":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Query","description":"Search query text. Required for 'search' operation.","examples":["artificial intelligence regulations","how to deploy kubernetes"]},"source_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Path","description":"Directory path containing documents to index. Required for 'index' operation.","examples":["/data/documents","/home/user/corpus"]},"store_name":{"type":"string","maxLength":100,"minLength":1,"title":"Store Name","description":"Vector store name for organizing embeddings. Use separate stores for different projects.","default":"default","examples":["default","research_2024","competitor_analysis"]},"k":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"K","description":"Number of results to return for search operation.","default":10,"examples":[5,10,25]},"geo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo","description":"Geographic filter for VPN routing. Routes Ollama API calls through specified region.","examples":["us","de"]}},"type":"object","required":["operation"],"title":"VectlJobRequest","description":"Request parameters for vector embedding and similarity search.\n\nUse vectl jobs when you need to:\n- Generate embeddings for semantic search\n- Find similar documents by meaning\n- Index documents for later retrieval"},"cbintel__Watch":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"term":{"type":"string","title":"Term"},"hours":{"type":"integer","title":"Hours","default":6},"created_at":{"type":"string","title":"Created At"},"trend":{"items":{"$ref":"#/components/schemas/cbintel__TrendPoint"},"type":"array","title":"Trend","default":[]},"alert":{"anyOf":[{"$ref":"#/components/schemas/cbintel__AlertInfo"},{"type":"null"}]}},"type":"object","required":["id","name","term","created_at"],"title":"Watch"},"cbintel__WatchCreate":{"properties":{"name":{"type":"string","title":"Name"},"term":{"type":"string","title":"Term"},"hours":{"type":"integer","title":"Hours","default":6}},"type":"object","required":["name","term"],"title":"WatchCreate"},"cbintel__WorkspaceDetail":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description","default":""},"status":{"$ref":"#/components/schemas/cbintel__WorkspaceStatus"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"run_count":{"type":"integer","title":"Run Count","default":0},"artifact_count":{"type":"integer","title":"Artifact Count","default":0},"active_job_count":{"type":"integer","title":"Active Job Count","default":0},"index_enabled":{"type":"boolean","title":"Index Enabled","default":false},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","default":[]},"layout_profile":{"$ref":"#/components/schemas/cbintel__LayoutProfile","default":"mixed"},"signature":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Signature"},"address":{"type":"string","title":"Address","default":""}},"type":"object","required":["workspace_id","name","status","created_at"],"title":"WorkspaceDetail","description":"Detailed workspace information for the explorer view."},"cbintel__WorkspaceResponse":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id","description":"Unique workspace identifier"},"name":{"type":"string","title":"Name","description":"Human-readable workspace name"},"description":{"type":"string","title":"Description","description":"Workspace description"},"status":{"type":"string","title":"Status","description":"Workspace status: active, archived"},"created_at":{"type":"string","title":"Created At","description":"ISO 8601 creation timestamp"},"job_count":{"type":"integer","title":"Job Count","description":"Number of jobs associated with this workspace"},"artifact_count":{"type":"integer","title":"Artifact Count","description":"Number of artifacts stored in this workspace"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"User-defined tags for filtering"},"index_enabled":{"type":"boolean","title":"Index Enabled","description":"Whether document indexing is enabled"}},"type":"object","required":["workspace_id","name","description","status","created_at","job_count","artifact_count","tags","index_enabled"],"title":"WorkspaceResponse","description":"Workspace details response."},"cbintel__WorkspaceStatus":{"type":"string","enum":["active","archived","deleted"],"title":"WorkspaceStatus","description":"Status of a workspace."},"cbintel__WorkspaceSummary":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"name":{"type":"string","title":"Name"},"status":{"$ref":"#/components/schemas/cbintel__WorkspaceStatus"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"job_count":{"type":"integer","title":"Job Count","default":0},"artifact_count":{"type":"integer","title":"Artifact Count","default":0},"index_enabled":{"type":"boolean","title":"Index Enabled","default":false},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["workspace_id","name","status","created_at"],"title":"WorkspaceSummary","description":"Summary view of a workspace for listings."},"cbintel__cbintel__corpus__api__SearchResponse":{"properties":{"query":{"type":"string","title":"Query"},"catalog":{"type":"string","title":"Catalog"},"flavor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flavor"},"total":{"type":"integer","title":"Total"},"hits":{"items":{"$ref":"#/components/schemas/cbintel__SearchHitModel"},"type":"array","title":"Hits"}},"type":"object","required":["query","catalog","total","hits"],"title":"SearchResponse","description":"Search results envelope."},"cbintel__cbintel__jobs__models__PipelineRequest":{"properties":{"steps":{"items":{"$ref":"#/components/schemas/cbintel__PipelineStep"},"type":"array","maxItems":10,"minItems":1,"title":"Steps","description":"Ordered list of pipeline steps (1-10)"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id","description":"Workspace ID to associate all pipeline jobs with"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url","description":"URL to POST pipeline status to on completion or failure"}},"type":"object","required":["steps"],"title":"PipelineRequest","description":"Request to create a multi-step job pipeline."},"cbintel__cbintel__jobs__routers__documents__PipelineRequest":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"URL to fetch document from (e.g., https://files.campaignbrain.dev/docs/report.pdf)"},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path","description":"Local file path to process"},"extract_images":{"type":"boolean","title":"Extract Images","default":true},"extract_entities":{"type":"boolean","title":"Extract Entities","default":true},"extract_summary":{"type":"boolean","title":"Extract Summary","default":true},"extract_topics":{"type":"boolean","title":"Extract Topics","default":true},"entity_types":{"items":{"type":"string"},"type":"array","title":"Entity Types","default":["person","organization","location","event","topic"]},"max_summary_length":{"type":"integer","maximum":500.0,"minimum":50.0,"title":"Max Summary Length","default":200},"max_topics":{"type":"integer","maximum":20.0,"minimum":3.0,"title":"Max Topics","default":10},"ocr_provider":{"anyOf":[{"type":"string","enum":["mistral","unstructured"]},{"type":"null"}],"title":"Ocr Provider"}},"type":"object","title":"PipelineRequest","description":"Request for full document processing pipeline.\n\nRuns detect → process → analyze in sequence."},"cbintel__cbintel__jobs__routers__explorer__WorkspaceListResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"workspaces":{"items":{"$ref":"#/components/schemas/cbintel__WorkspaceSummary"},"type":"array","title":"Workspaces"}},"type":"object","required":["tenant_id","workspaces"],"title":"WorkspaceListResponse","description":"Response for workspace listing within a tenant."},"cbintel__cbintel__jobs__routers__vectl__SearchResponse":{"properties":{"query":{"type":"string","title":"Query"},"results":{"items":{"$ref":"#/components/schemas/cbintel__cbintel__jobs__routers__vectl__SearchResult"},"type":"array","title":"Results"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["query","results","total"],"title":"SearchResponse","description":"Paginated vector search response."},"cbintel__cbintel__jobs__routers__vectl__SearchResult":{"properties":{"bucket":{"type":"string","title":"Bucket"},"object_name":{"type":"string","title":"Object Name"},"score":{"type":"number","title":"Score"},"snippet":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snippet"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","required":["bucket","object_name","score"],"title":"SearchResult","description":"Single vector search match."},"cbintel__cbintel__jobs__routers__workspaces__WorkspaceListResponse":{"properties":{"workspaces":{"items":{"$ref":"#/components/schemas/cbintel__WorkspaceResponse"},"type":"array","title":"Workspaces","description":"List of workspace summaries"},"total":{"type":"integer","title":"Total","description":"Total number of workspaces returned"}},"type":"object","required":["workspaces","total"],"title":"WorkspaceListResponse","description":"List of workspaces."},"cbintel__cbintel__sos__api__SearchResponse":{"properties":{"query":{"type":"string","title":"Query"},"states_searched":{"items":{"type":"string"},"type":"array","title":"States Searched"},"limit":{"type":"integer","title":"Limit"},"items":{"items":{"$ref":"#/components/schemas/cbintel__SearchResultRow"},"type":"array","title":"Items"}},"type":"object","required":["query","states_searched","limit","items"],"title":"SearchResponse"},"cbintel__cbintel__web__explorer__SearchResult":{"properties":{"address":{"type":"string","title":"Address"},"title":{"type":"string","title":"Title"},"snippet":{"type":"string","title":"Snippet","default":""},"score":{"type":"number","title":"Score","default":0.0},"result_type":{"type":"string","title":"Result Type"},"artifact_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Artifact Type"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path"},"url":{"type":"string","title":"Url","default":""},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["address","title","result_type"],"title":"SearchResult","description":"Search result item."},"cbissues__Author":{"properties":{"login":{"type":"string","title":"Login","description":"GitHub username (login handle)","examples":["bisenbek"]},"id":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Id","description":"GitHub user ID (integer) or GraphQL node ID (string)","examples":["MDQ6VXNlcjEyMzQ1",12345]},"avatarUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatarurl","description":"URL of the user's avatar image","examples":["https://avatars.githubusercontent.com/u/12345"]}},"additionalProperties":true,"type":"object","required":["login"],"title":"Author","description":"A GitHub user who authored or is assigned to content.\n\nWraps the raw ``gh`` user object; ``extra=\"allow\"`` lets through any\nadditional fields GitHub returns (e.g. ``name``, ``url``)."},"cbissues__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbissues__ErrorResponse":{"properties":{"error":{"type":"string","title":"Error","description":"Short error category","examples":["Repository access denied"]},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail","description":"Detailed error message","examples":["Repo 'octocat/hello' is not in the allowed set"]},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code","description":"Machine-readable error code","examples":["VALIDATION_ERROR","INTERNAL_ERROR"]}},"type":"object","required":["error"],"title":"ErrorResponse","description":"Standard error envelope returned for 4xx/5xx responses.","examples":[{"code":"FORBIDDEN","detail":"Repo 'octocat/hello' is not in the allowed set","error":"Repository access denied"}]},"cbissues__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbissues__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbissues__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbissues__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbissues__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbissues__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbissues__IssueClose":{"properties":{"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment","description":"Optional closing comment","examples":["Fixed in #128 — closing."]},"reason":{"anyOf":[{"type":"string","pattern":"^(completed|not_planned)$"},{"type":"null"}],"title":"Reason","description":"Close reason: 'completed' (resolved) or 'not_planned' (won't-fix/duplicate)","examples":["completed","not_planned"]}},"type":"object","title":"IssueClose","description":"Request body for closing an issue (all fields optional).\n\n``reason`` must be either ``completed`` (the work is done) or ``not_planned``\n(won't-fix / duplicate / out of scope), matching GitHub's close reasons.","examples":[{"comment":"Resolved in #128.","reason":"completed"}]},"cbissues__IssueComment":{"properties":{"body":{"type":"string","minLength":1,"title":"Body","description":"Comment body (Markdown supported)","examples":["Confirmed on staging — escalating to priority:high."]}},"type":"object","required":["body"],"title":"IssueComment","description":"Request body for adding a comment to an issue.","examples":[{"body":"Reproduced on main @ 1a2b3c4. Patch incoming."}]},"cbissues__IssueCreate":{"properties":{"repo":{"type":"string","title":"Repo","description":"Repository in owner/name format (e.g., 'Nominate-AI/cbinfra')","examples":["Nominate-AI/cbinfra","tinymachines/example"]},"title":{"type":"string","maxLength":256,"minLength":1,"title":"Title","description":"Issue title","examples":["Login page returns 500 on empty password"]},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Issue body (Markdown supported)","examples":["Steps to reproduce:\n1. Open /login\n2. Submit blank form"]},"labels":{"items":{"type":"string"},"type":"array","title":"Labels","description":"Labels to apply to the issue","examples":[["bug","priority:high"]]},"assignees":{"items":{"type":"string"},"type":"array","title":"Assignees","description":"GitHub usernames to assign","examples":[["bisenbek"]]},"milestone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Milestone","description":"Milestone to associate with the issue","examples":["v1.0"]}},"type":"object","required":["repo","title"],"title":"IssueCreate","description":"Request body for creating a new GitHub issue.\n\nThe target ``repo`` must be within the allowed set (``Nominate-AI/cb*`` or\n``tinymachines/*``); otherwise the API returns 403. Only ``repo`` and\n``title`` are required.","examples":[{"assignees":["bisenbek"],"body":"We should surface GitHub's rate-limit budget.","labels":["enhancement"],"repo":"Nominate-AI/cbissues","title":"Add rate-limit headers to issue list endpoint"}]},"cbissues__IssueListResponse":{"properties":{"issues":{"items":{"$ref":"#/components/schemas/cbissues__IssueResponse"},"type":"array","title":"Issues","description":"The issues matching the query"},"total":{"type":"integer","title":"Total","description":"Number of issues returned in this response","examples":[30]}},"type":"object","required":["issues","total"],"title":"IssueListResponse","description":"A page of issues plus the count returned."},"cbissues__IssueReopen":{"properties":{"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment","description":"Optional reopening comment","examples":["Regression observed in v1.2 — reopening."]}},"type":"object","title":"IssueReopen","description":"Request body for reopening a closed issue (comment optional).","examples":[{"comment":"Still reproducible after the patch."}]},"cbissues__IssueResponse":{"properties":{"number":{"type":"integer","title":"Number","description":"Issue number within the repository","examples":[128]},"title":{"type":"string","title":"Title","description":"Issue title","examples":["Login page returns 500 on empty password"]},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Issue body (Markdown)","examples":["Steps to reproduce: ..."]},"state":{"type":"string","title":"State","description":"Issue state ('open' or 'closed')","examples":["open"]},"url":{"type":"string","title":"Url","description":"Canonical GitHub URL for the issue","examples":["https://github.com/Nominate-AI/cbissues/issues/128"]},"author":{"anyOf":[{"$ref":"#/components/schemas/cbissues__Author"},{"type":"null"}],"description":"The user who opened the issue"},"assignees":{"items":{"$ref":"#/components/schemas/cbissues__Author"},"type":"array","title":"Assignees","description":"Users currently assigned to the issue"},"labels":{"items":{"$ref":"#/components/schemas/cbissues__Label"},"type":"array","title":"Labels","description":"Labels applied to the issue"},"milestone":{"anyOf":[{"$ref":"#/components/schemas/cbissues__Milestone"},{"type":"null"}],"description":"Milestone the issue belongs to, if any"},"comments":{"anyOf":[{"type":"integer"},{"items":{},"type":"array"}],"title":"Comments","description":"Comment count (integer) in list views, or the full list of comment objects when include_comments=true","default":0,"examples":[3]},"closed":{"type":"boolean","title":"Closed","description":"Whether the issue is closed","default":false},"closedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Closedat","description":"Timestamp the issue was closed (ISO 8601), if closed"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat","description":"Timestamp the issue was created (ISO 8601)"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat","description":"Timestamp the issue was last updated (ISO 8601)"},"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"GraphQL node ID of the issue","examples":["I_kwDOABCD1234"]}},"additionalProperties":true,"type":"object","required":["number","title","state","url"],"title":"IssueResponse","description":"A single GitHub issue as returned by cbissues.\n\nMirrors the ``gh issue view`` JSON shape. ``extra=\"allow\"`` preserves any\nextra fields GitHub includes. Note ``comments`` may be either an integer\ncount (list view) or the full list of comment objects (when\n``include_comments=true`` on the get-issue endpoint).","examples":[{"closed":false,"comments":3,"labels":[{"color":"d73a4a","name":"bug"}],"number":128,"state":"open","title":"Login page returns 500 on empty password","url":"https://github.com/Nominate-AI/cbissues/issues/128"}]},"cbissues__IssueUpdate":{"properties":{"title":{"anyOf":[{"type":"string","maxLength":256,"minLength":1},{"type":"null"}],"title":"Title","description":"New issue title","examples":["Fix login 500 on empty password"]},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"New issue body (Markdown supported)","examples":["Updated repro steps and stack trace attached."]},"add_labels":{"items":{"type":"string"},"type":"array","title":"Add Labels","description":"Labels to add to the issue","examples":[["needs-triage"]]},"remove_labels":{"items":{"type":"string"},"type":"array","title":"Remove Labels","description":"Labels to remove from the issue","examples":[["wontfix"]]},"add_assignees":{"items":{"type":"string"},"type":"array","title":"Add Assignees","description":"GitHub usernames to add as assignees","examples":[["bisenbek"]]},"remove_assignees":{"items":{"type":"string"},"type":"array","title":"Remove Assignees","description":"GitHub usernames to remove from assignees","examples":[["former-maintainer"]]},"milestone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Milestone","description":"Milestone to set (or None to keep current)","examples":["v1.1"]},"remove_milestone":{"type":"boolean","title":"Remove Milestone","description":"Remove milestone from issue","default":false}},"type":"object","title":"IssueUpdate","description":"Request body for updating an existing issue.\n\nAll fields are optional — only the supplied changes are applied. For labels\nand assignees, use the additive/subtractive ``add_*`` / ``remove_*`` fields\nrather than replacing the full set. Set ``remove_milestone=True`` to clear an\nexisting milestone.","examples":[{"add_labels":["confirmed"],"remove_labels":["needs-triage"],"title":"Fix login 500 on empty password"}]},"cbissues__Label":{"properties":{"name":{"type":"string","title":"Name","description":"Label name","examples":["bug","enhancement"]},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color","description":"Label color as a 6-digit hex string (no leading #)","examples":["d73a4a"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Human-readable description of the label","examples":["Something isn't working"]}},"type":"object","required":["name"],"title":"Label","description":"A GitHub issue label."},"cbissues__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbissues__Milestone":{"properties":{"title":{"type":"string","title":"Title","description":"Milestone title","examples":["v1.0"]},"number":{"type":"integer","title":"Number","description":"Milestone number within the repository","examples":[1]},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"Milestone state ('open' or 'closed')","examples":["open"]},"dueOn":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Dueon","description":"Milestone due date (ISO 8601), if set","examples":["2026-07-01T00:00:00Z"]}},"type":"object","required":["title","number"],"title":"Milestone","description":"A GitHub milestone attached to an issue."},"cbissues__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbissues__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbissues__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbissues__ProjectAssignment":{"properties":{"project_numbers":{"items":{"type":"integer"},"type":"array","minItems":1,"title":"Project Numbers","description":"GitHub Project numbers to add the issue to","examples":[[3,7]]},"owner":{"type":"string","title":"Owner","description":"Project owner (organization or user)","default":"Nominate-AI","examples":["Nominate-AI"]}},"type":"object","required":["project_numbers"],"title":"ProjectAssignment","description":"Request body for adding an issue to one or more GitHub Projects.","examples":[{"owner":"Nominate-AI","project_numbers":[3]}]},"cbissues__ProjectItemResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Project item ID (used to remove the item from the board)","examples":["PVTI_lADOABCD1234"]},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Title of the linked issue/PR/draft","examples":["Login page returns 500 on empty password"]},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Item status column (e.g. 'Todo', 'In Progress', 'Done')","examples":["In Progress"]}},"additionalProperties":true,"type":"object","required":["id"],"title":"ProjectItemResponse","description":"A single item (card) within a GitHub Project board."},"cbissues__ProjectListResponse":{"properties":{"projects":{"items":{"$ref":"#/components/schemas/cbissues__ProjectResponse"},"type":"array","title":"Projects","description":"GitHub Projects for the requested owner"},"total":{"type":"integer","title":"Total","description":"Number of projects returned","examples":[5]}},"type":"object","required":["projects","total"],"title":"ProjectListResponse","description":"The set of projects for an owner plus the count returned."},"cbissues__ProjectResponse":{"properties":{"number":{"type":"integer","title":"Number","description":"Project number within the owning org/user","examples":[3]},"title":{"type":"string","title":"Title","description":"Project title","examples":["Q3 Roadmap"]},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Project board URL","examples":["https://github.com/orgs/Nominate-AI/projects/3"]},"closed":{"type":"boolean","title":"Closed","description":"Whether the project is closed/archived","default":false}},"additionalProperties":true,"type":"object","required":["number","title"],"title":"ProjectResponse","description":"A GitHub Project (Projects v2 board)."},"cbissues__RepoListResponse":{"properties":{"repos":{"items":{"$ref":"#/components/schemas/cbissues__RepoResponse"},"type":"array","title":"Repos","description":"Allowed repositories"},"total":{"type":"integer","title":"Total","description":"Number of repositories returned","examples":[12]}},"type":"object","required":["repos","total"],"title":"RepoListResponse","description":"The set of allowed repositories plus the count returned."},"cbissues__RepoResponse":{"properties":{"repo":{"type":"string","title":"Repo","description":"Repository in owner/name format","examples":["Nominate-AI/cbissues"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Repository description","examples":["Issue tracking dashboard for Campaign Brain"]},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Repository URL","examples":["https://github.com/Nominate-AI/cbissues"]},"private":{"type":"boolean","title":"Private","description":"Whether the repository is private","default":false}},"type":"object","required":["repo"],"title":"RepoResponse","description":"A repository accessible through this API."},"cbissues__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbissues__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbissues__SuccessResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always true on success","default":true},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Human-readable description of what happened","examples":["Issue #128 deleted"]}},"type":"object","title":"SuccessResponse","description":"Generic success envelope returned by mutating operations.","examples":[{"message":"Issue #128 deleted","success":true}]},"cbissues__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbiterable__AllowRule":{"properties":{"methods":{"items":{"type":"string"},"type":"array","title":"Methods","description":"HTTP methods this rule allows (upper-case).","examples":[["GET"],["GET","POST"]]},"pattern":{"type":"string","title":"Pattern","description":"fnmatch-style glob matched against the Iterable API path.","examples":["/api/lists","/api/campaigns/*"]},"note":{"type":"string","title":"Note","description":"Why this rule exists (who asked, when) — the audit trail for scope decisions.","default":"","examples":["vendor asked 2026-07-08"]}},"type":"object","required":["pattern"],"title":"AllowRule","description":"One allowlist entry: which methods + path pattern a consumer may call.\n\nPatterns are ``fnmatch``-style globs matched against the Iterable API\npath (e.g. ``/api/campaigns/*`` matches ``/api/campaigns/metrics``).\nMethods are exact, upper-case. Default-deny: a request matching no rule\nis refused.","example":{"methods":["GET"],"note":"vendor asked 2026-07-08","pattern":"/api/campaigns/*"}},"cbiterable__AuditPage":{"properties":{"entries":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Entries","description":"Audit rows: ts, consumer_id, method, path, query, mode (mirror|live|denied|blocked|unauthorized|disabled|shed), status, bytes_out, duration_ms."},"count":{"type":"integer","title":"Count","description":"Number of rows in this page."}},"type":"object","required":["entries","count"],"title":"AuditPage","description":"One page of gateway audit rows, newest first.","example":{"count":1,"entries":[{"bytes_out":20480,"consumer_id":"consumer-1a2b3c4d","duration_ms":4.2,"id":42,"method":"GET","mode":"mirror","path":"/api/lists","status":200,"ts":"2026-07-08T12:00:00+00:00"}]}},"cbiterable__BackoffSnapshot":{"properties":{"level":{"type":"integer","title":"Level","description":"Consecutive-penalty level; 0 means healthy.","examples":[0]},"cooling_for_s":{"type":"number","title":"Cooling For S","description":"Seconds until the queue moves again; 0 when not cooling.","examples":[0.0]},"triggered_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Triggered By","description":"What opened the current/most recent cooldown (e.g. 'HTTP 429').","examples":["HTTP 429"]},"clean_streak":{"type":"integer","title":"Clean Streak","description":"Consecutive clean responses counted toward the next decay.","examples":[2]}},"type":"object","required":["level","cooling_for_s","clean_streak"],"title":"BackoffSnapshot","description":"Current global backoff state (shared by every lane)."},"cbiterable__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbiterable__ConsumerCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Human-readable vendor name.","examples":["AcmeVendor"]},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant (Iterable project) to scope this consumer to.","examples":["tenant-1a2b3c4d"]},"live_rps":{"type":"number","maximum":50.0,"exclusiveMinimum":0.0,"title":"Live Rps","description":"Per-consumer live-call rate limit (requests/second).","default":2.0,"examples":[2.0]},"rules":{"items":{"$ref":"#/components/schemas/cbiterable__AllowRule"},"type":"array","title":"Rules","description":"Initial allowlist rules (default: none — nothing reachable)."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"}},"type":"object","required":["name","tenant_id"],"title":"ConsumerCreate","description":"Request model for creating a gateway consumer.","example":{"live_rps":2.0,"name":"AcmeVendor","rules":[],"tenant_id":"tenant-1a2b3c4d"}},"cbiterable__ConsumerCreated":{"properties":{"consumer":{"$ref":"#/components/schemas/cbiterable__ConsumerPublic","description":"The created/updated consumer."},"token":{"type":"string","title":"Token","description":"The plaintext gateway token. Shown exactly once — store it now. If lost, rotate the consumer's token.","examples":["cbit_AbC123..."]}},"type":"object","required":["consumer","token"],"title":"ConsumerCreated","description":"Response for consumer create/rotate — carries the plaintext token ONCE.\n\nThe token is not stored and cannot be recovered; if lost, rotate.","example":{"consumer":{"created_at":"2026-07-08T12:00:00Z","enabled":true,"id":"consumer-1a2b3c4d","live_rps":2.0,"metadata":{},"name":"AcmeVendor","rules":[],"tenant_id":"tenant-1a2b3c4d","updated_at":"2026-07-08T12:00:00Z"},"token":"cbit_AbC123..."}},"cbiterable__ConsumerPublic":{"properties":{"id":{"type":"string","title":"Id","description":"Unique consumer identifier."},"name":{"type":"string","title":"Name","description":"Human-readable vendor name."},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant this consumer is scoped to."},"enabled":{"type":"boolean","title":"Enabled","description":"Kill-switch state."},"live_rps":{"type":"number","title":"Live Rps","description":"Per-consumer live-call rate limit."},"rules":{"items":{"$ref":"#/components/schemas/cbiterable__AllowRule"},"type":"array","title":"Rules","description":"Current allowlist rules."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp (UTC)."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp (UTC)."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Arbitrary metadata."}},"type":"object","required":["id","name","tenant_id","enabled","live_rps","rules","created_at","updated_at","metadata"],"title":"ConsumerPublic","description":"Consumer as returned by the admin API — token hash redacted.","example":{"created_at":"2026-07-08T12:00:00Z","enabled":true,"id":"consumer-1a2b3c4d","live_rps":2.0,"metadata":{},"name":"AcmeVendor","rules":[{"methods":["GET"],"pattern":"/api/lists"}],"tenant_id":"tenant-1a2b3c4d","updated_at":"2026-07-08T12:00:00Z"}},"cbiterable__ConsumerUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"New human-readable vendor name.","examples":["AcmeVendor"]},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled","description":"Kill switch — set false to cut the consumer off instantly."},"live_rps":{"anyOf":[{"type":"number","maximum":50.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Live Rps","description":"New per-consumer live-call rate limit (requests/second).","examples":[2.0]},"rules":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbiterable__AllowRule"},"type":"array"},{"type":"null"}],"title":"Rules","description":"Replacement allowlist (full list, not a merge)."},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Arbitrary metadata to store on the consumer."}},"type":"object","title":"ConsumerUpdate","description":"Request model for updating a gateway consumer (PATCH semantics).\n\nOnly provided fields change; omitted fields keep their existing values.\nToken rotation has its own endpoint and is not done here.","example":{"enabled":false}},"cbiterable__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbiterable__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbiterable__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbiterable__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbiterable__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbiterable__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbiterable__ListCreateRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Name for the new list","examples":["Newsletter","VIP"]}},"type":"object","required":["name"],"title":"ListCreateRequest","description":"Request to create a list in Iterable.","example":{"name":"Newsletter"}},"cbiterable__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbiterable__OrgGateConfig":{"properties":{"rate":{"type":"number","exclusiveMinimum":0.0,"title":"Rate","description":"Org-wide upstream budget in requests/second (burst == rate).","examples":[100.0]},"backoff_base":{"type":"number","exclusiveMinimum":0.0,"title":"Backoff Base","description":"Cooldown seconds after the first 429/5xx; doubles per level.","default":1.0,"examples":[1.0]},"backoff_cap":{"type":"number","exclusiveMinimum":0.0,"title":"Backoff Cap","description":"Maximum cooldown seconds regardless of backoff level.","default":60.0,"examples":[60.0]},"backoff_multiplier":{"type":"number","exclusiveMinimum":1.0,"title":"Backoff Multiplier","description":"Growth factor applied per consecutive backoff level.","default":2.0,"examples":[2.0]},"decay_after":{"type":"integer","minimum":1.0,"title":"Decay After","description":"Clean (2xx/3xx) responses required to shed one backoff level.","default":3,"examples":[3]}},"type":"object","required":["rate"],"title":"OrgGateConfig","description":"Tunables for the org gate (rate, backoff curve, vendor shedding).","example":{"backoff_base":1.0,"backoff_cap":60.0,"backoff_multiplier":2.0,"decay_after":3,"rate":100.0}},"cbiterable__OrgGateStatus":{"properties":{"config":{"$ref":"#/components/schemas/cbiterable__OrgGateConfig","description":"Active gate tunables."},"backoff":{"$ref":"#/components/schemas/cbiterable__BackoffSnapshot","description":"Global backoff state."},"waiting":{"additionalProperties":{"type":"integer"},"type":"object","title":"Waiting","description":"Callers currently queued, per lane (pipeline/internal/vendor).","examples":[{"internal":0,"pipeline":0,"vendor":2}]},"served":{"additionalProperties":{"type":"integer"},"type":"object","title":"Served","description":"Slots granted since process start, per lane.","examples":[{"internal":3,"pipeline":1200,"vendor":88}]},"shed":{"type":"integer","title":"Shed","description":"Vendor acquires abandoned at max_wait (answered 429 + Retry-After).","examples":[4]},"backoffs":{"type":"integer","title":"Backoffs","description":"Cooldown windows opened since process start (429/5xx reports).","examples":[1]}},"type":"object","required":["config","backoff","waiting","served","shed","backoffs"],"title":"OrgGateStatus","description":"Snapshot of the org gate for the admin surface.\n\nRead this to answer \"why is upstream slow\": a non-zero ``backoff.level``\nwith waiters queued means Iterable is pushing back and the gate is\npacing everyone; a large ``vendor`` wait count with zero backoff means\nthe vendor is simply out-running the org budget.","example":{"backoff":{"clean_streak":0,"cooling_for_s":1.7,"level":1,"triggered_by":"HTTP 429"},"backoffs":1,"config":{"backoff_base":1.0,"backoff_cap":60.0,"backoff_multiplier":2.0,"decay_after":3,"rate":100.0},"served":{"internal":3,"pipeline":1200,"vendor":88},"shed":4,"waiting":{"internal":0,"pipeline":1,"vendor":3}}},"cbiterable__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbiterable__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbiterable__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbiterable__QueryRequest":{"properties":{"artifact_type":{"type":"string","title":"Artifact Type","description":"Artifact group to query. One of: lists, campaigns, templates, channels, message_types","examples":["lists","campaigns"]},"sql":{"type":"string","title":"Sql","description":"SQL SELECT query to execute against the artifact's DuckDB store","examples":["SELECT * FROM lists","SELECT name, campaign_state FROM campaigns WHERE type = 'Blast'"]},"limit":{"type":"integer","maximum":10000.0,"minimum":1.0,"title":"Limit","description":"Maximum number of rows to return","default":1000}},"type":"object","required":["artifact_type","sql"],"title":"QueryRequest","description":"Request body for the ad-hoc query endpoint.","example":{"artifact_type":"campaigns","limit":100,"sql":"SELECT name, campaign_state, type FROM campaigns"}},"cbiterable__QueryResponse":{"properties":{"columns":{"items":{"type":"string"},"type":"array","title":"Columns","description":"Ordered column names of the result set.","examples":[["id","name","campaign_state"]]},"rows":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Rows","description":"Result rows, each a column-name → value mapping.","examples":[[{"campaign_state":"Running","id":1,"name":"Newsletter"}]]},"row_count":{"type":"integer","title":"Row Count","description":"Number of rows returned (after the limit is applied).","examples":[1]}},"type":"object","required":["columns","rows","row_count"],"title":"QueryResponse","description":"Result set returned by the ad-hoc query endpoint.","example":{"columns":["id","name","campaign_state"],"row_count":1,"rows":[{"campaign_state":"Running","id":1,"name":"Newsletter"}]}},"cbiterable__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbiterable__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbiterable__SyncJob":{"properties":{"id":{"type":"string","title":"Id","description":"Unique sync-job identifier in format 'sync-{8 hex chars}'.","examples":["sync-1a2b3c4d"]},"tenant_id":{"type":"string","title":"Tenant Id","description":"ID of the tenant this sync job belongs to.","examples":["tenant-1a2b3c4d"]},"resource_type":{"type":"string","title":"Resource Type","description":"Resource being synced: one of lists, campaigns, users, templates, channels, message_types.","examples":["lists","campaigns"]},"status":{"$ref":"#/components/schemas/cbiterable__SyncStatus","description":"Current lifecycle state of the job.","default":"pending"},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"UTC timestamp when the job was created/started."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"UTC timestamp when the job reached a terminal state; None while in flight."},"records_synced":{"type":"integer","title":"Records Synced","description":"Number of records pulled from Iterable and written to DuckDB so far.","default":0,"examples":[1500]},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if the job failed; None otherwise."},"progress":{"type":"number","title":"Progress","description":"Completion fraction from 0.0 (not started) to 1.0 (done).","default":0.0,"examples":[0.5]}},"type":"object","required":["tenant_id","resource_type"],"title":"SyncJob","description":"Tracks a single background sync operation (Iterable → DuckDB).\n\nOne job is created per resource type per sync request. Jobs are persisted\nso progress survives a service restart, and are polled via the Sync routes.","example":{"id":"sync-1a2b3c4d","progress":1.0,"records_synced":1500,"resource_type":"lists","status":"completed","tenant_id":"tenant-1a2b3c4d"}},"cbiterable__SyncRequest":{"properties":{"resource_types":{"items":{"type":"string"},"type":"array","title":"Resource Types","description":"Resources to sync: users, lists, campaigns, templates, channels, message_types","default":["lists"],"examples":[["lists","campaigns"],["users"]]}},"type":"object","title":"SyncRequest","description":"Request to start a sync job.","example":{"resource_types":["lists","campaigns","users"]}},"cbiterable__SyncStatus":{"type":"string","enum":["pending","running","completed","failed"],"title":"SyncStatus","description":"Status of a sync job.\n\nState machine: pending → running → completed | failed.\nJobs start as ``pending``, transition to ``running`` when work begins,\nand end as ``completed`` on success or ``failed`` on error."},"cbiterable__TenantCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Human-readable tenant name","examples":["Production","BOM"]},"iterable_api_key":{"type":"string","title":"Iterable Api Key","description":"Iterable API key","examples":["your-iterable-api-key"]},"iterable_project_name":{"type":"string","title":"Iterable Project Name","description":"Human label for the Iterable project","default":"","examples":["Prod","BOM"]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"}},"type":"object","required":["name","iterable_api_key"],"title":"TenantCreate","description":"Request model for creating a tenant.","example":{"iterable_api_key":"your-iterable-api-key","iterable_project_name":"Prod","name":"Production"}},"cbiterable__TenantKeyReveal":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"name":{"type":"string","title":"Name"},"iterable_api_key":{"type":"string","title":"Iterable Api Key","description":"The tenant's Iterable API key, in plaintext."}},"type":"object","required":["tenant_id","name","iterable_api_key"],"title":"TenantKeyReveal","description":"Response for the explicit key-reveal endpoint — plaintext, on purpose."},"cbiterable__TenantResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique tenant identifier in format 'tenant-{8 hex chars}'"},"name":{"type":"string","title":"Name","description":"Human-readable tenant name","examples":["Production","BOM"]},"iterable_api_key":{"type":"string","title":"Iterable Api Key","description":"MASKED Iterable API key (`abcd…wxyz`). The full value is never returned here; use `POST /tenants/{tenant_id}/key/reveal`.","default":""},"iterable_project_name":{"type":"string","title":"Iterable Project Name","description":"Human label for the Iterable project (e.g. 'Prod', 'BOM')","default":""},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"},"is_active":{"type":"boolean","title":"Is Active","default":true},"sync_interval_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sync Interval Minutes","description":"Auto-sync interval in minutes. None = no auto-sync."},"sync_resource_types":{"items":{"type":"string"},"type":"array","title":"Sync Resource Types","description":"Resource types to sync on schedule"},"last_synced_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Synced At","description":"When a sync job for this tenant last COMPLETED successfully (any resource type). Null = no successful sync recorded. Per-resource freshness lives in the sync job list."}},"type":"object","required":["name"],"title":"TenantResponse","description":"A tenant as returned by the API: the Iterable key is masked.\n\nLive third-party credentials must never ride in list/get/create/patch\nresponse bodies — any fleet-key holder can call those (#12). The full\nvalue is available only via the explicit reveal endpoint\n(``POST /tenants/{tenant_id}/key/reveal``), which logs each access.","example":{"id":"tenant-1a2b3c4d","is_active":true,"iterable_api_key":"5606…fe40","iterable_project_name":"Prod","name":"Production","sync_interval_minutes":60,"sync_resource_types":["lists","campaigns"]}},"cbiterable__TenantUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"New human-readable tenant name.","examples":["Production"]},"iterable_api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Iterable Api Key","description":"Replacement Iterable API key for this tenant's project."},"iterable_project_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Iterable Project Name","description":"New human label for the Iterable project.","examples":["Prod"]},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Arbitrary metadata to store on the tenant."},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Enable (True) or disable (False) the tenant. Inactive tenants reject requests."},"sync_interval_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sync Interval Minutes","description":"Auto-sync interval in minutes. None leaves the schedule unchanged.","examples":[60]},"sync_resource_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sync Resource Types","description":"Resource types to sync on schedule.","examples":[["lists","campaigns"]]},"last_synced_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Synced At","description":"Override the last-successful-sync timestamp (normally stamped automatically when a sync job completes; PATCH it only to backfill or correct)."}},"type":"object","title":"TenantUpdate","description":"Request model for updating a tenant.\n\nAll fields are optional; only the fields provided are changed (PATCH\nsemantics). Omitted fields keep their existing values.","example":{"is_active":true,"name":"Production","sync_interval_minutes":60,"sync_resource_types":["lists","campaigns"]}},"cbiterable__UserUpdateRequest":{"properties":{"email":{"type":"string","title":"Email","description":"User email address","examples":["user@example.com"]},"data_fields":{"additionalProperties":true,"type":"object","title":"Data Fields","description":"Custom data fields to set on the user profile"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"Optional Iterable user ID"}},"type":"object","required":["email"],"title":"UserUpdateRequest","description":"Request to create/update a user in Iterable.","example":{"data_fields":{"firstName":"Jane","lastName":"Doe"},"email":"user@example.com"}},"cbiterable__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbkb__AuditEntry":{"properties":{"id":{"type":"string","title":"Id","description":"Unique audit-entry id."},"action":{"type":"string","title":"Action","description":"The mutation performed (e.g. `create`, `patch`)."},"actor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Id","description":"Identifier of the user who performed the action."},"before":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Before","description":"Record state before the change (null on create)."},"after":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"After","description":"Record state after the change."},"diff_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Diff Summary","description":"Human-readable summary of what changed."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the action occurred (UTC)."}},"type":"object","required":["id","action","created_at"],"title":"AuditEntry","description":"A single audit-log entry recording one mutation of a record.\n\nCaptures the ``action`` (create/patch), who did it (``actor_id``), the\n``before``/``after`` snapshots, and a human-readable ``diff_summary``.","example":{"action":"patch","actor_id":"user_42","after":{"content":"Refunds within 45 days."},"before":{"content":"Refunds within 30 days."},"created_at":"2026-06-01T12:00:00Z","diff_summary":"content changed","id":"01J9ZC3D4E5F6G7H8J9K0M1N2P"}},"cbkb__AuditTrace":{"properties":{"record_id":{"type":"string","title":"Record Id","description":"Id of the record this trail belongs to."},"entries":{"items":{"$ref":"#/components/schemas/cbkb__AuditEntry"},"type":"array","title":"Entries","description":"Audit entries, oldest first."}},"type":"object","required":["record_id"],"title":"AuditTrace","description":"The full audit trail for one record: its id plus all audit entries.","example":{"entries":[{"action":"create","actor_id":"user_42","after":{"title":"Refund window"},"created_at":"2026-05-20T09:15:00Z","diff_summary":"created","id":"01J9ZC3D4E5F6G7H8J9K0M1N2P"}],"record_id":"01J9Z8X7Q4M3N2P1R0S5T6V7W8"}},"cbkb__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbkb__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbkb__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbkb__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbkb__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbkb__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbkb__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbkb__LiveData":{"properties":{"topic":{"type":"string","title":"Topic","description":"What the live datum is about."},"value":{"title":"Value","description":"The live value, if fetched."},"fetched_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fetched At","description":"ISO 8601 timestamp of when the value was fetched."},"fell_back":{"type":"boolean","title":"Fell Back","description":"True if live fetch failed and a fallback value was used."}},"type":"object","required":["topic","fell_back"],"title":"LiveData","description":"Live/real-time data attached to an answer (when requested or available).\n\n``fell_back`` is true when live data could not be fetched and a fallback\nvalue was used instead.","example":{"fell_back":false,"fetched_at":"2026-06-21T14:30:00Z","topic":"store_hours","value":{"close":"21:00","open":"09:00"}}},"cbkb__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbkb__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbkb__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbkb__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbkb__QueryRequest":{"properties":{"question":{"type":"string","title":"Question","description":"The natural-language question to answer."},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"Restrict retrieval to records/rules in this scope."},"surface":{"type":"string","title":"Surface","description":"Calling channel/surface (e.g. `chat`, `email`); used for logging and rule scoping."},"category_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Hint","description":"Optional category to bias record ranking toward."},"max_results":{"type":"integer","title":"Max Results","description":"Maximum number of records to consider during retrieval.","default":5},"require_live":{"type":"boolean","title":"Require Live","description":"If true, the answer must include live data (else it may indicate a fallback).","default":false},"synthesize":{"type":"boolean","title":"Synthesize","description":"When True, run the Layer-3 LLM synthesizer to rule-adapt phrasing. When False, return the verbatim top-ranked record body (staff-raw lookup). See cbkb#17.","default":true}},"additionalProperties":true,"type":"object","required":["question","surface"],"title":"QueryRequest","description":"Request body for the composed knowledge-base query.\n\nCarries the user's ``question`` plus the controls that govern retrieval:\noptional ``scope``/``category_hint`` narrowing, how many records to consider\n(``max_results``), whether live data is required (``require_live``), and\nwhether to run the Layer-3 LLM synthesizer (``synthesize``). ``surface`` is\nrequired and identifies the calling channel for logging/rule scoping.","example":{"category_hint":"policy","max_results":5,"question":"How long do I have to request a refund?","require_live":false,"scope":"billing","surface":"chat","synthesize":true}},"cbkb__QueryResponse":{"properties":{"answer_text":{"type":"string","title":"Answer Text","description":"The composed natural-language answer."},"confidence":{"type":"number","title":"Confidence","description":"Confidence score for the answer, 0.0-1.0."},"sources":{"items":{"$ref":"#/components/schemas/cbkb__Source"},"type":"array","title":"Sources","description":"Cited sources backing the answer."},"live_data":{"anyOf":[{"$ref":"#/components/schemas/cbkb__LiveData"},{"type":"null"}],"description":"Attached live data, if any."},"rules_applied":{"items":{"type":"string"},"type":"array","title":"Rules Applied","description":"Ids of the Layer-3 rules applied during synthesis."},"retrieval_log_id":{"type":"string","title":"Retrieval Log Id","description":"Id of the logged retrieval; replay via GET /kb/retrieval/{log_id}."}},"type":"object","required":["answer_text","confidence","retrieval_log_id"],"title":"QueryResponse","description":"The composed answer returned by ``POST /kb/query``.\n\nBundles the natural-language ``answer_text`` with a ``confidence`` score,\nthe ``sources`` cited, any ``rules_applied`` during synthesis, optional\n``live_data``, and the ``retrieval_log_id`` for later trace-back.","example":{"answer_text":"You can request a refund within 30 days of purchase.","confidence":0.92,"retrieval_log_id":"01J9ZB2C3D4E5F6G7H8J9K0M1N","rules_applied":["01J9ZA1B2C3D4E5F6G7H8J9K0M"],"sources":[{"citation_url":"https://example.com/policies/refunds","record_id":"01J9Z8X7Q4M3N2P1R0S5T6V7W8","snippet":"Refunds within 30 days...","source_tier":"layer_2","title":"Refund window"}]}},"cbkb__Record":{"properties":{"id":{"type":"string","title":"Id","description":"Server-assigned unique record id (ULID)."},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant the record belongs to (from the JWT)."},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"Logical grouping/scope."},"category":{"type":"string","title":"Category","description":"Classification of the fact."},"title":{"type":"string","title":"Title","description":"Short human-readable title."},"content":{"type":"string","title":"Content","description":"The canonical fact text."},"content_struct":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Content Struct","description":"Optional structured/JSON content."},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url","description":"Source URL for citation."},"source_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Type","description":"Kind of source."},"owner_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Id","description":"Owning user/team id."},"status":{"type":"string","title":"Status","description":"Lifecycle status.","default":"active"},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"When the fact was last verified."},"verify_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verify By","description":"Who verified the fact."},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"Free-form tags."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the record was created (UTC)."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the record was last updated (UTC)."}},"type":"object","required":["id","tenant_id","category","title","content","created_at","updated_at"],"title":"Record","description":"A persisted Layer-2 structured-fact record returned to API clients.\n\nMirrors :class:`RecordCreate` plus the server-assigned identity, the\nresolved ``tenant_id``, and the ``created_at`` / ``updated_at`` timestamps.","example":{"category":"policy","content":"Customers may request a refund within 30 days of purchase.","created_at":"2026-05-20T09:15:00Z","id":"01J9Z8X7Q4M3N2P1R0S5T6V7W8","owner_id":"user_42","scope":"billing","source_type":"policy_doc","source_url":"https://example.com/policies/refunds","status":"active","tags":["refunds","billing"],"tenant_id":"acme","title":"Refund window","updated_at":"2026-06-01T12:00:00Z","verified_at":"2026-06-01T12:00:00Z","verify_by":"user_42"}},"cbkb__RecordCreate":{"properties":{"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"Logical grouping the record belongs to (e.g. a topic or domain)."},"category":{"type":"string","title":"Category","description":"Required classification of the fact (e.g. `policy`, `faq`)."},"title":{"type":"string","title":"Title","description":"Short human-readable title for the record."},"content":{"type":"string","title":"Content","description":"The canonical fact text — the authoritative body of the record."},"content_struct":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Content Struct","description":"Optional structured/JSON representation of the content."},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url","description":"URL the fact was sourced from, for citation."},"source_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Type","description":"Kind of source (e.g. `policy_doc`, `web`, `manual`)."},"owner_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Id","description":"Identifier of the user/team that owns this fact."},"status":{"type":"string","title":"Status","description":"Lifecycle status; defaults to `active`.","default":"active"},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"When the fact was last verified, if applicable."},"verify_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verify By","description":"Who verified the fact, if applicable."},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"Free-form tags for filtering and grouping."}},"additionalProperties":true,"type":"object","required":["category","title","content"],"title":"RecordCreate","description":"Request body for creating a Layer-2 structured-fact record.\n\nCarries the canonical, curated content a knowledge base is built on.\n``category``, ``title``, and ``content`` are required; everything else is\noptional metadata. ``extra=\"allow\"`` keeps forward-compatibility for\nadditional fields, but a body-supplied ``tenant_id`` is always stripped and\nrebound to the caller's JWT tenant by the route.","examples":[{"category":"policy","content":"Customers may request a refund within 30 days of purchase.","scope":"billing","source_type":"policy_doc","source_url":"https://example.com/policies/refunds","status":"active","tags":["refunds","billing"],"title":"Refund window"}]},"cbkb__RecordList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbkb__Record"},"type":"array","title":"Items","description":"The matching records for this tenant."}},"type":"object","title":"RecordList","description":"Envelope returned by the list-records endpoint.","example":{"items":[{"category":"policy","content":"Refunds within 30 days.","created_at":"2026-05-20T09:15:00Z","id":"01J9Z8X7Q4M3N2P1R0S5T6V7W8","status":"active","tenant_id":"acme","title":"Refund window","updated_at":"2026-06-01T12:00:00Z"}]}},"cbkb__RecordPatch":{"properties":{"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"New scope, if changing."},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"New category, if changing."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"New title, if changing."},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content","description":"New content, if changing."},"content_struct":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Content Struct","description":"New structured content, if changing."},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url","description":"New source URL, if changing."},"source_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Type","description":"New source type, if changing."},"owner_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Id","description":"New owner, if changing."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"New lifecycle status, if changing."},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"New verification timestamp, if changing."},"verify_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verify By","description":"New verifier, if changing."},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"Replacement tag list, if changing."}},"additionalProperties":true,"type":"object","title":"RecordPatch","description":"Partial-update body for an existing record.\n\nEvery field is optional; only the fields actually present in the request\nare applied (see :meth:`applied_changes`). Unset fields are left untouched.\nA body-supplied ``tenant_id`` is stripped by the route.","example":{"content":"Customers may request a refund within 45 days of purchase.","status":"active","tags":["refunds","billing","updated"]}},"cbkb__RetrievalLog":{"properties":{"id":{"type":"string","title":"Id","description":"Unique retrieval-log id."},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant the retrieval was made for."},"query_text":{"type":"string","title":"Query Text","description":"The question that was asked."},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"Scope the query ran within."},"surface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Surface","description":"Calling surface/channel for the query."},"retrieved_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Retrieved Ids","description":"Ids of the records retrieved for the answer."},"retrieved_pointer_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Retrieved Pointer Ids","description":"Ids of any live pointers retrieved."},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence","description":"Confidence score of the answer, 0.0-1.0."},"response_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Text","description":"The answer text that was returned."},"response_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Id","description":"Identifier of the produced response, if any."},"user_context":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"User Context","description":"Caller context captured at query time."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the retrieval occurred (UTC)."}},"type":"object","required":["id","tenant_id","query_text","created_at"],"title":"RetrievalLog","description":"A logged retrieval — one past /kb/query call and what it produced.\n\nRecords the query text, scope/surface, which records/pointers were\nretrieved, the resulting confidence, and the response that was returned.","example":{"confidence":0.92,"created_at":"2026-06-21T14:30:00Z","id":"01J9ZB2C3D4E5F6G7H8J9K0M1N","query_text":"How long do I have to request a refund?","response_id":"resp_001","response_text":"You can request a refund within 30 days.","retrieved_ids":["01J9Z8X7Q4M3N2P1R0S5T6V7W8"],"retrieved_pointer_ids":[],"scope":"billing","surface":"chat","tenant_id":"acme","user_context":{"locale":"en-US"}}},"cbkb__RetrievalLogList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbkb__RetrievalLog"},"type":"array","title":"Items","description":"The retrieval-log rows for this page."},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Offset for the next page, or null if there are no more rows."}},"type":"object","title":"RetrievalLogList","description":"Paged envelope of retrieval-log rows.\n\n``next_offset`` is non-null when more rows exist beyond this page, letting\ncallers page without a separate COUNT query.","example":{"items":[{"confidence":0.92,"created_at":"2026-06-21T14:30:00Z","id":"01J9ZB2C3D4E5F6G7H8J9K0M1N","query_text":"How long do I have to request a refund?","scope":"billing","surface":"chat","tenant_id":"acme"}],"next_offset":50}},"cbkb__Rule":{"properties":{"id":{"type":"string","title":"Id","description":"Server-assigned unique rule id (ULID)."},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant the rule belongs to (from the JWT)."},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"Logical grouping/scope."},"rule_type":{"type":"string","title":"Rule Type","description":"Kind of rule (e.g. `voice`, `brand`)."},"rule_text":{"type":"string","title":"Rule Text","description":"The rule text in natural language."},"priority":{"type":"integer","title":"Priority","description":"Application priority (lower first).","default":100},"scope_json":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Scope Json","description":"Optional structured scope constraints."},"active":{"type":"boolean","title":"Active","description":"Whether the rule is in effect.","default":true},"version":{"type":"integer","title":"Version","description":"Server-managed version, bumped on each write.","default":1},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Identifier of the user who created the rule."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the rule was created (UTC)."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the rule was last updated (UTC)."}},"type":"object","required":["id","tenant_id","rule_type","rule_text","created_at","updated_at"],"title":"Rule","description":"A persisted Layer-3 brand/voice rule returned to API clients.\n\nMirrors :class:`RuleCreate` plus the server-assigned identity, resolved\n``tenant_id``, monotonic ``version``, and audit timestamps.","example":{"active":true,"created_at":"2026-05-20T09:15:00Z","created_by":"user_42","id":"01J9ZA1B2C3D4E5F6G7H8J9K0M","priority":100,"rule_text":"Always address the customer in a warm, concise tone.","rule_type":"voice","scope":"support","scope_json":{"surfaces":["chat","email"]},"tenant_id":"acme","updated_at":"2026-06-01T12:00:00Z","version":1}},"cbkb__RuleCreate":{"properties":{"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"Logical grouping the rule applies within (e.g. a surface or domain)."},"rule_type":{"type":"string","title":"Rule Type","description":"Required kind of rule (e.g. `voice`, `brand`, `style`)."},"rule_text":{"type":"string","title":"Rule Text","description":"The rule itself, in natural language, fed to the synthesizer."},"priority":{"type":"integer","title":"Priority","description":"Application priority; lower-priority rules apply first. Default 100.","default":100},"scope_json":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Scope Json","description":"Optional structured scope constraints (e.g. surfaces, categories)."},"active":{"type":"boolean","title":"Active","description":"Whether the rule is currently in effect. Default true.","default":true}},"additionalProperties":true,"type":"object","required":["rule_type","rule_text"],"title":"RuleCreate","description":"Request body for creating a Layer-3 brand/voice rule.\n\nRules shape *how* an answer is phrased rather than *what* is true.\n``rule_type`` and ``rule_text`` are required. ``version`` is managed\nserver-side (new rules start at 1) and a body-supplied ``version`` or\n``tenant_id`` is stripped by the route.","examples":[{"active":true,"priority":100,"rule_text":"Always address the customer in a warm, concise tone.","rule_type":"voice","scope":"support","scope_json":{"surfaces":["chat","email"]}}]},"cbkb__RuleList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbkb__Rule"},"type":"array","title":"Items","description":"The matching rules for this tenant."}},"type":"object","title":"RuleList","description":"Envelope returned by the list-rules endpoint.","example":{"items":[{"active":true,"created_at":"2026-05-20T09:15:00Z","id":"01J9ZA1B2C3D4E5F6G7H8J9K0M","priority":100,"rule_text":"Warm, concise tone.","rule_type":"voice","tenant_id":"acme","updated_at":"2026-06-01T12:00:00Z","version":1}]}},"cbkb__RulePatch":{"properties":{"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"New scope, if changing."},"rule_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rule Type","description":"New rule type, if changing."},"rule_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rule Text","description":"New rule text, if changing."},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority","description":"New priority, if changing."},"scope_json":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Scope Json","description":"New structured scope, if changing."},"active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Active","description":"New active state, if changing."}},"additionalProperties":true,"type":"object","title":"RulePatch","description":"Partial-update body for an existing rule.\n\nEvery field is optional; only fields present in the request are applied\n(see :meth:`applied_changes`). ``tenant_id`` and ``version`` are stripped by\nthe route — the version is bumped server-side.","example":{"active":true,"priority":50,"rule_text":"Address the customer warmly and keep replies under 3 sentences."}},"cbkb__Source":{"properties":{"record_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Record Id","description":"Id of the backing record, if record-sourced."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Title of the backing record, if available."},"snippet":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snippet","description":"Short excerpt supporting the answer."},"citation_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Citation Url","description":"URL the source can be cited from."},"source_tier":{"type":"string","enum":["layer_2","layer_4","synthesis"],"title":"Source Tier","description":"Knowledge layer the source came from: `layer_2` (curated fact), `layer_4` (history), or `synthesis` (LLM-produced)."}},"type":"object","required":["source_tier"],"title":"Source","description":"A single cited source backing the composed answer.\n\n``source_tier`` identifies which knowledge layer the citation came from.\nRecord-backed sources carry ``record_id``/``title``; all may carry a\n``snippet`` and a ``citation_url``.","example":{"citation_url":"https://example.com/policies/refunds","record_id":"01J9Z8X7Q4M3N2P1R0S5T6V7W8","snippet":"Customers may request a refund within 30 days...","source_tier":"layer_2","title":"Refund window"}},"cbkb__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbkb__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbkb__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbkrnl__Body_lint_api_v1_lint_post":{"properties":{"graph":{"additionalProperties":true,"type":"object","title":"Graph","description":"A parsed agent-graph document (the same dict shape `cbkrnl.lint.lint_graph` takes)."},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path","description":"Optional source path, echoed into each finding."}},"type":"object","required":["graph"],"title":"Body_lint_api_v1_lint_post"},"cbkrnl__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbkrnl__FindingOut":{"properties":{"rule":{"type":"string","title":"Rule","description":"dangling-state-read | loop-never-terminates | ..."},"severity":{"type":"string","title":"Severity","description":"error | warning"},"message":{"type":"string","title":"Message"},"fields":{"items":{"type":"string"},"type":"array","title":"Fields"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path"}},"type":"object","required":["rule","severity","message"],"title":"FindingOut","description":"One lint finding."},"cbkrnl__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbkrnl__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbkrnl__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbkrnl__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbkrnl__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbkrnl__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbkrnl__LintResponse":{"properties":{"errors":{"type":"integer","title":"Errors"},"warnings":{"type":"integer","title":"Warnings"},"findings":{"items":{"$ref":"#/components/schemas/cbkrnl__FindingOut"},"type":"array","title":"Findings"}},"type":"object","required":["errors","warnings","findings"],"title":"LintResponse","description":"Lint result for a single graph document."},"cbkrnl__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbkrnl__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbkrnl__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbkrnl__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbkrnl__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbkrnl__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbkrnl__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cblinks__APIKeyCreateResponse":{"properties":{"key_info":{"$ref":"#/components/schemas/cblinks__APIKeyInfo"},"api_key":{"type":"string","title":"Api Key","description":"Full API key - shown only once!"}},"additionalProperties":false,"type":"object","required":["key_info","api_key"],"title":"APIKeyCreateResponse","description":"One-time response containing the full API key; the secret is not retrievable afterwards.","x-model-type":"cblinks"},"cblinks__APIKeyInfo":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"user_id":{"type":"string","format":"uuid","title":"User Id"},"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name","description":"Key identifier, e.g. 'prod-server-1'"},"prefix":{"type":"string","title":"Prefix","description":"First 12 chars of key for identification"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"additionalProperties":false,"type":"object","required":["user_id","name","prefix"],"title":"APIKeyInfo","description":"API key metadata returned after creation or in listings (never includes the secret).","x-model-type":"cblinks"},"cblinks__AdminLinkUpdate":{"properties":{"target_url":{"anyOf":[{"type":"string","maxLength":2048,"minLength":1},{"type":"null"}],"title":"Target Url","description":"New destination URL"},"title":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Title","description":"New display title"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Enable/disable the link"},"expires_in_days":{"anyOf":[{"type":"integer","maximum":3650.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days","description":"Reset the link's expiry to N days from now"},"expires_in_minutes":{"anyOf":[{"type":"integer","maximum":525600.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Minutes","description":"Reset the link's expiry to N minutes from now"}},"additionalProperties":false,"type":"object","title":"AdminLinkUpdate","description":"Admin (cross-owner) partial update — LinkUpdate plus expiry extension.\n\nExists so an operator can rescue an already-distributed link whose owner\nis an internal service identity (cblinks#9): repoint a dying presigned\ntarget and/or push out ``expires_at``.","x-model-type":"cblinks"},"cblinks__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cblinks__ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message describing what went wrong","example":"Link not found: abc123"}},"additionalProperties":false,"type":"object","required":["detail"],"title":"ErrorResponse","description":"Standard error response returned by all endpoints.","example":{"detail":"Link not found: abc123"}},"cblinks__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cblinks__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cblinks__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cblinks__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cblinks__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cblinks__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cblinks__LinkCreate":{"properties":{"target_url":{"type":"string","maxLength":2048,"minLength":1,"title":"Target Url","description":"Full destination URL","example":"https://example.com/long/path"},"slug":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Slug","description":"Custom slug (auto-generated if not provided)","example":"my-link"},"title":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Title","description":"Optional display title"},"expires_in_days":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days","description":"Link expiry in days (None = never expires)"},"expires_in_minutes":{"anyOf":[{"type":"integer","maximum":525600.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Minutes","description":"Link expiry in minutes (None = fall back to expires_in_days). For short-lived links — a one-hour share is 60."}},"additionalProperties":false,"type":"object","required":["target_url"],"title":"LinkCreate","description":"Request body for creating a new short link with an optional custom slug.","x-model-type":"cblinks"},"cblinks__LinkResponse":{"properties":{"slug":{"type":"string","title":"Slug","description":"Unique short identifier for the link"},"target_url":{"type":"string","title":"Target Url","description":"Destination URL the link redirects to"},"owner_id":{"type":"string","format":"uuid","title":"Owner Id","description":"ID of the user who created the link"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Optional human-readable title"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the link was created (UTC)"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Expiry timestamp, or null if the link never expires"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the link is currently accepting redirects"},"click_count":{"type":"integer","title":"Click Count","description":"Total number of redirect events recorded"},"short_url":{"type":"string","title":"Short Url","description":"Full short URL for sharing"}},"additionalProperties":false,"type":"object","required":["slug","target_url","owner_id","created_at","is_active","click_count","short_url"],"title":"LinkResponse","description":"API response containing a link's full details and its resolved short URL.","x-model-type":"cblinks"},"cblinks__LinkStats":{"properties":{"slug":{"type":"string","title":"Slug","description":"Link slug these stats belong to"},"total_clicks":{"type":"integer","minimum":0.0,"title":"Total Clicks","description":"All-time redirect count","default":0},"clicks_today":{"type":"integer","minimum":0.0,"title":"Clicks Today","description":"Redirects since midnight UTC","default":0},"clicks_this_week":{"type":"integer","minimum":0.0,"title":"Clicks This Week","description":"Redirects in the last 7 days","default":0},"top_referrers":{"items":{"prefixItems":[{"type":"string"},{"type":"integer"}],"type":"array","maxItems":2,"minItems":2},"type":"array","title":"Top Referrers","description":"Top referrer domains with click counts"}},"additionalProperties":false,"type":"object","required":["slug"],"title":"LinkStats","description":"Aggregated click analytics for a single short link.","x-model-type":"cblinks"},"cblinks__LinkUpdate":{"properties":{"target_url":{"anyOf":[{"type":"string","maxLength":2048,"minLength":1},{"type":"null"}],"title":"Target Url","description":"New destination URL"},"title":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Title","description":"New display title"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Enable/disable the link"},"expires_in_days":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days","description":"Reset this link's expiry to N days from now"},"expires_in_minutes":{"anyOf":[{"type":"integer","maximum":525600.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Minutes","description":"Reset this link's expiry to N minutes from now"}},"additionalProperties":false,"type":"object","title":"LinkUpdate","description":"Partial-update request for an existing link; only supplied fields are changed.","x-model-type":"cblinks"},"cblinks__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cblinks__MintSignedActionRequest":{"properties":{"action":{"type":"string","maxLength":128,"minLength":1,"title":"Action"},"subject_uri":{"type":"string","maxLength":512,"minLength":1,"title":"Subject Uri"},"actor_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Actor User Id","description":"Principal the token will authorise (omit if unknown)"},"ttl_seconds":{"anyOf":[{"type":"integer","maximum":86400.0,"minimum":1.0},{"type":"null"}],"title":"Ttl Seconds","description":"Lifetime in seconds; defaults to service-wide setting"}},"additionalProperties":false,"type":"object","required":["action","subject_uri"],"title":"MintSignedActionRequest","description":"Request body for minting a new signed-action token.","x-model-type":"cblinks"},"cblinks__MintSignedActionResponse":{"properties":{"signed_token_id":{"type":"string","title":"Signed Token Id"},"action":{"type":"string","title":"Action"},"subject_uri":{"type":"string","title":"Subject Uri"},"actor_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Actor User Id"},"issued_at":{"type":"string","format":"date-time","title":"Issued At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"additionalProperties":false,"type":"object","required":["signed_token_id","action","subject_uri","issued_at","expires_at"],"title":"MintSignedActionResponse","description":"One-time response carrying the freshly minted token id and metadata.","x-model-type":"cblinks"},"cblinks__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cblinks__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cblinks__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cblinks__RejectionReason":{"type":"string","enum":["invalid_token","expired","consumed","action_mismatch","subject_mismatch"],"title":"RejectionReason","description":"Why a signed-action verify call returned ``valid: false``.\n\nConsumed by cbgreenroom's ``SignedTokenRejectedEvent.rejection_reason``\naudit field. Coordinate any additions there."},"cblinks__ServiceUser":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When this resource was created (UTC)","example":"2024-01-15T10:30:00Z"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When this resource was last modified (UTC)","example":"2024-01-15T14:45:00Z"},"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"email":{"type":"string","maxLength":256,"title":"Email"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"additionalProperties":false,"type":"object","required":["name","email"],"title":"ServiceUser","description":"Registered API consumer with credentials and activity tracking.","x-model-type":"cblinks"},"cblinks__ServiceUserCreate":{"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"email":{"type":"string","maxLength":256,"title":"Email"}},"additionalProperties":false,"type":"object","required":["name","email"],"title":"ServiceUserCreate","description":"Request body for registering a new API consumer account.","x-model-type":"cblinks"},"cblinks__ShareRequest":{"properties":{"filename":{"type":"string","maxLength":200,"minLength":1,"pattern":"^[A-Za-z0-9._-]+$","title":"Filename","description":"Filename for the uploaded object. Letters, digits, dot, underscore, and dash only."},"content_type":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Content Type","description":"Optional MIME type. Informational only — cbfiles infers from key extension."},"expires_in_days":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":1.0},{"type":"null"}],"title":"Expires In Days","description":"Days until the short link expires. Defaults to settings (typically 30)."},"upload_expires_in_seconds":{"anyOf":[{"type":"integer","maximum":604800.0,"minimum":60.0},{"type":"null"}],"title":"Upload Expires In Seconds","description":"TTL of the presigned PUT URL in seconds. Defaults to 3600 (1 hour); cbfiles caps at 7 days."},"title":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Title","description":"Optional human-readable title stored on the short link."}},"additionalProperties":false,"type":"object","required":["filename"],"title":"ShareRequest","description":"Request body for creating a share (presigned-upload + short link).","x-model-type":"cblinks"},"cblinks__ShareResponse":{"properties":{"slug":{"type":"string","title":"Slug","description":"Short slug identifying both the link and the object key."},"filename":{"type":"string","title":"Filename","description":"Filename portion of the cbfiles object key."},"bucket":{"type":"string","title":"Bucket","description":"cbfiles bucket the file lands in."},"key":{"type":"string","title":"Key","description":"Full cbfiles object key (typically share/<slug>/<filename>)."},"upload_url":{"type":"string","title":"Upload Url","description":"Presigned PUT URL. Time-limited."},"upload_method":{"type":"string","title":"Upload Method","description":"HTTP method to use for the upload.","default":"PUT"},"upload_expires_at":{"type":"string","format":"date-time","title":"Upload Expires At","description":"When the upload URL stops working (UTC)."},"upload_curl":{"type":"string","title":"Upload Curl","description":"Ready-to-run curl command for the upload step."},"short_url":{"type":"string","title":"Short Url","description":"Shareable short URL."},"public_url":{"type":"string","title":"Public Url","description":"Eventual public CDN URL the short link redirects to."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Short-link expiry (UTC), or null for never."}},"additionalProperties":false,"type":"object","required":["slug","filename","bucket","key","upload_url","upload_expires_at","upload_curl","short_url","public_url"],"title":"ShareResponse","description":"Combined upload + short-link response.\n\nThe client uploads the file with::\n\n    curl -X PUT --upload-file <path> \"<upload_url>\"\n\nAnyone hitting ``short_url`` gets a 302 to ``public_url`` (after upload\ncompletes — until then it redirects to a 404 on the CDN).","x-model-type":"cblinks"},"cblinks__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cblinks__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cblinks__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cblinks__VerifySignedActionRequest":{"properties":{"signed_token_id":{"type":"string","maxLength":256,"minLength":1,"title":"Signed Token Id"},"expected_action":{"type":"string","maxLength":128,"minLength":1,"title":"Expected Action"},"expected_subject_uri":{"type":"string","maxLength":512,"minLength":1,"title":"Expected Subject Uri"}},"additionalProperties":false,"type":"object","required":["signed_token_id","expected_action","expected_subject_uri"],"title":"VerifySignedActionRequest","description":"Request body for the verify-and-consume call.","x-model-type":"cblinks"},"cblinks__VerifySignedActionResponse":{"properties":{"valid":{"type":"boolean","title":"Valid"},"reason":{"anyOf":[{"$ref":"#/components/schemas/cblinks__RejectionReason"},{"type":"null"}]},"consumed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Consumed At"},"action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action"},"subject_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Uri"},"actor_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Actor User Id"}},"additionalProperties":false,"type":"object","required":["valid"],"title":"VerifySignedActionResponse","description":"Result of a verify-and-consume call.\n\nAlways returned with HTTP 200 — ``valid`` distinguishes success from\nrejection. ``reason`` is only set when ``valid`` is False; ``action``,\n``subject_uri``, ``actor_user_id``, and ``consumed_at`` are only set when\n``valid`` is True.","x-model-type":"cblinks"},"cblist__Body_ingest_set_mappings_api_v1_ingest__job_id__mappings_put":{"properties":{"mappings":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":"object","title":"Mappings"}},"type":"object","required":["mappings"],"title":"Body_ingest_set_mappings_api_v1_ingest__job_id__mappings_put"},"cblist__Body_ingest_upload_api_v1_ingest_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"CSV/TSV/XLSX file to ingest"},"list_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"List Name"}},"type":"object","required":["file"],"title":"Body_ingest_upload_api_v1_ingest_post"},"cblist__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cblist__FieldOut":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label"},"type":{"type":"string","title":"Type","description":"text|number|currency|date|boolean|enum|tag"},"group":{"type":"string","title":"Group","default":""},"operators":{"items":{"type":"string"},"type":"array","title":"Operators","description":"Operators legal for this type."},"enum_values":{"items":{"type":"string"},"type":"array","title":"Enum Values"}},"type":"object","required":["key","label","type"],"title":"FieldOut","description":"One filterable field, as the builder should render it."},"cblist__FieldsOut":{"properties":{"source":{"type":"string","title":"Source"},"fields":{"items":{"$ref":"#/components/schemas/cblist__FieldOut"},"type":"array","title":"Fields"},"dropped":{"items":{"type":"string"},"type":"array","title":"Dropped","description":"Columns with no sane filter UI (JSON/STRUCT/MAP/BLOB) or excluded by curation. Reported so a console can say how many, rather than presenting a short list as if it were complete."},"requires_bound":{"type":"boolean","title":"Requires Bound","default":false}},"type":"object","required":["source","fields"],"title":"FieldsOut","description":"The field set for one source, plus what was held back."},"cblist__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cblist__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cblist__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cblist__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cblist__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cblist__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cblist__ListCreate":{"properties":{"name":{"type":"string","minLength":1,"title":"Name","description":"Human-readable list name."},"source":{"type":"string","title":"Source","description":"How the list originates: manual | upload | voterfile | query | api.","default":"manual"},"kind":{"type":"string","title":"Kind","description":"static (materialized rows) | dynamic (a saved query re-evaluated on read).","default":"static"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"filter_criteria":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Filter Criteria","description":"For dynamic lists/segments: the query definition."}},"type":"object","required":["name"],"title":"ListCreate","description":"Create a new list."},"cblist__ListOut":{"properties":{"list_id":{"type":"string","title":"List Id"},"name":{"type":"string","title":"Name"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"row_count":{"type":"integer","title":"Row Count","default":0},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["list_id","name"],"title":"ListOut","description":"A list's registry record."},"cblist__ListUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","title":"ListUpdate","description":"Patch a list's mutable fields."},"cblist__MemberOut":{"properties":{"contact":{"type":"string","title":"Contact","description":"The email or phone for the requested channel."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"email2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email2"},"phone2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone2"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"merge_vars":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Merge Vars"}},"type":"object","required":["contact"],"title":"MemberOut","description":"A resolved recipient for a send channel."},"cblist__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cblist__PreviewIn":{"properties":{"filter_criteria":{"additionalProperties":true,"type":"object","title":"Filter Criteria"},"sample":{"type":"integer","minimum":0.0,"title":"Sample","description":"Rows of sample to return alongside the count.","default":0},"breakdown":{"type":"boolean","title":"Breakdown","description":"Also return `per_source`: how many people each pool contributes and how many of them are in another pool too. ⚠️ A SECOND federation query, so the live reach counter should leave it off and ask only where the answer is displayed. Ignored for a single-source segment, which has nothing to match against.","default":false}},"type":"object","required":["filter_criteria"],"title":"PreviewIn","description":"Evaluate an UNSAVED AST — powers the live reach counter and step 3."},"cblist__PreviewOut":{"properties":{"reach":{"type":"integer","title":"Reach"},"emails":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Emails","description":"Distinct non-null email addresses across the reached population — how many of them can actually be mailed. DISTINCT because a single pool's `reach` is ROWS: donor_rollup is grained (client, email), so one address under two client books is two rows and one mailbox. Over two or more pools it is counted after identity resolution, so a person in both is one address. ⚠️ NULL, not 0, when the pool carries no email column at all — the difference between 'your filters excluded everyone' and 'this source cannot be emailed', which is the one a reader would otherwise go hunting for a filter over."},"phones":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Phones","description":"Distinct non-null phone values, on the same terms as `emails`."},"unbounded":{"type":"boolean","title":"Unbounded","description":"True when no condition narrows the query. Some catalogs refuse an unbounded scan outright, so a console should warn before asking.","default":false},"sql":{"type":"string","title":"Sql","description":"The compiled SQL — the mock's dev panel.","default":""},"columns":{"items":{"type":"string"},"type":"array","title":"Columns"},"rows":{"items":{"items":{},"type":"array"},"type":"array","title":"Rows"},"query_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Query Time Ms"},"resolution":{"items":{"type":"string"},"type":"array","title":"Resolution","description":"Per-source provenance, in words: which rule decided each source's ties (recency column, or source order), and — when `breakdown` was asked for — every source whose rows carry no usable join key, which is the difference between 'your filters excluded everybody' and 'this pool cannot be matched on that key at all'. Empty for a single-source segment, which has nothing to resolve. ⚠️ THIS FIELD WAS MISSING FROM THIS MODEL until 2026-08-28, so `evaluate` built the notes and pydantic's default `extra=\"ignore\"` dropped every one of them on both routes. Nothing errored and no test could see it: a unit test of `evaluate` reads the dict before the model does."},"matched_total":{"type":"integer","title":"Matched Total","description":"People present in TWO OR MORE sources. Set with `per_source`. ⚠️ Not the sum of the per-source `matched` values and not derivable from them — with two pools that is the same people counted twice.","default":0},"per_source":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Per Source","description":"One entry per source, in the order given: `{id, table, keys, matched, rows, keyless}`. `keys` is the distinct people that source contributes to THIS segment (after its filters); `matched` is how many of those are also in at least one other source. Present only when `breakdown` was asked for and the segment has two or more sources. ⚠️ `matched` is per source and not a single overlap figure — with three pools a scalar is ambiguous, and with two it is the same number twice. ⚠️ `rows` and `keyless` describe a DIFFERENT population from the two above: they are the source's whole scanned set, NOT narrowed by the segment's conditions. `keyless` is how many of those rows carry no usable value for the join key and so cannot be matched to anybody — which is what separates 'this pool has no email addresses' from 'your filters excluded all of its people'. Both arrive as `keys: 0`, and only one of them is a data problem. Unfiltered on purpose: the question is about the pool, and the user's conditions would shrink the figure for an unrelated reason."}},"type":"object","required":["reach"],"title":"PreviewOut","description":"Reach, and optionally a bounded sample."},"cblist__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cblist__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cblist__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cblist__RowsAdded":{"properties":{"added":{"type":"integer","title":"Added"},"row_count":{"type":"integer","title":"Row Count"}},"type":"object","required":["added","row_count"],"title":"RowsAdded","description":"Outcome of appending rows."},"cblist__RowsIn":{"properties":{"rows":{"items":{"additionalProperties":true,"type":"object"},"type":"array","minItems":1,"title":"Rows","description":"Records to append. Max 5000 per request."}},"type":"object","required":["rows"],"title":"RowsIn","description":"Append rows to a list. Each row is a flat dict; known columns are promoted,\nthe full dict is preserved in ``raw``."},"cblist__SegmentCreate":{"properties":{"name":{"type":"string","minLength":1,"title":"Name"},"filter_criteria":{"additionalProperties":true,"type":"object","title":"Filter Criteria","description":"The segment AST (see cblist/segment_ast.py)."},"is_dynamic":{"type":"boolean","title":"Is Dynamic","description":"Dynamic segments are re-evaluated on read; static ones freeze the membership captured at the last execute.","default":true}},"type":"object","required":["name","filter_criteria"],"title":"SegmentCreate","description":"Create a segment from a versioned AST."},"cblist__SegmentOut":{"properties":{"segment_id":{"type":"string","title":"Segment Id"},"name":{"type":"string","title":"Name"},"filter_criteria":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Filter Criteria"},"is_dynamic":{"type":"boolean","title":"Is Dynamic","default":true},"row_count":{"type":"integer","title":"Row Count","default":0},"last_refreshed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Refreshed At"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["segment_id","name"],"title":"SegmentOut","description":"A segment's stored record."},"cblist__SegmentUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"filter_criteria":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Filter Criteria"},"is_dynamic":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Dynamic"}},"type":"object","title":"SegmentUpdate","description":"Patch a segment's name or definition."},"cblist__SourceOut":{"properties":{"kind":{"type":"string","title":"Kind","description":"fabric | list"},"id":{"type":"string","title":"Id","description":"Fabric dataset id, or cblist list_id."},"table":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Table","description":"Table within a fabric dataset."},"key":{"type":"string","title":"Key","description":"Stable 'id.table' handle."},"label":{"type":"string","title":"Label"},"category":{"type":"string","title":"Category","description":"cbmodels flavor vocabulary: donation, campaign, voter, …","default":""},"identity":{"additionalProperties":{"type":"string"},"type":"object","title":"Identity","description":"Join key → column. Drives cross-source person matching. ⚠️ A SCHEMA CLAIM, not a measured fact: the column exists, its rows may all be NULL. See `coverage`."},"coverage":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"Coverage","description":"MEASURED identity coverage: join key → rows carrying a value the join would actually keep, plus `rows` — the denominator, counted in the same scan so 'phone: 0 of 43 rows' cannot be assembled from two figures that disagree. Counts the NORMALISED key, so a column full of `N/A` reads as 0 where `COUNT(phone)` would call it covered. ⚠️ `null` means NOT MEASURED and must not be rendered as a zero — '0 of 43 carry a phone' and 'we did not measure' are different sentences, and only the first is a reason not to build the segment. `null` for every fabric source today, deliberately: measuring one is a federation round trip on the picker's critical path, and their identity maps are hand-verified column names rather than a hardcoded four. Lists are the case that was measured wrong."},"requires_bound":{"type":"boolean","title":"Requires Bound","description":"The federation refuses unbounded scans of this source; a date filter is mandatory before a reach estimate can be produced.","default":false},"visible":{"type":"boolean","title":"Visible","description":"False when the caller's grants do not reach it.","default":true},"row_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Row Count"},"notes":{"type":"string","title":"Notes","default":""}},"type":"object","required":["kind","id","key","label"],"title":"SourceOut","description":"A pool a segment may be built over."},"cblist__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cblist__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cblist__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cblogs__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cblogs__CrashLoop":{"properties":{"unit":{"type":"string","title":"Unit"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["unit","count"],"title":"CrashLoop","description":"A crash-looping service."},"cblogs__CriticalPattern":{"properties":{"pattern":{"type":"string","title":"Pattern"},"unit":{"type":"string","title":"Unit"},"message":{"type":"string","title":"Message"}},"type":"object","required":["pattern","unit","message"],"title":"CriticalPattern","description":"A detected critical pattern (OOM, segfault, etc.)."},"cblogs__GroupInfo":{"properties":{"name":{"$ref":"#/components/schemas/cblogs__LogGroup"},"description":{"type":"string","title":"Description"},"backend":{"type":"string","title":"Backend"},"sources":{"items":{"type":"string"},"type":"array","title":"Sources"}},"type":"object","required":["name","description","backend","sources"],"title":"GroupInfo","description":"Information about a log group."},"cblogs__GroupStats":{"properties":{"name":{"$ref":"#/components/schemas/cblogs__LogGroup"},"total_size_bytes":{"type":"integer","title":"Total Size Bytes","default":0},"file_count":{"type":"integer","title":"File Count","default":0},"entry_count":{"type":"integer","title":"Entry Count","default":0},"error_count":{"type":"integer","title":"Error Count","default":0},"last_entry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Entry"}},"type":"object","required":["name"],"title":"GroupStats","description":"Statistics for a log group."},"cblogs__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cblogs__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cblogs__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cblogs__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cblogs__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cblogs__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cblogs__LogEntry":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"level":{"$ref":"#/components/schemas/cblogs__LogLevel","default":6},"message":{"type":"string","title":"Message"},"source":{"type":"string","title":"Source","default":""},"unit":{"type":"string","title":"Unit","default":""},"extra":{"additionalProperties":true,"type":"object","title":"Extra","default":{}}},"type":"object","required":["timestamp","message"],"title":"LogEntry","description":"A single log entry."},"cblogs__LogGroup":{"type":"string","enum":["system","services","web","database","dns","security","network","cron"],"title":"LogGroup","description":"Log groups available for querying."},"cblogs__LogLevel":{"type":"integer","enum":[0,1,2,3,4,5,6,7],"title":"LogLevel","description":"Log severity levels mapped to journald priorities."},"cblogs__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cblogs__NginxAccessEntry":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"client_ip":{"type":"string","title":"Client Ip"},"method":{"type":"string","title":"Method"},"path":{"type":"string","title":"Path"},"status":{"type":"integer","title":"Status"},"body_bytes":{"type":"integer","title":"Body Bytes"},"referer":{"type":"string","title":"Referer","default":""},"user_agent":{"type":"string","title":"User Agent","default":""},"upstream":{"type":"string","title":"Upstream","default":""}},"type":"object","required":["timestamp","client_ip","method","path","status","body_bytes"],"title":"NginxAccessEntry","description":"Parsed nginx combined format access log entry."},"cblogs__NginxErrorEntry":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"level":{"type":"string","title":"Level"},"message":{"type":"string","title":"Message"},"client":{"type":"string","title":"Client","default":""},"server":{"type":"string","title":"Server","default":""},"request":{"type":"string","title":"Request","default":""}},"type":"object","required":["timestamp","level","message"],"title":"NginxErrorEntry","description":"Parsed nginx error log entry."},"cblogs__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cblogs__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cblogs__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cblogs__QueryResult":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/cblogs__LogEntry"},"type":"array","title":"Entries"},"total":{"type":"integer","title":"Total"},"has_more":{"type":"boolean","title":"Has More","default":false},"query_time_ms":{"type":"number","title":"Query Time Ms","default":0.0}},"type":"object","required":["entries","total"],"title":"QueryResult","description":"Result of a log query."},"cblogs__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cblogs__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cblogs__SyslogScan":{"properties":{"total_errors":{"type":"integer","title":"Total Errors"},"errors_by_unit":{"additionalProperties":{"type":"integer"},"type":"object","title":"Errors By Unit"},"crash_looping":{"items":{"$ref":"#/components/schemas/cblogs__CrashLoop"},"type":"array","title":"Crash Looping"},"critical_patterns":{"items":{"$ref":"#/components/schemas/cblogs__CriticalPattern"},"type":"array","title":"Critical Patterns"},"sample_messages":{"items":{"type":"string"},"type":"array","title":"Sample Messages"},"window_minutes":{"type":"integer","title":"Window Minutes"},"max_priority":{"type":"integer","title":"Max Priority"}},"type":"object","required":["total_errors","errors_by_unit","crash_looping","critical_patterns","sample_messages","window_minutes","max_priority"],"title":"SyslogScan","description":"Result of a syslog analysis scan."},"cblogs__SystemStatus":{"properties":{"total_size_bytes":{"type":"integer","title":"Total Size Bytes","default":0},"total_size_human":{"type":"string","title":"Total Size Human","default":""},"groups":{"items":{"$ref":"#/components/schemas/cblogs__GroupStats"},"type":"array","title":"Groups","default":[]},"top_noisy_units":{"items":{"additionalProperties":{"type":"integer"},"type":"object"},"type":"array","title":"Top Noisy Units","default":[]},"journald_disk_usage":{"type":"string","title":"Journald Disk Usage","default":""}},"type":"object","title":"SystemStatus","description":"Overall system log status."},"cblogs__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cblogs__VhostInfo":{"properties":{"name":{"type":"string","title":"Name"},"access_log":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Access Log"},"error_log":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Log"},"access_log_size":{"type":"integer","title":"Access Log Size","default":0},"error_log_size":{"type":"integer","title":"Error Log Size","default":0}},"type":"object","required":["name"],"title":"VhostInfo","description":"Information about a discovered nginx vhost."},"cbloom__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbloom__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbloom__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbloom__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbloom__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbloom__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbloom__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbloom__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbloom__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbloom__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbloom__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbmanim__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbmanim__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbmanim__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbmanim__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbmanim__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbmanim__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbmanim__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbmanim__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbmanim__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbmanim__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbmanim__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbmanim__PromptRequest":{"properties":{"brief":{"type":"string","title":"Brief","description":"Natural-language description of the animation."},"render":{"type":"boolean","title":"Render","description":"Render the generated scene, or just return source.","default":true},"quality":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quality"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"}},"type":"object","required":["brief"],"title":"PromptRequest"},"cbmanim__RenderRequest":{"properties":{"source":{"type":"string","title":"Source","description":"Python module defining one Manim Scene subclass."},"scene_name":{"type":"string","title":"Scene Name","description":"The Scene class to render."},"quality":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quality","description":"manim -q: l|m|h|p|k (default from config)."},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format","description":"mp4|gif|webm|png (default from config)."}},"type":"object","required":["source","scene_name"],"title":"RenderRequest"},"cbmanim__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbmanim__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbmanim__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbmaps__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbmaps__GeneratePosterRequest":{"properties":{"city":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"City","description":"City name (required for vector/raster)"},"country":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Country","description":"Country name (required for vector/raster)"},"theme":{"type":"string","title":"Theme","description":"Theme name (for vector mode)","default":"feature_based"},"distance":{"type":"integer","maximum":50000.0,"minimum":4000.0,"title":"Distance","description":"Map radius in meters","default":29000},"render_mode":{"$ref":"#/components/schemas/cbmaps__RenderMode","description":"Rendering mode: 'vector', 'raster', or 'district'","default":"vector"},"tile_style":{"$ref":"#/components/schemas/cbmaps__TileStyle","description":"Tile style for raster/district mode","default":"dark_nolabels"},"district_geoid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"District Geoid","description":"District GEOID for district mode (e.g., '0601' for CA-01)"}},"type":"object","title":"GeneratePosterRequest","description":"Request model for generating a map poster.","examples":[{"city":"New York","country":"USA","distance":12000,"theme":"noir"},{"city":"Paris","country":"France","distance":10000,"theme":"pastel_dream"},{"city":"Tokyo","country":"Japan","distance":15000,"theme":"japanese_ink"},{"city":"Venice","country":"Italy","distance":4000,"theme":"blueprint"},{"city":"Amsterdam","country":"Netherlands","distance":6000,"theme":"ocean"},{"city":"Barcelona","country":"Spain","distance":8000,"theme":"warm_beige"},{"city":"London","country":"UK","distance":15000,"theme":"noir"},{"city":"Dubai","country":"UAE","distance":15000,"theme":"midnight_blue"},{"city":"San Francisco","country":"USA","distance":10000,"theme":"sunset"},{"city":"Sydney","country":"Australia","distance":12000,"theme":"ocean"},{"city":"Marrakech","country":"Morocco","distance":5000,"theme":"terracotta"},{"city":"Budapest","country":"Hungary","distance":8000,"theme":"copper_patina"}]},"cbmaps__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbmaps__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbmaps__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbmaps__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbmaps__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbmaps__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbmaps__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbmaps__PosterListResponse":{"properties":{"posters":{"items":{"type":"string"},"type":"array","title":"Posters","description":"List of poster filenames"},"count":{"type":"integer","title":"Count","description":"Total number of posters"}},"type":"object","required":["posters","count"],"title":"PosterListResponse","description":"Response listing previously generated poster filenames.\n\nReturned by ``GET /posters``. The ``posters`` list is sorted newest-first;\neach filename can be downloaded via ``GET /posters/{filename}``.","examples":[{"count":2,"posters":["paris_france_noir_20260621.png","tokyo_japan_japanese_ink_20260620.png"]}]},"cbmaps__PosterResponse":{"properties":{"filename":{"type":"string","title":"Filename","description":"Generated poster filename"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"City name (null for district mode)"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"Country name (null for district mode)"},"theme":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Theme","description":"Theme used (null for raster/district mode)"},"distance":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Distance","description":"Map radius in meters (null for district mode)"},"download_url":{"type":"string","title":"Download Url","description":"Relative URL to download the poster"},"render_mode":{"$ref":"#/components/schemas/cbmaps__RenderMode","description":"Rendering mode used"},"tile_style":{"anyOf":[{"$ref":"#/components/schemas/cbmaps__TileStyle"},{"type":"null"}],"description":"Tile style used (raster/district mode only)"},"district_geoid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"District Geoid","description":"District GEOID (district mode only)"},"district_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"District Name","description":"District display name (district mode only)"}},"type":"object","required":["filename","download_url","render_mode"],"title":"PosterResponse","description":"Response describing a single successfully generated poster.\n\nReturned by ``POST /posters/generate``. Fields are populated according to\nthe render mode used: city/country/theme/distance describe vector and\nraster posters, while district_geoid/district_name describe district-mode\nposters (and the city-oriented fields are then null). ``download_url`` is\nalways present and points at ``GET /posters/{filename}``.","examples":[{"city":"Paris","country":"France","distance":10000,"download_url":"/api/v1/posters/paris_france_noir_20260621.png","filename":"paris_france_noir_20260621.png","render_mode":"vector","theme":"noir"},{"district_geoid":"0601","district_name":"California 1st Congressional District","download_url":"/api/v1/posters/district_0601_20260621.png","filename":"district_0601_20260621.png","render_mode":"district","tile_style":"dark_nolabels"}]},"cbmaps__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbmaps__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbmaps__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbmaps__RenderMode":{"type":"string","enum":["vector","raster","district"],"title":"RenderMode","description":"How a poster is rendered.\n\n- ``VECTOR``  — high-quality vector render from OpenStreetMap data via\n  osmnx (the original maptoposter pipeline); honours ``theme``.\n- ``RASTER``  — faster tile-based render using CartoDB styles via cbtiles;\n  honours ``tile_style``.\n- ``DISTRICT`` — raster basemap with a congressional-district boundary\n  overlay; requires ``district_geoid`` instead of city/country."},"cbmaps__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbmaps__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbmaps__StreetImage":{"properties":{"id":{"type":"string","title":"Id","description":"Provider-scoped image identifier"},"provider":{"$ref":"#/components/schemas/cbmaps__StreetProviderName","description":"Source dataset"},"lat":{"type":"number","title":"Lat","description":"Capture latitude (WGS84)"},"lon":{"type":"number","title":"Lon","description":"Capture longitude (WGS84)"},"heading":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Heading","description":"Compass heading of the camera in degrees, if known"},"captured_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Captured At","description":"ISO-8601 capture timestamp, if known"},"is_pano":{"type":"boolean","title":"Is Pano","description":"Whether the image is a 360 panorama","default":false},"distance_m":{"type":"number","title":"Distance M","description":"Distance from the query point in meters"},"thumb_url":{"type":"string","title":"Thumb Url","description":"Relative URL of the cached 256px thumbnail"},"image_url":{"type":"string","title":"Image Url","description":"Relative URL of the cached 1024px image"}},"type":"object","required":["id","provider","lat","lon","distance_m","thumb_url","image_url"],"title":"StreetImage","description":"A single street-level photo near the queried location.\n\n``thumb_url`` and ``image_url`` point at cbmaps' own JIT image cache\n(``GET /street/images/{provider}/{image_id}.jpg``) — never at upstream\nCDNs, whose URLs may be signed and short-lived.","examples":[{"captured_at":"2016-03-23T09:33:51+00:00","distance_m":14.7,"heading":324.42,"id":"470385","image_url":"/api/v1/street/images/kartaview/470385.jpg?size=1024","is_pano":false,"lat":48.858582,"lon":2.294464,"provider":"kartaview","thumb_url":"/api/v1/street/images/kartaview/470385.jpg?size=256"}]},"cbmaps__StreetImageSearchResponse":{"properties":{"images":{"items":{"$ref":"#/components/schemas/cbmaps__StreetImage"},"type":"array","title":"Images","description":"Matching images, nearest first"},"count":{"type":"integer","title":"Count","description":"Number of images returned"},"providers":{"items":{"$ref":"#/components/schemas/cbmaps__StreetProviderName"},"type":"array","title":"Providers","description":"Providers that answered this search"},"lat":{"type":"number","title":"Lat","description":"Query latitude"},"lon":{"type":"number","title":"Lon","description":"Query longitude"},"radius":{"type":"integer","title":"Radius","description":"Effective search radius in meters"}},"type":"object","required":["images","count","providers","lat","lon","radius"],"title":"StreetImageSearchResponse","description":"Response for a street-imagery search near a point.\n\nReturned by ``GET /street/images``. Images are merged across the queried\nproviders and sorted nearest-first. ``providers`` lists the sources that\nactually answered (a provider that errors upstream is skipped).","examples":[{"count":1,"images":[{"captured_at":"2016-03-23T09:33:51+00:00","distance_m":14.7,"heading":324.42,"id":"470385","image_url":"/api/v1/street/images/kartaview/470385.jpg?size=1024","is_pano":false,"lat":48.858582,"lon":2.294464,"provider":"kartaview","thumb_url":"/api/v1/street/images/kartaview/470385.jpg?size=256"}],"lat":48.8584,"lon":2.2945,"providers":["kartaview"],"radius":150}]},"cbmaps__StreetProviderInfo":{"properties":{"name":{"$ref":"#/components/schemas/cbmaps__StreetProviderName","description":"Provider identifier"},"configured":{"type":"boolean","title":"Configured","description":"Whether the provider is ready to serve requests"},"note":{"type":"string","title":"Note","description":"Human-readable status detail"}},"type":"object","required":["name","configured","note"],"title":"StreetProviderInfo","description":"Availability of one street-imagery provider.","examples":[{"configured":true,"name":"mapillary","note":"Mapillary Graph API v4 (token configured)"}]},"cbmaps__StreetProviderListResponse":{"properties":{"providers":{"items":{"$ref":"#/components/schemas/cbmaps__StreetProviderInfo"},"type":"array","title":"Providers","description":"Known providers"},"count":{"type":"integer","title":"Count","description":"Number of known providers"}},"type":"object","required":["providers","count"],"title":"StreetProviderListResponse","description":"Response listing street-imagery providers and their readiness.\n\nReturned by ``GET /street/providers``. A provider with\n``configured=false`` (e.g. Mapillary without an access token) rejects\nexplicit requests with 503 and is skipped in all-provider searches.","examples":[{"count":2,"providers":[{"configured":false,"name":"mapillary","note":"MAPILLARY_ACCESS_TOKEN not set"},{"configured":true,"name":"kartaview","note":"KartaView 2.0 API (keyless)"}]}]},"cbmaps__StreetProviderName":{"type":"string","enum":["mapillary","kartaview"],"title":"StreetProviderName","description":"Street-level imagery data source.\n\n- ``MAPILLARY``  — crowdsourced street imagery via the Mapillary Graph API\n  (requires a configured access token).\n- ``KARTAVIEW`` — crowdsourced street imagery via the keyless KartaView\n  (formerly OpenStreetCam) API."},"cbmaps__ThemeInfo":{"properties":{"name":{"type":"string","title":"Name","description":"Theme identifier used in API requests"},"display_name":{"type":"string","title":"Display Name","description":"Human-readable theme name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Theme description"}},"type":"object","required":["name","display_name"],"title":"ThemeInfo","description":"Metadata for a single poster theme.\n\nThe ``name`` is the stable identifier passed in the ``theme`` field of a\ngenerate request; ``display_name`` is suitable for showing in a picker.","examples":[{"description":"High-contrast black-and-white minimalist map.","display_name":"Noir","name":"noir"}]},"cbmaps__ThemeListResponse":{"properties":{"themes":{"items":{"$ref":"#/components/schemas/cbmaps__ThemeInfo"},"type":"array","title":"Themes","description":"List of available themes"},"count":{"type":"integer","title":"Count","description":"Total number of themes"}},"type":"object","required":["themes","count"],"title":"ThemeListResponse","description":"Response listing all available poster themes.\n\nReturned by ``GET /themes``. Each entry is a :class:`ThemeInfo` whose\n``name`` may be supplied as the ``theme`` of a generate request.","examples":[{"count":2,"themes":[{"description":"High-contrast black-and-white minimalist map.","display_name":"Noir","name":"noir"},{"description":"Cool blue palette evoking coastal cities.","display_name":"Ocean","name":"ocean"}]}]},"cbmaps__TileStyle":{"type":"string","enum":["dark_all","light_all","dark_nolabels","light_nolabels","voyager","voyager_nolabels"],"title":"TileStyle","description":"CartoDB basemap tile style used by raster and district render modes.\n\nValues map to CartoDB's named basemaps: ``dark_all``/``light_all`` (full\nlabels), ``dark_nolabels``/``light_nolabels`` (no labels), and the\n``voyager``/``voyager_nolabels`` neutral palette. Ignored in vector mode."},"cbmaps__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbmarketing__AccessibleRequest":{"properties":{"foreground":{"type":"string","title":"Foreground","examples":["#9ca3af"]},"background":{"type":"string","title":"Background","examples":["#ffffff"]},"target_ratio":{"type":"number","maximum":21.0,"minimum":1.0,"title":"Target Ratio","default":4.5}},"type":"object","required":["foreground","background"],"title":"AccessibleRequest"},"cbmarketing__AccessibleResponse":{"properties":{"background":{"type":"string","title":"Background"},"requested":{"type":"string","title":"Requested"},"suggested":{"type":"string","title":"Suggested"},"original_ratio":{"type":"number","title":"Original Ratio"},"suggested_ratio":{"type":"number","title":"Suggested Ratio"},"adjusted":{"type":"boolean","title":"Adjusted"}},"type":"object","required":["background","requested","suggested","original_ratio","suggested_ratio","adjusted"],"title":"AccessibleResponse"},"cbmarketing__BrandKit":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"tenant":{"type":"string","maxLength":120,"title":"Tenant","default":"default"},"primary":{"type":"string","title":"Primary","examples":["#3b82f6"]},"secondary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secondary"},"accent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accent"},"neutral":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Neutral","examples":["#1f2937"]},"font_heading":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Font Heading","examples":["Inter"]},"font_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Font Body","examples":["Inter"]},"voice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice","description":"Brand voice / tone notes"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"integer","title":"Id"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["name","primary","id","created_at","updated_at"],"title":"BrandKit"},"cbmarketing__BrandKitInput":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"tenant":{"type":"string","maxLength":120,"title":"Tenant","default":"default"},"primary":{"type":"string","title":"Primary","examples":["#3b82f6"]},"secondary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secondary"},"accent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accent"},"neutral":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Neutral","examples":["#1f2937"]},"font_heading":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Font Heading","examples":["Inter"]},"font_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Font Body","examples":["Inter"]},"voice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice","description":"Brand voice / tone notes"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name","primary"],"title":"BrandKitInput"},"cbmarketing__BrandKitList":{"properties":{"brands":{"items":{"$ref":"#/components/schemas/cbmarketing__BrandKit"},"type":"array","title":"Brands"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["brands","count"],"title":"BrandKitList"},"cbmarketing__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbmarketing__ContrastRequest":{"properties":{"foreground":{"type":"string","title":"Foreground","examples":["#1f2937"]},"background":{"type":"string","title":"Background","examples":["#ffffff"]}},"type":"object","required":["foreground","background"],"title":"ContrastRequest"},"cbmarketing__ContrastResponse":{"properties":{"foreground":{"type":"string","title":"Foreground"},"background":{"type":"string","title":"Background"},"ratio":{"type":"number","title":"Ratio"},"levels":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Levels"},"passes_aa":{"type":"boolean","title":"Passes Aa"}},"type":"object","required":["foreground","background","ratio","levels","passes_aa"],"title":"ContrastResponse"},"cbmarketing__ConvertResponse":{"properties":{"hex":{"type":"string","title":"Hex"},"rgb":{"prefixItems":[{"type":"integer"},{"type":"integer"},{"type":"integer"}],"type":"array","maxItems":3,"minItems":3,"title":"Rgb"},"hsl":{"prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"type":"array","maxItems":3,"minItems":3,"title":"Hsl"}},"type":"object","required":["hex","rgb","hsl"],"title":"ConvertResponse"},"cbmarketing__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbmarketing__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbmarketing__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbmarketing__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbmarketing__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbmarketing__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbmarketing__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbmarketing__PaletteDecisionRequest":{"properties":{"mood":{"type":"string","title":"Mood","description":"Desired emotional tone","default":"trustworthy","examples":["trustworthy","energetic","calm","luxury","playful","urgent"]},"base":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base","description":"Optional anchor color; otherwise derived from mood"},"industry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry","examples":["political","nonprofit","tech"]},"with_rationale":{"type":"boolean","title":"With Rationale","description":"Ask cbai (if reachable) for a one-paragraph rationale","default":false}},"type":"object","title":"PaletteDecisionRequest"},"cbmarketing__PaletteDecisionResponse":{"properties":{"mood":{"type":"string","title":"Mood"},"base":{"type":"string","title":"Base"},"primary":{"type":"string","title":"Primary"},"secondary":{"type":"string","title":"Secondary"},"accent":{"type":"string","title":"Accent"},"neutral":{"type":"string","title":"Neutral"},"suggested_text_on_primary":{"type":"string","title":"Suggested Text On Primary"},"rationale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rationale"},"source":{"type":"string","title":"Source","description":"heuristic | cbai","default":"heuristic"}},"type":"object","required":["mood","base","primary","secondary","accent","neutral","suggested_text_on_primary"],"title":"PaletteDecisionResponse"},"cbmarketing__PaletteRequest":{"properties":{"base":{"type":"string","title":"Base","examples":["#3b82f6"]},"scheme":{"type":"string","title":"Scheme","default":"complementary","examples":["complementary","analogous","triadic","tetradic","split-complementary","monochromatic"]}},"type":"object","required":["base"],"title":"PaletteRequest"},"cbmarketing__PaletteResponse":{"properties":{"base":{"type":"string","title":"Base"},"scheme":{"type":"string","title":"Scheme"},"colors":{"items":{"type":"string"},"type":"array","title":"Colors"}},"type":"object","required":["base","scheme","colors"],"title":"PaletteResponse"},"cbmarketing__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbmarketing__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbmarketing__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbmarketing__RampResponse":{"properties":{"base":{"type":"string","title":"Base"},"ramp":{"additionalProperties":{"type":"string"},"type":"object","title":"Ramp"}},"type":"object","required":["base","ramp"],"title":"RampResponse"},"cbmarketing__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbmarketing__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbmarketing__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbmobile__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbmobile__Contact":{"properties":{"id":{"type":"integer","title":"Id","description":"Stable contact identifier (walkbook row id)."},"first_name":{"type":"string","title":"First Name","description":"Contact's first name."},"last_name":{"type":"string","title":"Last Name","description":"Contact's last name."},"address":{"type":"string","title":"Address","description":"Street address used for door-to-door routing."},"latitude":{"type":"number","title":"Latitude","description":"Address latitude in decimal degrees (WGS84)."},"longitude":{"type":"number","title":"Longitude","description":"Address longitude in decimal degrees (WGS84)."},"status":{"type":"string","title":"Status","description":"Canvassing status, e.g. 'pending' or 'completed'.","default":"pending"}},"type":"object","required":["id","first_name","last_name","address","latitude","longitude"],"title":"Contact","description":"A canvassing contact (stub model for development).\n\nRepresents one person on a walk list. Currently returned as hardcoded stub\ndata; the real values will come from the walkbook DuckDB store.","example":{"address":"123 Main St","first_name":"John","id":1,"last_name":"Doe","latitude":37.7749,"longitude":-122.4194,"status":"pending"}},"cbmobile__ContactList":{"properties":{"contacts":{"items":{"$ref":"#/components/schemas/cbmobile__Contact"},"type":"array","title":"Contacts","description":"The contacts on this page, after applying limit/offset/status."},"total":{"type":"integer","title":"Total","description":"Total number of matching contacts before pagination is applied."}},"type":"object","required":["contacts","total"],"title":"ContactList","description":"Paginated list of canvassing contacts.","example":{"contacts":[{"address":"123 Main St","first_name":"John","id":1,"last_name":"Doe","latitude":37.7749,"longitude":-122.4194,"status":"pending"}],"total":1}},"cbmobile__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbmobile__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbmobile__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbmobile__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbmobile__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbmobile__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbmobile__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbmobile__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbmobile__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbmobile__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbmobile__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbmobile__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbmobile__SyncCheckpoint":{"properties":{"checkpoint":{"type":"string","title":"Checkpoint","description":"Opaque checkpoint identifier the client replays from after reconnecting (e.g. 'initial')."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"UTC timestamp of when this checkpoint was last advanced."}},"type":"object","required":["checkpoint","updated_at"],"title":"SyncCheckpoint","description":"Current sync checkpoint for offline sync recovery.","example":{"checkpoint":"initial","updated_at":"2026-06-21T00:00:00Z"}},"cbmobile__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbmodels__AddMonthRequest":{"properties":{"month":{"type":"string","pattern":"^\\d{4}-\\d{2}(-01)?$","title":"Month"},"cohort_id":{"type":"integer","maximum":100000.0,"minimum":1.0,"title":"Cohort Id","default":60}},"additionalProperties":false,"type":"object","required":["month"],"title":"AddMonthRequest","description":"+Add payload — everything the build needs that varies per month."},"cbmodels__AddressCleanBatchRequest":{"properties":{"addresses":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1,"title":"Addresses","description":"List of raw address strings to clean and geocode. Maximum 100 addresses per request.","examples":[["123 Main St, Detroit MI","456 Oak Ave, Ann Arbor MI 48104"]]},"include_district":{"type":"boolean","title":"Include District","description":"Enrich all addresses with congressional district lookup. Adds extra API calls, increasing total processing time.","default":false}},"additionalProperties":false,"type":"object","required":["addresses"],"title":"AddressCleanBatchRequest","description":"Request to clean and geocode multiple addresses.\n\nAddresses are processed concurrently for efficiency. Maximum 100 addresses\nper request to avoid overwhelming the geocoding service.","example":{"addresses":["123 Main St, Detroit MI","456 Oak Ave, Ann Arbor MI 48104"],"include_district":false}},"cbmodels__AddressCleanBatchResponse":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Unique identifier for this request. Use for debugging/logging.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"cleaned_at":{"type":"string","format":"date-time","title":"Cleaned At","description":"Timestamp when cleaning was performed (UTC)."},"total":{"type":"integer","title":"Total","description":"Total number of addresses in the request.","examples":[2]},"matched":{"type":"integer","title":"Matched","description":"Number of addresses successfully geocoded.","examples":[2]},"failed":{"type":"integer","title":"Failed","description":"Number of addresses that failed to geocode.","examples":[0]},"results":{"items":{"$ref":"#/components/schemas/cbmodels__CleanedAddress"},"type":"array","title":"Results","description":"Cleaned address results in the same order as the input. Check the 'matched' field on each result to identify failures."},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Total processing time in milliseconds.","examples":[512.7]}},"type":"object","required":["request_id","cleaned_at","total","matched","failed","results","query_time_ms"],"title":"AddressCleanBatchResponse","description":"Response from batch address cleaning endpoint.\n\nContains results for all addresses with summary statistics.\nSupports partial success - some addresses may fail while others succeed.","example":{"cleaned_at":"2026-01-28T10:30:00Z","failed":0,"matched":2,"query_time_ms":512.7,"request_id":"550e8400-e29b-41d4-a716-446655440000","results":[{"city":"Detroit","confidence":0.92,"display_name":"123 Main Street, Detroit, MI 48201, USA","input":"123 Main St, Detroit MI","latitude":42.3314,"longitude":-83.0458,"matched":true,"postal_code":"48201","state":"Michigan","street":"123 Main Street"},{"city":"Ann Arbor","confidence":0.94,"display_name":"456 Oak Avenue, Ann Arbor, MI 48104, USA","input":"456 Oak Ave, Ann Arbor MI 48104","latitude":42.2808,"longitude":-83.743,"matched":true,"postal_code":"48104","state":"Michigan","street":"456 Oak Avenue"}],"total":2}},"cbmodels__AddressCleanRequest":{"properties":{"address":{"type":"string","maxLength":500,"minLength":1,"title":"Address","description":"Raw address string to clean and geocode. Can be partial or complete. Examples: '1600 Pennsylvania Ave NW, Washington DC', '123 Main St, Detroit MI 48226'","examples":["1600 Pennsylvania Ave NW, Washington DC","123 Main St, Detroit MI 48226"]},"include_district":{"type":"boolean","title":"Include District","description":"Enrich with congressional district lookup. When true, uses the geocoded coordinates to look up the congressional district (e.g., 'MI-07'). This adds an extra API call, increasing latency.","default":false}},"additionalProperties":false,"type":"object","required":["address"],"title":"AddressCleanRequest","description":"Request to clean and geocode a single address.\n\nThe address will be standardized and geocoded using geo.campaignbrain.dev.\nOptionally, congressional district information can be added using\ndistricts.campaignbrain.dev.","examples":[{"address":"1600 Pennsylvania Ave NW, Washington DC","include_district":true},{"address":"123 Main St, Detroit MI 48226","include_district":false}]},"cbmodels__AddressCleanResponse":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Unique identifier for this request. Use for debugging/logging.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"cleaned_at":{"type":"string","format":"date-time","title":"Cleaned At","description":"Timestamp when cleaning was performed (UTC)."},"result":{"$ref":"#/components/schemas/cbmodels__CleanedAddress","description":"Cleaned and geocoded address result."},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Total processing time in milliseconds.","examples":[245.3]}},"type":"object","required":["request_id","cleaned_at","result","query_time_ms"],"title":"AddressCleanResponse","description":"Response from single address cleaning endpoint.\n\nContains the cleaned address result along with request metadata.","example":{"cleaned_at":"2026-01-28T10:30:00Z","query_time_ms":245.3,"request_id":"550e8400-e29b-41d4-a716-446655440000","result":{"city":"Washington","confidence":0.95,"congressional_district":"DC-00","display_name":"1600 Pennsylvania Avenue NW, Washington, DC 20500, USA","input":"1600 Pennsylvania Ave NW, Washington DC","latitude":38.8977,"longitude":-77.0365,"matched":true,"postal_code":"20500","state":"District of Columbia","street":"1600 Pennsylvania Avenue NW"}}},"cbmodels__AirtableSyncRequest":{"properties":{"mode":{"type":"string","pattern":"^(auto|full|incremental)$","title":"Mode","default":"auto"}},"additionalProperties":false,"type":"object","title":"AirtableSyncRequest"},"cbmodels__ApiCountRequest":{"properties":{"state":{"type":"string","maxLength":2,"minLength":2,"title":"State","description":"Two-letter state code"},"county":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"County","description":"Numeric i360 county ID (omit for statewide)"},"affinity_min":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Affinity Min","description":"Min political affinity. Must pair with max."},"affinity_max":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Affinity Max","description":"Max political affinity. Must pair with min."},"propensity_min":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Propensity Min","description":"Min voting propensity (1=most likely). Must pair with max."},"propensity_max":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Propensity Max","description":"Max voting propensity. Must pair with min."}},"additionalProperties":false,"type":"object","required":["state"],"title":"ApiCountRequest","description":"Request for i360 API voter count (any US state)."},"cbmodels__ApiCountResponse":{"properties":{"state":{"type":"string","title":"State"},"county":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"County"},"individuals":{"type":"integer","title":"Individuals"},"residences":{"type":"integer","title":"Residences"},"apartments":{"type":"integer","title":"Apartments"},"mailing_addresses":{"type":"integer","title":"Mailing Addresses"},"landlines":{"type":"integer","title":"Landlines"},"mobiles":{"type":"integer","title":"Mobiles"},"all_phones":{"type":"integer","title":"All Phones"},"query_time_ms":{"type":"number","title":"Query Time Ms"}},"type":"object","required":["state","individuals","residences","apartments","mailing_addresses","landlines","mobiles","all_phones","query_time_ms"],"title":"ApiCountResponse","description":"Response from i360 API count query."},"cbmodels__ArchiveAction":{"properties":{"archived":{"type":"boolean","title":"Archived","description":"True = soft-delete (archive), False = restore","default":true},"actor":{"type":"string","title":"Actor","description":"User performing the action","default":"unknown"}},"additionalProperties":false,"type":"object","title":"ArchiveAction"},"cbmodels__ArmBody":{"properties":{"actor":{"type":"string","title":"Actor","description":"User arming the job","default":"unknown"},"confirm":{"type":"boolean","title":"Confirm","description":"False = dry run: report what would be armed, subscribe nothing","default":false}},"additionalProperties":false,"type":"object","title":"ArmBody"},"cbmodels__BaselineSummaryResponse":{"properties":{"database":{"type":"string","title":"Database","description":"Path or name of the source database."},"created_at":{"type":"string","title":"Created At","description":"When the baseline model was created."},"tables":{"type":"integer","title":"Tables","description":"Number of tables in the database."},"total_rows":{"type":"integer","title":"Total Rows","description":"Total rows across all tables."},"total_columns":{"type":"integer","title":"Total Columns","description":"Total columns across all tables."},"correlations_found":{"type":"integer","title":"Correlations Found","description":"Number of significant correlations detected."},"outliers_found":{"type":"integer","title":"Outliers Found","description":"Number of outlier patterns detected."},"patterns_found":{"type":"integer","title":"Patterns Found","description":"Number of data patterns (missing, imbalance, etc.) detected."}},"type":"object","required":["database","created_at","tables","total_rows","total_columns","correlations_found","outliers_found","patterns_found"],"title":"BaselineSummaryResponse","description":"Summary of the pre-computed baseline model.\n\nUse this to understand what data is available and the scope of the baseline.","example":{"correlations_found":23,"created_at":"2025-12-26T00:00:00Z","database":"pocket.db","outliers_found":8,"patterns_found":15,"tables":5,"total_columns":45,"total_rows":2150000}},"cbmodels__BehavioralEnrichmentRequest":{"properties":{"svids":{"items":{"type":"string"},"type":"array","maxItems":100000,"minItems":1,"title":"Svids","description":"List of State Voter IDs to enrich with behavioral data. The API will find matching emails and return engagement metrics.","examples":[["FL-123456789","FL-987654321"]]},"include_client_affinity":{"type":"boolean","title":"Include Client Affinity","description":"Include breakdown of engagement by client/organization.","default":true},"include_donation_history":{"type":"boolean","title":"Include Donation History","description":"Include donation metrics for the segment.","default":true}},"additionalProperties":false,"type":"object","required":["svids"],"title":"BehavioralEnrichmentRequest","description":"Request for behavioral enrichment of a voter segment.\n\nEnriches SVIDs with email engagement, donation history, and client affinity data.","example":{"include_client_affinity":true,"include_donation_history":true,"svids":["FL-123456789","FL-987654321","FL-456789123"]}},"cbmodels__BehavioralEnrichmentResponse":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Unique request identifier."},"analyzed_at":{"type":"string","format":"date-time","title":"Analyzed At","description":"Timestamp of analysis."},"svids_provided":{"type":"integer","title":"Svids Provided","description":"Number of SVIDs in request."},"svids_matched":{"type":"integer","title":"Svids Matched","description":"SVIDs matched to person records."},"emails_matched":{"type":"integer","title":"Emails Matched","description":"Persons with emails found in behavioral data."},"match_rate":{"type":"number","title":"Match Rate","description":"Percentage of SVIDs with behavioral data (emails_matched / svids_provided * 100)."},"segment_is_clicker_rate":{"type":"number","title":"Segment Is Clicker Rate","description":"Percentage of matched segment that has clicked emails."},"segment_is_donor_rate":{"type":"number","title":"Segment Is Donor Rate","description":"Percentage of matched segment that has donated."},"avg_campaigns_engaged":{"type":"number","title":"Avg Campaigns Engaged","description":"Average campaigns per person in segment."},"avg_clicks_per_person":{"type":"number","title":"Avg Clicks Per Person","description":"Average email clicks per person in segment."},"client_affinity":{"items":{"$ref":"#/components/schemas/cbmodels__ClientAffinity"},"type":"array","title":"Client Affinity","description":"Engagement breakdown by client, sorted by affinity score."},"donation_summary":{"anyOf":[{"$ref":"#/components/schemas/cbmodels__DonationSummary"},{"type":"null"}],"description":"Aggregate donation metrics for the segment."},"profiles":{"items":{"$ref":"#/components/schemas/cbmodels__BehavioralProfile"},"type":"array","title":"Profiles","description":"Individual behavioral profiles. Only included for segments < 1000. For larger segments, use aggregate metrics."},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Query execution time in milliseconds."}},"type":"object","required":["request_id","analyzed_at","svids_provided","svids_matched","emails_matched","match_rate","segment_is_clicker_rate","segment_is_donor_rate","avg_campaigns_engaged","avg_clicks_per_person","query_time_ms"],"title":"BehavioralEnrichmentResponse","description":"Response with behavioral enrichment data for a segment.","example":{"analyzed_at":"2025-12-26T10:30:00Z","avg_campaigns_engaged":4.2,"avg_clicks_per_person":2.8,"client_affinity":[{"affinity_score":18.5,"click_rate":15.2,"client_name":"TPUSA","open_rate":25.0,"total_clicks":95,"total_opens":625,"total_sends":2500,"unique_contacts":450}],"donation_summary":{"avg_donations_per_donor":2.3,"donor_rate":8.5,"total_donations":120,"total_donors":52},"emails_matched":612,"match_rate":61.2,"profiles":[],"query_time_ms":245.8,"request_id":"550e8400-e29b-41d4-a716-446655440000","segment_is_clicker_rate":35.2,"segment_is_donor_rate":8.5,"svids_matched":823,"svids_provided":1000}},"cbmodels__BehavioralProfile":{"properties":{"svid":{"type":"string","title":"Svid","description":"State Voter ID."},"email_matched":{"type":"boolean","title":"Email Matched","description":"Whether email was found in behavioral data."},"is_clicker":{"type":"boolean","title":"Is Clicker","description":"Has clicked at least one email."},"is_donor":{"type":"boolean","title":"Is Donor","description":"Has made at least one donation."},"total_campaigns":{"type":"integer","title":"Total Campaigns","description":"Number of campaigns engaged with."},"total_clicks":{"type":"integer","title":"Total Clicks","description":"Total email clicks."},"total_opens":{"type":"integer","title":"Total Opens","description":"Total email opens."},"clients_engaged":{"type":"integer","title":"Clients Engaged","description":"Number of distinct clients engaged with."},"donation_count":{"type":"integer","title":"Donation Count","description":"Number of donations on record."},"last_engagement":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Engagement","description":"Date of most recent email engagement (ISO format)."},"last_donation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Donation","description":"Date of most recent donation (ISO format)."},"donor_propensity":{"type":"number","title":"Donor Propensity","description":"Likelihood score for future donation (0-1). Based on engagement history and past donations.","examples":[0.65]}},"type":"object","required":["svid","email_matched","is_clicker","is_donor","total_campaigns","total_clicks","total_opens","clients_engaged","donation_count","donor_propensity"],"title":"BehavioralProfile","description":"Behavioral profile for a single voter."},"cbmodels__Body_create_job_api_v1_cc_suppression_jobs_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"actor":{"type":"string","title":"Actor","default":"unknown"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"list_ids":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"List Ids"}},"type":"object","required":["file"],"title":"Body_create_job_api_v1_cc_suppression_jobs_post"},"cbmodels__Body_probe_file_endpoint_api_v1_intake_probe_file_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_probe_file_endpoint_api_v1_intake_probe_file_post"},"cbmodels__CampaignDuplicate":{"properties":{"value":{"type":"string","title":"Value","description":"The duplicate value (email, phone, etc.).","examples":["john@example.com"]},"source_count":{"type":"integer","title":"Source Count","description":"Number of sources containing this value."},"sources":{"items":{"type":"string"},"type":"array","title":"Sources","description":"Names of source files containing this value.","examples":[["Donors.csv","Volunteers.xlsx","Rally.csv"]]}},"type":"object","required":["value","source_count","sources"],"title":"CampaignDuplicate","description":"A contact value appearing in multiple campaign sources."},"cbmodels__CampaignDuplicatesRequest":{"properties":{"field":{"type":"string","title":"Field","description":"Field to check for duplicates.","default":"email","examples":["email","phone","first_name"]},"min_sources":{"type":"integer","minimum":2.0,"title":"Min Sources","description":"Minimum number of sources to be considered a duplicate.","default":2},"limit":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Limit","description":"Maximum number of duplicates to return.","default":50}},"additionalProperties":false,"type":"object","title":"CampaignDuplicatesRequest","description":"Request to find cross-source duplicates.","example":{"field":"email","limit":50,"min_sources":3}},"cbmodels__CampaignDuplicatesResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant slug."},"field":{"type":"string","title":"Field","description":"Field checked for duplicates."},"min_sources":{"type":"integer","title":"Min Sources","description":"Minimum sources threshold used."},"duplicate_count":{"type":"integer","title":"Duplicate Count","description":"Number of duplicate values found."},"duplicates":{"items":{"$ref":"#/components/schemas/cbmodels__CampaignDuplicate"},"type":"array","title":"Duplicates","description":"Duplicate values sorted by source count descending."},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Query execution time in milliseconds."}},"type":"object","required":["tenant_id","field","min_sources","duplicate_count","duplicates","query_time_ms"],"title":"CampaignDuplicatesResponse","description":"Response with cross-source duplicates."},"cbmodels__CampaignEnrichRequest":{"properties":{"emails":{"items":{"type":"string"},"type":"array","title":"Emails","description":"Email addresses to look up.","examples":[["john@example.com","jane@example.com"]]},"phones":{"items":{"type":"string"},"type":"array","title":"Phones","description":"Phone numbers to look up.","examples":[["555-123-4567"]]}},"additionalProperties":false,"type":"object","title":"CampaignEnrichRequest","description":"Request to enrich contacts with campaign data.","example":{"emails":["john@example.com","jane@example.com"],"phones":[]}},"cbmodels__CampaignEnrichResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant slug."},"contacts_requested":{"type":"integer","title":"Contacts Requested","description":"Number of contacts in the request."},"contacts_matched":{"type":"integer","title":"Contacts Matched","description":"Number of contacts found in campaign data."},"match_rate":{"type":"number","title":"Match Rate","description":"Percentage matched (0-100)."},"enrichments":{"items":{"$ref":"#/components/schemas/cbmodels__CampaignEnrichment"},"type":"array","title":"Enrichments","description":"Enrichment data for matched contacts."},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Query execution time in milliseconds."}},"type":"object","required":["tenant_id","contacts_requested","contacts_matched","match_rate","enrichments","query_time_ms"],"title":"CampaignEnrichResponse","description":"Response with campaign enrichment for multiple contacts."},"cbmodels__CampaignEnrichment":{"properties":{"lookup_value":{"type":"string","title":"Lookup Value","description":"The email or phone used for lookup."},"lookup_field":{"type":"string","title":"Lookup Field","description":"Field used: 'email' or 'phone'."},"source_count":{"type":"integer","title":"Source Count","description":"Number of campaign sources this contact appears in."},"source_types":{"items":{"type":"string"},"type":"array","title":"Source Types","description":"Types of sources (if classified): donor, volunteer, event, etc."},"sources":{"items":{"type":"string"},"type":"array","title":"Sources","description":"Names of source files containing this contact."},"first_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Seen","description":"Earliest date found in campaign data (if date fields exist)."},"last_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen","description":"Most recent date in campaign data (if date fields exist)."}},"type":"object","required":["lookup_value","lookup_field","source_count","sources"],"title":"CampaignEnrichment","description":"Campaign enrichment data for a single contact."},"cbmodels__CampaignLookupRequest":{"properties":{"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Email address to search across campaign sources.","examples":["john@example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Phone number to search (any format, digits extracted).","examples":["555-123-4567","5551234567"]}},"additionalProperties":false,"type":"object","title":"CampaignLookupRequest","description":"Request to find campaign records by email or phone.","examples":[{"email":"john@example.com"},{"phone":"555-123-4567"}]},"cbmodels__CampaignLookupResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant slug."},"search_field":{"type":"string","title":"Search Field","description":"Field searched: 'email' or 'phone'."},"search_value":{"type":"string","title":"Search Value","description":"Value searched for."},"match_count":{"type":"integer","title":"Match Count","description":"Number of matching records."},"source_count":{"type":"integer","title":"Source Count","description":"Number of distinct sources with matches."},"records":{"items":{"$ref":"#/components/schemas/cbmodels__CampaignRecord"},"type":"array","title":"Records","description":"Matching records with full field data."},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Query execution time in milliseconds."}},"type":"object","required":["tenant_id","search_field","search_value","match_count","source_count","records","query_time_ms"],"title":"CampaignLookupResponse","description":"Response with campaign records matching the search."},"cbmodels__CampaignRecord":{"properties":{"source_name":{"type":"string","title":"Source Name","description":"Name of the source file.","examples":["2024_Donors.csv"]},"source_id":{"type":"string","title":"Source Id","description":"Unique hash of the source file."},"row_idx":{"type":"integer","title":"Row Idx","description":"Row index within the source file."},"fields":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":"object","title":"Fields","description":"Field name -> value mapping for this record.","examples":[{"amount":"250.00","email":"john@example.com","first_name":"John","last_name":"Smith"}]}},"type":"object","required":["source_name","source_id","row_idx","fields"],"title":"CampaignRecord","description":"A single record from campaign data."},"cbmodels__CampaignSource":{"properties":{"source_path":{"type":"string","title":"Source Path","description":"Original file path.","examples":["data/sources/2024_Donors.csv"]},"source_name":{"type":"string","title":"Source Name","description":"File name only.","examples":["2024_Donors.csv"]},"source_id":{"type":"string","title":"Source Id","description":"Unique hash of the source file."},"row_count":{"type":"integer","title":"Row Count","description":"Number of rows in the source."},"col_count":{"type":"integer","title":"Col Count","description":"Number of columns in the source."},"cell_count":{"type":"integer","title":"Cell Count","description":"Total cells (rows * cols)."},"fields":{"items":{"type":"string"},"type":"array","title":"Fields","description":"List of normalized field names in this source.","examples":[["email","first_name","last_name","amount"]]}},"type":"object","required":["source_path","source_name","source_id","row_count","col_count","cell_count","fields"],"title":"CampaignSource","description":"Summary of an ingested campaign source file."},"cbmodels__CampaignSourcesResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant slug."},"source_count":{"type":"integer","title":"Source Count","description":"Number of source files."},"total_rows":{"type":"integer","title":"Total Rows","description":"Total rows across all sources."},"total_cells":{"type":"integer","title":"Total Cells","description":"Total cells across all sources."},"sources":{"items":{"$ref":"#/components/schemas/cbmodels__CampaignSource"},"type":"array","title":"Sources","description":"List of source files with statistics."},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Query execution time in milliseconds."}},"type":"object","required":["tenant_id","source_count","total_rows","total_cells","sources","query_time_ms"],"title":"CampaignSourcesResponse","description":"Response listing all ingested campaign sources for a tenant."},"cbmodels__ClaimResponse":{"properties":{"dataset_id":{"type":"string","title":"Dataset Id"},"owner_tenant":{"type":"string","title":"Owner Tenant"},"visibility":{"type":"string","title":"Visibility"},"claimed_by_user":{"type":"string","title":"Claimed By User"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["dataset_id","owner_tenant","visibility","claimed_by_user","updated_at"],"title":"ClaimResponse","description":"Returned from POST /datasets/{id}/claim — the dataset's new ownership state."},"cbmodels__CleanedAddress":{"properties":{"input":{"type":"string","title":"Input","description":"Original input address string.","examples":["1600 Pennsylvania Ave NW, Washington DC"]},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name","description":"Full standardized address from the geocoding service.","examples":["1600 Pennsylvania Avenue NW, Washington, DC 20500, USA"]},"street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Street","description":"Street address including house number.","examples":["1600 Pennsylvania Avenue NW"]},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"City or locality name.","examples":["Washington"]},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County","description":"County name.","examples":["District of Columbia"]},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State or province name.","examples":["District of Columbia"]},"postal_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Postal Code","description":"ZIP or postal code.","examples":["20500"]},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"Country name.","examples":["United States"]},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude","description":"Latitude coordinate (WGS84).","examples":[38.8977]},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude","description":"Longitude coordinate (WGS84).","examples":[-77.0365]},"congressional_district":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Congressional District","description":"Congressional district code (e.g., 'MI-07', 'DC-00'). Only populated if include_district=True in the request.","examples":["DC-00","MI-07"]},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence","description":"Confidence score (0-1) based on geocoding service importance score. Higher values indicate more confident matches.","default":0.0,"examples":[0.95]},"matched":{"type":"boolean","title":"Matched","description":"Whether geocoding succeeded. False if address could not be resolved.","default":false},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if geocoding failed. None on success.","examples":["Address not found","Geocoding service timeout"]}},"type":"object","required":["input"],"title":"CleanedAddress","description":"Standardized and geocoded address result.\n\nContains the original input, standardized address components, coordinates,\nand optional congressional district information.","example":{"city":"Washington","confidence":0.95,"congressional_district":"DC-00","country":"United States","county":"District of Columbia","display_name":"1600 Pennsylvania Avenue NW, Washington, DC 20500, USA","input":"1600 Pennsylvania Ave NW, Washington DC","latitude":38.8977,"longitude":-77.0365,"matched":true,"postal_code":"20500","state":"District of Columbia","street":"1600 Pennsylvania Avenue NW"}},"cbmodels__ClientAffinity":{"properties":{"client_name":{"type":"string","title":"Client Name","description":"Short name of the client organization.","examples":["TPUSA","AFP","NRCC"]},"unique_contacts":{"type":"integer","title":"Unique Contacts","description":"Number of unique segment members who received emails from this client."},"total_sends":{"type":"integer","title":"Total Sends","description":"Total emails sent to this segment from this client."},"total_opens":{"type":"integer","title":"Total Opens","description":"Total email opens by this segment from this client."},"total_clicks":{"type":"integer","title":"Total Clicks","description":"Total email clicks by this segment from this client."},"open_rate":{"type":"number","title":"Open Rate","description":"Open rate as percentage (opens / sends * 100).","examples":[25.5]},"click_rate":{"type":"number","title":"Click Rate","description":"Click-through rate as percentage (clicks / opens * 100).","examples":[3.2]},"affinity_score":{"type":"number","title":"Affinity Score","description":"Weighted engagement score. Higher = stronger affinity. Formula: (clicks*3 + opens - unsubs*5) / sends * 100","examples":[12.5]}},"type":"object","required":["client_name","unique_contacts","total_sends","total_opens","total_clicks","open_rate","click_rate","affinity_score"],"title":"ClientAffinity","description":"Engagement metrics for a specific client/organization."},"cbmodels__ClientAffinityRequest":{"properties":{"svids":{"items":{"type":"string"},"type":"array","maxItems":100000,"minItems":1,"title":"Svids","description":"List of State Voter IDs to analyze client affinity for."},"top_n":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Top N","description":"Number of top clients to return, sorted by affinity score.","default":10},"min_sends":{"type":"integer","minimum":1.0,"title":"Min Sends","description":"Minimum sends required to include a client in results.","default":10}},"additionalProperties":false,"type":"object","required":["svids"],"title":"ClientAffinityRequest","description":"Request for client affinity analysis.","example":{"min_sends":10,"svids":["FL-123456789","FL-987654321"],"top_n":10}},"cbmodels__ClientAffinityResponse":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Unique request identifier."},"analyzed_at":{"type":"string","format":"date-time","title":"Analyzed At","description":"Timestamp of analysis."},"svids_provided":{"type":"integer","title":"Svids Provided","description":"Number of SVIDs in request."},"emails_matched":{"type":"integer","title":"Emails Matched","description":"Persons with behavioral data."},"clients":{"items":{"$ref":"#/components/schemas/cbmodels__ClientAffinity"},"type":"array","title":"Clients","description":"Client engagement rankings, sorted by affinity score descending."},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Query execution time in milliseconds."}},"type":"object","required":["request_id","analyzed_at","svids_provided","emails_matched","clients","query_time_ms"],"title":"ClientAffinityResponse","description":"Response with client affinity rankings for a segment."},"cbmodels__CohortRequest":{"properties":{"cohort_id":{"type":"integer","maximum":100000.0,"minimum":1.0,"title":"Cohort Id"}},"additionalProperties":false,"type":"object","required":["cohort_id"],"title":"CohortRequest"},"cbmodels__ColumnComparison":{"properties":{"column_name":{"type":"string","title":"Column Name","description":"Name of the database column being compared.","examples":["age","county","party_affiliation"]},"column_type":{"type":"string","title":"Column Type","description":"Data type classification: 'numeric' for continuous values (age, income), 'categorical' for discrete values (county, party, gender).","examples":["numeric","categorical"]},"segment_count":{"type":"integer","title":"Segment Count","description":"Total number of records in the segment for this column."},"segment_null_count":{"type":"integer","title":"Segment Null Count","description":"Number of NULL/missing values in the segment."},"segment_distinct_count":{"type":"integer","title":"Segment Distinct Count","description":"Number of unique values in the segment."},"baseline_count":{"type":"integer","title":"Baseline Count","description":"Total number of records in the baseline for this column."},"baseline_null_count":{"type":"integer","title":"Baseline Null Count","description":"Number of NULL/missing values in the baseline."},"baseline_distinct_count":{"type":"integer","title":"Baseline Distinct Count","description":"Number of unique values in the baseline."},"segment_mean":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Segment Mean","description":"Average value in segment. Only for numeric columns."},"baseline_mean":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Baseline Mean","description":"Average value in baseline. Only for numeric columns."},"mean_deviation":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mean Deviation","description":"Percentage deviation of segment mean from baseline mean. Value of 0.15 means segment is 15% higher than baseline. Negative values indicate segment is lower than baseline."},"segment_median":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Segment Median","description":"Median value in segment. Only for numeric columns."},"baseline_median":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Baseline Median","description":"Median value in baseline. Only for numeric columns."},"segment_std":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Segment Std","description":"Standard deviation in segment. Only for numeric columns."},"baseline_std":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Baseline Std","description":"Standard deviation in baseline. Only for numeric columns."},"segment_top_values":{"anyOf":[{"items":{"prefixItems":[{},{"type":"integer"},{"type":"number"}],"type":"array","maxItems":3,"minItems":3},"type":"array"},{"type":"null"}],"title":"Segment Top Values","description":"Top 10 most common values in segment. Each tuple contains: (value, count, percentage). Example: ('Miami-Dade', 150, 18.5) means 150 records (18.5%) have value 'Miami-Dade'."},"baseline_top_values":{"anyOf":[{"items":{"prefixItems":[{},{"type":"integer"},{"type":"number"}],"type":"array","maxItems":3,"minItems":3},"type":"array"},{"type":"null"}],"title":"Baseline Top Values","description":"Top 10 most common values in baseline. Same format as segment_top_values. Compare with segment to see distribution shifts."},"segment_sparkline":{"anyOf":[{"$ref":"#/components/schemas/cbmodels__SparklineData"},{"type":"null"}],"description":"Distribution data for the segment. Use for visualizing how values are distributed within this segment."},"baseline_sparkline":{"anyOf":[{"$ref":"#/components/schemas/cbmodels__SparklineData"},{"type":"null"}],"description":"Distribution data for the baseline. Compare with segment_sparkline."},"is_significant":{"type":"boolean","title":"Is Significant","description":"True if the segment deviates significantly from baseline for this column. Significance is determined by deviation_threshold in the request. AI should highlight columns where is_significant=True.","default":false}},"type":"object","required":["column_name","column_type","segment_count","segment_null_count","segment_distinct_count","baseline_count","baseline_null_count","baseline_distinct_count"],"title":"ColumnComparison","description":"Detailed comparison of a column between segment and baseline.\n\nThis is the core unit of analysis - it shows how the segment differs\nfrom the baseline for a specific data column (e.g., age, county, party).","examples":[{"baseline_count":81000,"baseline_distinct_count":67,"baseline_null_count":200,"baseline_top_values":[["Miami-Dade",12000,14.8],["Hillsborough",8500,10.5],["Orange",7200,8.9]],"column_name":"county","column_type":"categorical","is_significant":true,"segment_count":800,"segment_distinct_count":15,"segment_null_count":5,"segment_top_values":[["Miami-Dade",320,40.0],["Broward",180,22.5],["Palm Beach",120,15.0]]},{"baseline_count":81000,"baseline_distinct_count":85,"baseline_mean":45.2,"baseline_median":43.0,"baseline_null_count":100,"baseline_std":18.2,"column_name":"age","column_type":"numeric","is_significant":true,"mean_deviation":0.29,"segment_count":800,"segment_distinct_count":65,"segment_mean":58.3,"segment_median":61.0,"segment_null_count":0,"segment_std":12.5}]},"cbmodels__CommissionWrite":{"properties":{"owner_id":{"type":"string","title":"Owner Id","description":"Owner this commission is scoped to (always linked)"},"fields":{"additionalProperties":true,"type":"object","title":"Fields","description":"{salesman, rate_row_id?|rate_pct?, clients?[], lists?[], owners?[]}"},"actor":{"type":"string","title":"Actor","description":"User performing the write","default":"unknown"},"confirm":{"type":"boolean","title":"Confirm","description":"False = dry run","default":false}},"additionalProperties":false,"type":"object","required":["owner_id","fields"],"title":"CommissionWrite"},"cbmodels__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbmodels__CorrelationComparison":{"properties":{"column_a":{"type":"string","title":"Column A","description":"First column in the correlation pair.","examples":["age"]},"column_b":{"type":"string","title":"Column B","description":"Second column in the correlation pair.","examples":["donation_amount"]},"segment_correlation":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Segment Correlation","description":"Pearson correlation coefficient in segment (-1 to 1). Values near 1 indicate strong positive correlation, near -1 indicate strong negative correlation, near 0 indicates no correlation."},"baseline_correlation":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Baseline Correlation","description":"Pearson correlation coefficient in baseline (-1 to 1)."},"deviation":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Deviation","description":"Difference between segment and baseline correlation. Large absolute values indicate the segment has a different relationship between these variables than the general population."},"is_new":{"type":"boolean","title":"Is New","description":"True if this correlation exists only in segment (not in baseline). Indicates a unique relationship within this segment.","default":false}},"type":"object","required":["column_a","column_b","segment_correlation","baseline_correlation"],"title":"CorrelationComparison","description":"Correlation between two columns compared across segment and baseline.\n\nCorrelations indicate how strongly two variables are related. A correlation\nthat differs between segment and baseline suggests the segment has a unique\nrelationship between these variables."},"cbmodels__CountFilterRequest":{"properties":{"column":{"type":"string","title":"Column","description":"Column to filter on"},"operator":{"type":"string","title":"Operator","description":"Comparison operator (=, !=, >, >=, <, <=, IN, LIKE)"},"value":{"title":"Value","description":"Filter value"}},"additionalProperties":false,"type":"object","required":["column","operator","value"],"title":"CountFilterRequest","description":"A single filter for count queries."},"cbmodels__CountQueryRequest":{"properties":{"state":{"type":"string","title":"State","description":"State slug to query (lowercase full word, e.g. 'michigan', 'georgia'). Resolved via the catalog registry (`i360_{state}`).","default":"michigan"},"filters":{"items":{"$ref":"#/components/schemas/cbmodels__CountFilterRequest"},"type":"array","title":"Filters","description":"Filters to apply to the count query"}},"additionalProperties":false,"type":"object","required":["filters"],"title":"CountQueryRequest","description":"Request for voter count query."},"cbmodels__CountQueryResponse":{"properties":{"individuals":{"type":"integer","title":"Individuals","description":"Total matching individuals"},"residences":{"type":"integer","title":"Residences","description":"Unique residences (address + zip)"},"all_phones":{"type":"integer","title":"All Phones","description":"Individuals with any phone"},"unique_landlines":{"type":"integer","title":"Unique Landlines","description":"Unique landline numbers"},"unique_mobiles":{"type":"integer","title":"Unique Mobiles","description":"Unique mobile numbers"},"filters_applied":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Filters Applied","description":"Filters that were applied"},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Query execution time in ms"}},"type":"object","required":["individuals","residences","all_phones","unique_landlines","unique_mobiles","filters_applied","query_time_ms"],"title":"CountQueryResponse","description":"Response for voter count query."},"cbmodels__CountReportRequest":{"properties":{"state":{"type":"string","title":"State","description":"State slug to query (lowercase full word, e.g. 'michigan', 'georgia'). Resolved via the catalog registry (`i360_{state}`).","default":"michigan"},"filters":{"items":{"$ref":"#/components/schemas/cbmodels__CountFilterRequest"},"type":"array","title":"Filters","description":"Filters to apply to the count query"},"request_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Text","description":"Original natural language request"},"share":{"type":"boolean","title":"Share","description":"Upload JSON report to cbfiles for sharing","default":true}},"additionalProperties":false,"type":"object","required":["filters"],"title":"CountReportRequest","description":"Request for a voter count report with optional sharing."},"cbmodels__CredentialsRequest":{"properties":{"auth_kind":{"type":"string","enum":["basic","bearer","header_apikey","aws_keypair","query_token"],"title":"Auth Kind"},"values":{"additionalProperties":{"type":"string"},"type":"object","title":"Values"}},"type":"object","required":["auth_kind","values"],"title":"CredentialsRequest"},"cbmodels__DeploymentCreate":{"properties":{"fields":{"additionalProperties":true,"type":"object","title":"Fields","description":"Airtable-field-name → value (curated human fields only)"},"actor":{"type":"string","title":"Actor","description":"User performing the write","default":"unknown"},"confirm":{"type":"boolean","title":"Confirm","description":"False = dry run (validate + audit, no Airtable write)","default":false}},"additionalProperties":false,"type":"object","required":["fields"],"title":"DeploymentCreate"},"cbmodels__DeploymentUpdate":{"properties":{"fields":{"additionalProperties":true,"type":"object","title":"Fields","description":"Editable result fields only (manual stats / statuses)"},"actor":{"type":"string","title":"Actor","description":"User performing the edit","default":"unknown"},"confirm":{"type":"boolean","title":"Confirm","description":"False = dry run","default":false}},"additionalProperties":false,"type":"object","required":["fields"],"title":"DeploymentUpdate"},"cbmodels__DiscardBody":{"properties":{"actor":{"type":"string","title":"Actor","description":"User discarding the job","default":"unknown"}},"additionalProperties":false,"type":"object","title":"DiscardBody"},"cbmodels__DonationSummary":{"properties":{"total_donors":{"type":"integer","title":"Total Donors","description":"Number of unique donors in the segment."},"total_donations":{"type":"integer","title":"Total Donations","description":"Total number of donation records."},"donor_rate":{"type":"number","title":"Donor Rate","description":"Percentage of segment that has donated (donors / segment_size * 100).","examples":[8.5]},"avg_donations_per_donor":{"type":"number","title":"Avg Donations Per Donor","description":"Average number of donations per donor.","examples":[2.3]}},"type":"object","required":["total_donors","total_donations","donor_rate","avg_donations_per_donor"],"title":"DonationSummary","description":"Donation metrics for a segment."},"cbmodels__DonorPropensityProfile":{"properties":{"svid":{"type":"string","title":"Svid","description":"State Voter ID."},"propensity_score":{"type":"number","title":"Propensity Score","description":"Donation likelihood score (0-1). Higher = more likely to donate.","examples":[0.72]},"score_tier":{"type":"string","title":"Score Tier","description":"Tier classification: 'high' (>0.7), 'medium' (0.4-0.7), 'low' (<0.4).","examples":["high","medium","low"]},"is_previous_donor":{"type":"boolean","title":"Is Previous Donor","description":"Has donated before (strongest predictor)."},"engagement_level":{"type":"string","title":"Engagement Level","description":"Email engagement: 'high' (>10 clicks), 'medium' (1-10), 'low' (0).","examples":["high","medium","low"]},"features":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Features","description":"Feature values if include_features=True."}},"type":"object","required":["svid","propensity_score","score_tier","is_previous_donor","engagement_level"],"title":"DonorPropensityProfile","description":"Donor propensity score for an individual voter."},"cbmodels__DonorPropensityRequest":{"properties":{"svids":{"items":{"type":"string"},"type":"array","maxItems":100000,"minItems":1,"title":"Svids","description":"List of State Voter IDs to score for donation likelihood."},"min_score":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Min Score","description":"Only return profiles with propensity score >= this threshold.","default":0.0},"include_features":{"type":"boolean","title":"Include Features","description":"Include the feature values used to compute the score.","default":false}},"additionalProperties":false,"type":"object","required":["svids"],"title":"DonorPropensityRequest","description":"Request for donor propensity scoring.","example":{"include_features":false,"min_score":0.5,"svids":["FL-123456789","FL-987654321"]}},"cbmodels__DonorPropensityResponse":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Unique request identifier."},"analyzed_at":{"type":"string","format":"date-time","title":"Analyzed At","description":"Timestamp of analysis."},"svids_provided":{"type":"integer","title":"Svids Provided","description":"Number of SVIDs in request."},"svids_scored":{"type":"integer","title":"Svids Scored","description":"SVIDs with propensity scores."},"avg_propensity":{"type":"number","title":"Avg Propensity","description":"Average propensity score across segment."},"high_propensity_count":{"type":"integer","title":"High Propensity Count","description":"Number of voters with score > 0.7."},"medium_propensity_count":{"type":"integer","title":"Medium Propensity Count","description":"Number of voters with score 0.4-0.7."},"low_propensity_count":{"type":"integer","title":"Low Propensity Count","description":"Number of voters with score < 0.4."},"profiles":{"items":{"$ref":"#/components/schemas/cbmodels__DonorPropensityProfile"},"type":"array","title":"Profiles","description":"Individual propensity scores, sorted descending."},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Query execution time in milliseconds."}},"type":"object","required":["request_id","analyzed_at","svids_provided","svids_scored","avg_propensity","high_propensity_count","medium_propensity_count","low_propensity_count","profiles","query_time_ms"],"title":"DonorPropensityResponse","description":"Response with donor propensity scores for a segment."},"cbmodels__ExportRequest":{"properties":{"sql":{"type":"string","title":"Sql","description":"DuckDB SELECT to export IN FULL (no row cap). Same syntax as /query: address tables as `catalog_name.table_name`."},"catalogs":{"type":"string","title":"Catalogs","description":"Comma-separated catalog names to scope (empty = all visible)","default":""},"format":{"type":"string","pattern":"^(parquet|csv)$","title":"Format","description":"parquet (compact, typed — default) or csv (spreadsheets)","default":"parquet"},"name":{"type":"string","title":"Name","description":"Human name for the file, e.g. 'tx35-carveup' — becomes the download filename (slugified)","default":""},"allow_full_scan":{"type":"boolean","title":"Allow Full Scan","description":"Same guard as /query: unbounded exports of event-stream / >50M-row catalogs are refused unless the user explicitly confirmed. Never pass true on your own initiative.","default":false}},"additionalProperties":false,"type":"object","required":["sql"],"title":"ExportRequest"},"cbmodels__FilterOptions":{"properties":{"parties":{"items":{"type":"string"},"type":"array","title":"Parties","description":"Available party values"},"counties":{"items":{"type":"string"},"type":"array","title":"Counties","description":"Available county values"},"congressional_districts":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Congressional Districts","description":"Available CD values"},"genders":{"items":{"type":"string"},"type":"array","title":"Genders","description":"Available gender values"}},"type":"object","required":["parties","counties","congressional_districts","genders"],"title":"FilterOptions","description":"Available filter options for voter search."},"cbmodels__GateRequest":{"properties":{"value":{"type":"boolean","title":"Value"}},"additionalProperties":false,"type":"object","required":["value"],"title":"GateRequest"},"cbmodels__GrantRequest":{"properties":{"grantee_tenant":{"type":"string","minLength":1,"title":"Grantee Tenant"},"role":{"type":"string","pattern":"^(reader|admin)$","title":"Role","default":"reader"}},"type":"object","required":["grantee_tenant"],"title":"GrantRequest"},"cbmodels__GrantResponse":{"properties":{"dataset_id":{"type":"string","title":"Dataset Id"},"grantee_tenant":{"type":"string","title":"Grantee Tenant"},"role":{"type":"string","title":"Role"},"granted_by_user":{"type":"string","title":"Granted By User"},"granted_at":{"type":"string","title":"Granted At"}},"type":"object","required":["dataset_id","grantee_tenant","role","granted_by_user","granted_at"],"title":"GrantResponse"},"cbmodels__GrantWithDataset":{"properties":{"dataset_id":{"type":"string","title":"Dataset Id"},"grantee_tenant":{"type":"string","title":"Grantee Tenant"},"role":{"type":"string","title":"Role"},"granted_by_user":{"type":"string","title":"Granted By User"},"granted_at":{"type":"string","title":"Granted At"},"owner_tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Tenant"},"visibility":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visibility"}},"type":"object","required":["dataset_id","grantee_tenant","role","granted_by_user","granted_at"],"title":"GrantWithDataset","description":"A grant row carrying its dataset's owner_tenant + visibility, so a caller\ncan split owned-vs-granted-to-me client-side without per-dataset lookups."},"cbmodels__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbmodels__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbmodels__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbmodels__IngestRequest":{"properties":{"probe_id":{"type":"string","title":"Probe Id"},"name":{"type":"string","minLength":1,"title":"Name"},"tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant"},"visibility":{"type":"string","enum":["tenant","global","quarantine"],"title":"Visibility","default":"tenant"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"select_inner":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Select Inner"},"select_all":{"type":"boolean","title":"Select All","default":false},"column_overrides":{"additionalProperties":{"type":"string"},"type":"object","title":"Column Overrides"},"max_rows":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Rows"}},"type":"object","required":["probe_id","name"],"title":"IngestRequest"},"cbmodels__InstallPackagesRequest":{"properties":{"packages":{"type":"string","title":"Packages","description":"Comma-separated CRAN package names (e.g. 'lme4, brms, sf')."},"timeout_s":{"type":"integer","title":"Timeout S","description":"Timeout in seconds (max 1800).","default":600}},"additionalProperties":false,"type":"object","required":["packages"],"title":"InstallPackagesRequest"},"cbmodels__InstallPackagesResponse":{"properties":{"installed":{"items":{"type":"string"},"type":"array","title":"Installed"},"already_present":{"items":{"type":"string"},"type":"array","title":"Already Present"},"failed":{"items":{"type":"string"},"type":"array","title":"Failed"},"stdout":{"type":"string","title":"Stdout"},"stderr":{"type":"string","title":"Stderr"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["installed","already_present","failed","stdout","stderr","error"],"title":"InstallPackagesResponse"},"cbmodels__LedgerSave":{"properties":{"month":{"type":"string","title":"Month","description":"YYYY-MM (keyed to first of month)"},"fields":{"additionalProperties":true,"type":"object","title":"Fields","description":"Ledger line items + Status"},"actor":{"type":"string","title":"Actor","default":"unknown"},"confirm":{"type":"boolean","title":"Confirm","description":"False = dry run","default":false}},"additionalProperties":false,"type":"object","required":["month","fields"],"title":"LedgerSave"},"cbmodels__MasterRun":{"properties":{"month":{"type":"string","title":"Month","description":"YYYY-MM"},"dry_run":{"type":"boolean","title":"Dry Run","description":"Dry run unless explicitly false","default":true},"confirm":{"type":"boolean","title":"Confirm","description":"Must be true to launch","default":false},"actor":{"type":"string","title":"Actor","default":"unknown"},"force":{"type":"boolean","title":"Force","description":"Launch despite blocking readiness failures","default":false},"force_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Force Reason","description":"Required when force=true (audit trail)"}},"additionalProperties":false,"type":"object","required":["month"],"title":"MasterRun"},"cbmodels__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbmodels__OutlierComparison":{"properties":{"column_name":{"type":"string","title":"Column Name","description":"Column being analyzed for outliers."},"segment_outlier_count":{"type":"integer","title":"Segment Outlier Count","description":"Number of outlier values in the segment."},"baseline_outlier_count":{"type":"integer","title":"Baseline Outlier Count","description":"Number of outlier values in the baseline."},"segment_outlier_rate":{"type":"number","title":"Segment Outlier Rate","description":"Percentage of segment values that are outliers (0-100)."},"baseline_outlier_rate":{"type":"number","title":"Baseline Outlier Rate","description":"Percentage of baseline values that are outliers (0-100)."},"sample_values":{"items":{},"type":"array","title":"Sample Values","description":"Sample of actual outlier values from the segment for context."}},"type":"object","required":["column_name","segment_outlier_count","baseline_outlier_count","segment_outlier_rate","baseline_outlier_rate"],"title":"OutlierComparison","description":"Outlier comparison between segment and baseline.\n\nOutliers are values that fall outside the normal range (beyond IQR bounds).\nComparing outlier rates reveals if the segment has more extreme values."},"cbmodels__OwnerWrite":{"properties":{"fields":{"additionalProperties":true,"type":"object","title":"Fields","description":"Owner fields keyed by Airtable field name; link fields are arrays of kbids"},"actor":{"type":"string","title":"Actor","description":"User performing the write","default":"unknown"},"confirm":{"type":"boolean","title":"Confirm","description":"False = dry run (validate + audit only)","default":false}},"additionalProperties":false,"type":"object","required":["fields"],"title":"OwnerWrite"},"cbmodels__PatternAlert":{"properties":{"pattern_type":{"type":"string","title":"Pattern Type","description":"Type of pattern detected. Common types: 'missing_data' (high null rate), 'class_imbalance' (one value dominates), 'skewed_distribution' (asymmetric), 'unique_concentration' (segment has values not in baseline).","examples":["missing_data","class_imbalance","skewed_distribution"]},"severity":{"type":"string","title":"Severity","description":"Severity level: 'low', 'medium', 'high'. High severity patterns should be highlighted.","examples":["low","medium","high"]},"description":{"type":"string","title":"Description","description":"Human-readable description of the pattern.","examples":["Segment has 3x higher missing data rate for email column","95% of segment is concentrated in 3 counties vs 15% in baseline"]},"columns":{"items":{"type":"string"},"type":"array","title":"Columns","description":"Columns affected by this pattern."},"details":{"additionalProperties":true,"type":"object","title":"Details","description":"Additional structured details about the pattern for programmatic use."}},"type":"object","required":["pattern_type","severity","description","columns"],"title":"PatternAlert","description":"Alert for a detected data pattern that differs from baseline.\n\nPatterns include missing data concentrations, class imbalances, skewed\ndistributions, and other data quality issues."},"cbmodels__PipelineCreateRequest":{"properties":{"name":{"type":"string","maxLength":63,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_]*$","title":"Name","description":"Pipeline slug, unique per tenant (lowercase/underscore).","examples":["mi_engagers_90d"]},"sql":{"type":"string","title":"Sql","description":"The script — a single result-producing SELECT/CTE over registered federation catalogs (catalog.table addressing).","examples":["SELECT email, st FROM cc_events.email_click_dedup WHERE st='MI'"]},"description":{"type":"string","title":"Description","description":"What the output dataset IS — drives chat routing; be specific.","default":"","examples":["MI voters who clicked a CC email in the last 90 days"]},"visibility":{"type":"string","pattern":"^(private|tenant|global)$","title":"Visibility","description":"'private' (creator-only), 'tenant' (default), or 'global' (fleet-visible — requires fabric.pipelines.admin, i.e. super users; visibility model v2, 2026-07-07).","default":"tenant"}},"additionalProperties":false,"type":"object","required":["name","sql"],"title":"PipelineCreateRequest","description":"Create a pipeline: compiles v1, does NOT run it."},"cbmodels__PortalSearchRequest":{"properties":{"state":{"type":"string","title":"State","description":"2-letter state code (e.g. MI, OH, FL)"},"gvh":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gvh","description":"General Voting History (e.g. '4x4'); run separate calls per bucket"},"pvh":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pvh","description":"Primary Voting History (e.g. '4x4'); run separate calls per bucket"},"registration_status":{"type":"string","title":"Registration Status","description":"Registration status (default 'Registered')","default":"Registered"},"partisan_score_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Partisan Score Min","description":"Min partisan score (0-100)"},"partisan_score_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Partisan Score Max","description":"Max partisan score (0-100)"},"turnout_score_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Turnout Score Min","description":"Min turnout score (0-100)"},"turnout_score_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Turnout Score Max","description":"Max turnout score (0-100)"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender","description":"Male or Female"},"age_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Age Min","description":"Minimum age (pair with age_max)"},"age_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Age Max","description":"Maximum age (pair with age_min)"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"City name; auto-resolved to i360 ID"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County","description":"County name; comma-separate for multiple (e.g. 'Fulton,Cobb')"},"zip_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zip Code","description":"ZIP code"},"congressional_district":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Congressional District","description":"Congressional district"},"legislative_district":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Legislative District","description":"State legislative district"},"senate_district":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Senate District","description":"State senate district"},"exclude_legislative_district":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Exclude Legislative District","description":"Legislative district to exclude"}},"additionalProperties":false,"type":"object","required":["state"],"title":"PortalSearchRequest"},"cbmodels__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbmodels__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbmodels__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbmodels__ProbeRequest":{"properties":{"kind":{"type":"string","enum":["url","s3","presigned_url","text"],"title":"Kind"},"source":{"type":"string","minLength":1,"title":"Source"}},"type":"object","required":["kind","source"],"title":"ProbeRequest"},"cbmodels__QueryRequest":{"properties":{"sql":{"type":"string","title":"Sql","description":"DuckDB SQL. Tables are addressed as `catalog_name.table_name` (e.g. `s3_vt_deployment_table.vt_deployment_table`). Cross-catalog JOINs work natively via DuckDB ATTACH."},"catalogs":{"type":"string","title":"Catalogs","description":"Comma-separated catalog names to scope (empty = all visible)","default":""},"allow_full_scan":{"type":"boolean","title":"Allow Full Scan","description":"Unbounded queries (no date filter / LIMIT / SAMPLE) against event-stream or >50M-row catalogs are refused by the full-scan guard with a clear error. Pass true ONLY after the user has explicitly confirmed a full scan — it can take minutes and is expensive.","default":false}},"additionalProperties":false,"type":"object","required":["sql"],"title":"QueryRequest"},"cbmodels__ReopenRequest":{"properties":{"reason":{"type":"string","maxLength":200,"minLength":1,"title":"Reason"}},"additionalProperties":false,"type":"object","required":["reason"],"title":"ReopenRequest"},"cbmodels__ReshipRequest":{"properties":{"xlsx_path":{"type":"string","title":"Xlsx Path"},"reason":{"type":"string","title":"Reason"}},"additionalProperties":false,"type":"object","required":["xlsx_path","reason"],"title":"ReshipRequest"},"cbmodels__RouteTemplateRequest":{"properties":{"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id","description":"Existing template id (recXXX format)."},"html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Html","description":"Raw HTML body of a draft creative."},"top_n":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Top N","description":"How many top + bottom list_owners to return.","default":10}},"additionalProperties":false,"type":"object","title":"RouteTemplateRequest"},"cbmodels__RunScriptRequest":{"properties":{"script":{"type":"string","title":"Script","description":"R source code to execute."},"data_sql":{"type":"string","title":"Data Sql","description":"Optional SQL query (requires data_tenant_id + data_base_id). Result is loaded as `df` in the R script.","default":""},"data_url":{"type":"string","title":"Data Url","description":"Optional URL to a CSV or parquet file (500 MB cap). Downloaded and loaded as `df`.","default":""},"data_tenant_id":{"type":"string","title":"Data Tenant Id","description":"cbairtable tenant UUID (required with data_sql).","default":""},"data_base_id":{"type":"string","title":"Data Base Id","description":"Airtable base ID (required with data_sql).","default":""},"timeout_s":{"type":"integer","title":"Timeout S","description":"Wall-clock timeout in seconds (max 1800).","default":300}},"additionalProperties":false,"type":"object","required":["script"],"title":"RunScriptRequest"},"cbmodels__RunScriptResponse":{"properties":{"stdout":{"type":"string","title":"Stdout"},"stderr":{"type":"string","title":"Stderr"},"exit_code":{"type":"integer","title":"Exit Code"},"files":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Files"},"duration_s":{"type":"number","title":"Duration S"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["stdout","stderr","exit_code","files","duration_s","error"],"title":"RunScriptResponse"},"cbmodels__SQLRequest":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"cbairtable tenant UUID"},"base_id":{"type":"string","title":"Base Id","description":"Airtable base ID (appXXX)"},"sql":{"type":"string","title":"Sql","description":"DuckDB SQL to execute against synced data"}},"additionalProperties":false,"type":"object","required":["tenant_id","base_id","sql"],"title":"SQLRequest"},"cbmodels__SegmentAnalysisRequest":{"properties":{"svids":{"items":{"type":"string"},"type":"array","maxItems":100000,"minItems":1,"title":"Svids","description":"List of State Voter IDs (SVIDs) defining the segment to analyze. SVIDs are unique identifiers from the state voter file. Maximum 100,000 SVIDs per request for performance.","examples":[["FL-123456789","FL-987654321","FL-456789123"],["2712345","2712463","2799001"]]},"include_sparklines":{"type":"boolean","title":"Include Sparklines","description":"Include distribution data (bins/counts) for sparkline visualizations. Set to false if you only need summary statistics to reduce payload size.","default":true},"deviation_threshold":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Deviation Threshold","description":"Minimum deviation from baseline to flag as significant. Value of 0.1 means 10% deviation. Lower values find more deviations. Recommended: 0.1 for general analysis, 0.05 for detailed analysis.","default":0.1}},"additionalProperties":false,"type":"object","required":["svids"],"title":"SegmentAnalysisRequest","description":"Request body for analyzing a voter segment.\n\nProvide a list of State Voter IDs (SVIDs) to analyze. The API will\ncompare this segment's characteristics against the full voter baseline.","examples":[{"deviation_threshold":0.1,"include_sparklines":true,"svids":["FL-123456789","FL-987654321","FL-456789123"]},{"deviation_threshold":0.05,"include_sparklines":false,"svids":["2712345","2712463"]}]},"cbmodels__SegmentAnalysisResponse":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Unique identifier for this analysis request. Use for debugging/logging."},"analyzed_at":{"type":"string","format":"date-time","title":"Analyzed At","description":"Timestamp when analysis was performed (UTC)."},"segment_svids_provided":{"type":"integer","title":"Segment Svids Provided","description":"Number of SVIDs in the original request."},"segment_svids_matched":{"type":"integer","title":"Segment Svids Matched","description":"Number of SVIDs that matched persons in the database. If significantly lower than provided, some SVIDs may be invalid."},"summary":{"$ref":"#/components/schemas/cbmodels__SegmentSummary","description":"High-level summary of segment vs baseline. Read this first."},"columns":{"items":{"$ref":"#/components/schemas/cbmodels__ColumnComparison"},"type":"array","title":"Columns","description":"Detailed comparison for each analyzed column. Focus on columns where is_significant=True."},"correlations":{"items":{"$ref":"#/components/schemas/cbmodels__CorrelationComparison"},"type":"array","title":"Correlations","description":"Correlation comparisons between numeric columns."},"outliers":{"items":{"$ref":"#/components/schemas/cbmodels__OutlierComparison"},"type":"array","title":"Outliers","description":"Outlier analysis for numeric columns."},"patterns":{"items":{"$ref":"#/components/schemas/cbmodels__PatternAlert"},"type":"array","title":"Patterns","description":"Detected patterns that differ from baseline."},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Time spent resolving SVIDs to person records (milliseconds)."},"analysis_time_ms":{"type":"number","title":"Analysis Time Ms","description":"Time spent computing segment statistics (milliseconds)."}},"type":"object","required":["request_id","analyzed_at","segment_svids_provided","segment_svids_matched","summary","columns","query_time_ms","analysis_time_ms"],"title":"SegmentAnalysisResponse","description":"Complete segment analysis response.\n\nThis is the main response from the segment analysis endpoint. It contains:\n- Summary for quick overview\n- Detailed column comparisons\n- Correlations, outliers, and patterns for deeper analysis\n- Timing information for performance monitoring","example":{"analysis_time_ms":312.8,"analyzed_at":"2025-12-26T10:30:00Z","columns":[{"baseline_count":81000,"baseline_distinct_count":67,"baseline_null_count":150,"column_name":"county","column_type":"categorical","is_significant":true,"segment_count":823,"segment_distinct_count":12,"segment_null_count":2}],"correlations":[],"outliers":[],"patterns":[],"query_time_ms":45.2,"request_id":"550e8400-e29b-41d4-a716-446655440000","segment_svids_matched":823,"segment_svids_provided":1000,"summary":{"alerts":[],"baseline_size":81000,"segment_percentage":1.02,"segment_size":823,"significant_deviations":8,"top_deviations":["county","age","party_affiliation"]}}},"cbmodels__SegmentSummary":{"properties":{"segment_size":{"type":"integer","title":"Segment Size","description":"Number of matched persons in the segment.","examples":[800]},"baseline_size":{"type":"integer","title":"Baseline Size","description":"Total persons in the baseline population.","examples":[81000]},"segment_percentage":{"type":"number","title":"Segment Percentage","description":"Segment size as percentage of baseline (segment_size / baseline_size * 100).","examples":[0.99]},"significant_deviations":{"type":"integer","title":"Significant Deviations","description":"Number of columns with significant deviations from baseline. Higher numbers indicate the segment is more distinct from the general population.","examples":[12]},"top_deviations":{"items":{"type":"string"},"type":"array","title":"Top Deviations","description":"Names of the top 5 columns with most significant deviations. AI should focus explanation on these columns first.","examples":[["county","age","party_affiliation","email_engagement"]]},"alerts":{"items":{"type":"string"},"type":"array","title":"Alerts","description":"High-level alert messages about data quality or unusual patterns."}},"type":"object","required":["segment_size","baseline_size","segment_percentage","significant_deviations"],"title":"SegmentSummary","description":"High-level summary of segment analysis results.\n\nUse this summary for quick overviews before diving into column details.\nThe AI should present this information first when reporting results."},"cbmodels__SendBackRequest":{"properties":{"reason":{"type":"string","maxLength":200,"minLength":1,"title":"Reason"}},"additionalProperties":false,"type":"object","required":["reason"],"title":"SendBackRequest"},"cbmodels__ShipRequest":{"properties":{"xlsx_path":{"type":"string","title":"Xlsx Path"},"force":{"type":"boolean","title":"Force","default":false},"force_reason":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Force Reason"}},"additionalProperties":false,"type":"object","required":["xlsx_path"],"title":"ShipRequest"},"cbmodels__SparklineData":{"properties":{"bins":{"items":{"type":"string"},"type":"array","title":"Bins","description":"Bin labels for the distribution. For numeric columns, these are ranges (e.g., '0-10', '10-20'). For categorical columns, these are category names.","examples":[["0-25","25-50","50-75","75-100"]]},"counts":{"items":{"type":"integer"},"type":"array","title":"Counts","description":"Number of records in each bin.","examples":[[150,320,280,50]]},"percentages":{"items":{"type":"number"},"type":"array","title":"Percentages","description":"Percentage of total records in each bin (0-100 scale).","examples":[[18.75,40.0,35.0,6.25]]}},"type":"object","required":["bins","counts","percentages"],"title":"SparklineData","description":"Distribution data for rendering sparkline/histogram visualizations.\n\nContains binned data suitable for creating small inline charts showing\nthe distribution of values in a column."},"cbmodels__SqlRequest":{"properties":{"sql":{"type":"string","title":"Sql","description":"Pipeline SQL to compile (single SELECT/CTE).","examples":["SELECT st, COUNT(*) AS n FROM orders.orders GROUP BY st"]}},"additionalProperties":false,"type":"object","required":["sql"],"title":"SqlRequest","description":"A bare script — for /compile (dry) and /versions (compile-gated)."},"cbmodels__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbmodels__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbmodels__SyncRequest":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"cbairtable tenant UUID"},"base_id":{"type":"string","title":"Base Id","description":"Airtable base ID to sync"}},"additionalProperties":false,"type":"object","required":["tenant_id","base_id"],"title":"SyncRequest"},"cbmodels__TableStatsResponse":{"properties":{"table_name":{"type":"string","title":"Table Name","description":"Name of the table."},"row_count":{"type":"integer","title":"Row Count","description":"Number of rows in the table."},"column_stats":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Column Stats","description":"Statistics for each column including: name, type, null_count, distinct_count, mean (numeric), top_values (categorical)."},"correlations":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Correlations","description":"Significant correlations between numeric columns in this table."},"outliers":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Outliers","description":"Outlier information for numeric columns."},"patterns":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Patterns","description":"Detected patterns (missing data, class imbalance, etc.)."}},"type":"object","required":["table_name","row_count","column_stats","correlations","outliers","patterns"],"title":"TableStatsResponse","description":"Baseline statistics for a specific table.\n\nProvides detailed statistics for exploring a table's structure and patterns."},"cbmodels__TriggerBuildRequest":{"properties":{"force":{"type":"boolean","title":"Force","default":false},"force_reason":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Force Reason"}},"additionalProperties":false,"type":"object","title":"TriggerBuildRequest"},"cbmodels__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbmodels__VisibilityRequest":{"properties":{"visibility":{"type":"string","pattern":"^(private|tenant|global)$","title":"Visibility","description":"'private' (creator-only), 'tenant', or 'global' (super users)."}},"additionalProperties":false,"type":"object","required":["visibility"],"title":"VisibilityRequest","description":"PATCH body — the only mutable knob the console exposes today."},"cbmodels__VoterCountReport":{"properties":{"request_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Text","description":"Original natural language request"},"filters":{"items":{"$ref":"#/components/schemas/cbmodels__CountFilterRequest"},"type":"array","title":"Filters","description":"Structured filters applied"},"query_plan":{"type":"string","title":"Query Plan","description":"Human-readable filter summary, e.g. 'county = Knox AND ...'"},"results":{"$ref":"#/components/schemas/cbmodels__CountQueryResponse","description":"The count query results"},"share_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Url","description":"Public cbfiles URL for this report"},"created_at":{"type":"string","title":"Created At","description":"ISO 8601 timestamp"}},"type":"object","required":["filters","query_plan","results","created_at"],"title":"VoterCountReport","description":"Full voter count report with context and optional share link."},"cbmodels__VoterDetail":{"properties":{"svid":{"anyOf":[{"type":"string"},{"type":"integer"}],"title":"Svid","description":"State Voter ID"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"middle_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Middle Name"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"address_line_2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Line 2"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"zip_code":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Zip Code"},"congressional_district":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Congressional District"},"precinct":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Precinct"},"phone":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Phone"},"phone_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Type"},"landline_phone":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Landline Phone"},"cell_phone":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Cell Phone"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Email from mydata"},"party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party"},"calculated_party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Calculated Party"},"observed_party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Observed Party"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"birth_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Birth Year"},"ethnicity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ethnicity"},"race":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Race"},"religion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Religion"},"turnout_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Turnout Score"},"trump_support_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Trump Support Score"},"biden_oppose_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Biden Oppose Score"},"immigration_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Immigration Score"},"last_four_general_elections":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Last Four General Elections"},"last_four_primary_elections":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Last Four Primary Elections"},"general_2020":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"General 2020"},"general_2022":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"General 2022"},"primary_president_2020":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}],"title":"Primary President 2020"},"absentee_history":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Absentee History"},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"},"dma_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dma Name"},"municipality_town_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Municipality Town Name"},"registration_status":{"anyOf":[{"type":"string"},{"type":"boolean"},{"type":"null"}],"title":"Registration Status"}},"type":"object","required":["svid"],"title":"VoterDetail","description":"Full voter record with all available fields."},"cbmodels__VoterSearchRequest":{"properties":{"state":{"type":"string","title":"State","description":"State slug (lowercase full word). Only 'michigan' is served by this endpoint today — non-MI calls return 400 with a redirect to /api/v1/i360/portal-search or federation data_query.","default":"michigan"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"First name (partial match)"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"Last name (partial match)"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"City name"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County","description":"County name"},"zip_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zip Code","description":"ZIP code"},"party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party","description":"Party affiliation (R, D, I, etc.)"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender","description":"Gender (M, F)"},"congressional_district":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Congressional District","description":"Congressional district"},"min_turnout_score":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Min Turnout Score"},"max_turnout_score":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Max Turnout Score"},"min_trump_support":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Min Trump Support"},"max_trump_support":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Max Trump Support"},"limit":{"type":"integer","maximum":1000.0,"minimum":1.0,"title":"Limit","description":"Max results","default":100},"offset":{"type":"integer","minimum":0.0,"title":"Offset","description":"Results offset","default":0}},"additionalProperties":false,"type":"object","title":"VoterSearchRequest","description":"Request for voter search."},"cbmodels__VoterSearchResponse":{"properties":{"total_matches":{"type":"integer","title":"Total Matches","description":"Total matching voters (before limit)"},"returned":{"type":"integer","title":"Returned","description":"Number of voters returned"},"offset":{"type":"integer","title":"Offset","description":"Current offset"},"voters":{"items":{"$ref":"#/components/schemas/cbmodels__VoterSummary"},"type":"array","title":"Voters","description":"Matching voters"},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Query execution time"}},"type":"object","required":["total_matches","returned","offset","voters","query_time_ms"],"title":"VoterSearchResponse","description":"Response for voter search."},"cbmodels__VoterStatsResponse":{"properties":{"database_name":{"type":"string","title":"Database Name","description":"Name of the loaded database"},"total_voters":{"type":"integer","title":"Total Voters","description":"Total voters in database"},"state":{"type":"string","title":"State","description":"State covered"},"party_distribution":{"additionalProperties":{"type":"integer"},"type":"object","title":"Party Distribution","description":"Voters by party"},"gender_distribution":{"additionalProperties":{"type":"integer"},"type":"object","title":"Gender Distribution","description":"Voters by gender"},"top_counties":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Top Counties","description":"Top 10 counties by population"},"avg_turnout_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Turnout Score","description":"Average turnout score"},"avg_trump_support":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Trump Support","description":"Average Trump support score"},"query_time_ms":{"type":"number","title":"Query Time Ms","description":"Query execution time"}},"type":"object","required":["database_name","total_voters","state","party_distribution","gender_distribution","top_counties","avg_turnout_score","avg_trump_support","query_time_ms"],"title":"VoterStatsResponse","description":"Response for voter database statistics."},"cbmodels__VoterSummary":{"properties":{"svid":{"anyOf":[{"type":"string"},{"type":"integer"}],"title":"Svid","description":"State Voter ID"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"birth_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Birth Year"},"turnout_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Turnout Score"},"trump_support_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Trump Support Score"}},"type":"object","required":["svid"],"title":"VoterSummary","description":"Summary voter record for search results."},"cbmodels__cbmodels__api__schemas__HealthResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Overall health status: 'healthy' if all systems ready, 'degraded' if some components unavailable.","examples":["healthy","degraded"]},"model_loaded":{"type":"boolean","title":"Model Loaded","description":"True if the baseline model is loaded and ready."},"db_connected":{"type":"boolean","title":"Db Connected","description":"True if the database connection is active."},"svid_field_configured":{"type":"boolean","title":"Svid Field Configured","description":"True if the State Voter ID custom field is configured. Required for segment analysis."}},"type":"object","required":["status","model_loaded","db_connected","svid_field_configured"],"title":"HealthResponse","description":"API health check response.\n\nCheck this endpoint before making analysis requests to ensure the\nsystem is properly configured and ready.","example":{"db_connected":true,"model_loaded":true,"status":"healthy","svid_field_configured":true}},"cbmodels__sysforge__core__health__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbmodels__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbmodels__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbnetwork__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbnetwork__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbnetwork__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbnetwork__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbnetwork__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbnetwork__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbnetwork__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbnetwork__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbnetwork__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbnetwork__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbnetwork__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbnetwork__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbnetwork__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbnetwork__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbnominate__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbnominate__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbnominate__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbnominate__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbnominate__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbnominate__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbnominate__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbnominate__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbnominate__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbnominate__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbnominate__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbnominate__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbnominate__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbnominate__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbnotify__ChannelHealth":{"properties":{"armed":{"type":"boolean","title":"Armed","description":"Whether this channel can send at all."},"blocked_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Blocked Reason","description":"Why it is disarmed, or null when armed.","examples":["delivery disabled (CBNOTIFY_DELIVERY_ENABLED unset)"]},"detail":{"additionalProperties":true,"type":"object","title":"Detail","description":"Channel-specific config worth seeing without reading the env (e.g. the paging floor). Never contains the page number itself."}},"type":"object","required":["armed"],"title":"ChannelHealth","description":"Arming + recent outcome for one delivery channel."},"cbnotify__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbnotify__DeliveryArming":{"properties":{"email_enabled":{"type":"boolean","title":"Email Enabled","description":"CBNOTIFY_DELIVERY_ENABLED — the email leg."},"paging_armed":{"type":"boolean","title":"Paging Armed","description":"True when a page number is configured. Gated independently of email, so a HIGH row can page with email off (and vice versa)."},"page_min_priority":{"type":"string","title":"Page Min Priority","description":"Lowest priority that pages when paging is armed."},"page_max_age_minutes":{"type":"integer","title":"Page Max Age Minutes","description":"Rows older than this are DRAINED unsent by the storm guard rather than delivered late."}},"type":"object","required":["email_enabled","paging_armed","page_min_priority","page_max_age_minutes"],"title":"DeliveryArming","description":"Whether the delivery channels are actually switched on.\n\nThe single most useful fact on the whole surface: a wall of PENDING rows\nmeans nothing until you know no channel was armed to send them. Reports\nbooleans only — the page number itself is never exposed."},"cbnotify__DeliveryHealth":{"properties":{"healthy":{"type":"boolean","title":"Healthy","description":"True when every channel that ought to be armed is armed and nothing is currently failing to send."},"problems":{"items":{"type":"string"},"type":"array","title":"Problems","description":"Human-readable reasons `healthy` is false. Empty when healthy."},"channels":{"additionalProperties":{"$ref":"#/components/schemas/cbnotify__ChannelHealth"},"type":"object","title":"Channels","description":"Per-channel arming and configuration (`email`, `page`)."},"backlog":{"additionalProperties":{"type":"integer"},"type":"object","title":"Backlog","description":"Undelivered row counts: `pending` (never attempted) and `retrying` (attempted, failed, still queued)."},"last_dispatch_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Dispatch At","description":"ISO-8601 time of the most recent clean hand-off to a channel, or null if nothing has ever been delivered. Derived from the store, so it survives a restart."},"last_failure_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Failure At","description":"ISO-8601 time of the most recent send failure, or null."},"last_failure":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Failure","description":"The most recent send failure message, or null."},"last_tick_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Tick At","description":"ISO-8601 time the dispatch tick last ran in THIS process. Null after a restart until the next tick — that is not itself a fault, but a null that persists means the cbcron tick is not arriving."}},"type":"object","required":["healthy","channels","backlog"],"title":"DeliveryHealth","description":"Whether cbnotify can actually deliver right now — the honest answer.\n\nExists so a caller can tell \"accepted\" from \"will arrive\" without reading\nanyone's environment. ``POST /notify`` returning 2xx says a row was stored;\nonly this says whether anything will carry it."},"cbnotify__DeliveryOutlook":{"type":"string","enum":["queued","no_channel_armed","no_channel_for_priority","page_destination_missing"],"title":"DeliveryOutlook","description":"Why a freshly published row will or will not be delivered.\n\nA caller needs to tell \"wait a moment\" apart from \"reconfigure or stop\nsending\" — those want opposite reactions, and collapsing them into one\nsilent 2xx is the defect this whole endpoint family exists to fix."},"cbnotify__DeliveryPlan":{"properties":{"will_deliver":{"type":"boolean","title":"Will Deliver","description":"True if at least one channel will carry this row. False means it is stored and queryable but nothing will send it."},"channels":{"items":{"type":"string"},"type":"array","title":"Channels","description":"Channels that will carry it, e.g. ['page'] or ['email'].","examples":[["page","email"]]},"outlook":{"$ref":"#/components/schemas/cbnotify__DeliveryOutlook","description":"Machine-readable reason code. `queued` = wait. Anything else means no amount of waiting delivers this row as configured."},"detail":{"type":"string","title":"Detail","description":"Human-readable explanation of the outlook, for logs.","examples":["email delivery disabled; 'normal' ranks below page floor 'high'"]}},"type":"object","required":["will_deliver","outlook","detail"],"title":"DeliveryPlan","description":"What cbnotify will actually do with a row it just accepted.\n\nReturned inline on ``POST /notify`` so a producer learns the truth at\npublish time instead of inferring it from a 2xx. Computed from the same\npredicate the dispatcher uses, so it cannot drift from real behaviour."},"cbnotify__DeliveryState":{"type":"string","enum":["pending","retrying","dispatched","drained"],"title":"DeliveryState","description":"What actually became of a notification row.\n\nDeliberately NOT a boolean. ``dispatched_at`` alone answers the wrong\nquestion — the stale guard stamps it on rows it never sent — so a consumer\nthat reads it as \"delivered\" reports a delivery that did not happen.\n\n``DISPATCHED`` is the strongest claim cbnotify can honestly make: the row\nwas handed to cbemail/cbsms without an error. It is **not** evidence a human\nreceived anything."},"cbnotify__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbnotify__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbnotify__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbnotify__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbnotify__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbnotify__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbnotify__Link":{"properties":{"label":{"type":"string","maxLength":200,"minLength":1,"title":"Label","description":"Human-readable text for the link, e.g. a button caption.","examples":["View dashboard","Open incident"]},"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url","description":"Destination URL the link points to (must be HTTP/HTTPS).","examples":["https://loom.campaignbrain.dev/app/hive"]}},"type":"object","required":["label","url"],"title":"Link","description":"A labeled hyperlink attached to a notification.\n\nRenders as a clickable action in the consumer surface (e.g. an email\nbutton or an alert deep-link). The ``url`` is validated as a well-formed\nHTTP/HTTPS URL.","examples":[{"label":"View dashboard","url":"https://loom.campaignbrain.dev/app/hive"}]},"cbnotify__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbnotify__NotificationCreate":{"properties":{"channel":{"type":"string","maxLength":200,"minLength":1,"title":"Channel","description":"Logical routing channel the notification belongs to. Consumers filter on this (e.g. an alert category or producing subsystem).","examples":["alerts","cbtapwatch","deploys"]},"title":{"type":"string","maxLength":500,"minLength":1,"title":"Title","description":"Short headline shown in the notification summary.","examples":["Tap threshold exceeded on monroe"]},"body_markdown":{"type":"string","minLength":1,"title":"Body Markdown","description":"Notification body as Markdown. Rendered by the consumer surface (email, alert panel, etc.).","examples":["**3 taps** crossed the warning line in the last 5m."]},"links":{"items":{"$ref":"#/components/schemas/cbnotify__Link"},"type":"array","title":"Links","description":"Optional labeled action links attached to the notification."},"priority":{"$ref":"#/components/schemas/cbnotify__PriorityEnum","description":"Delivery priority; consumers may sort or escalate on it.","default":"normal","examples":["normal","urgent"]}},"type":"object","required":["channel","title","body_markdown"],"title":"NotificationCreate","description":"Inbound payload for ``POST /notify``.\n\nProducers send a notification's content here; the persisted\n``producer_email`` is taken from the upstream ``X-User-Email`` header, not\nfrom this body. ``body_markdown`` is rendered by the consumer surface.","examples":[{"body_markdown":"**3 taps** crossed the warning line in the last 5m.","channel":"alerts","links":[{"label":"View dashboard","url":"https://loom.campaignbrain.dev/app/hive"}],"priority":"urgent","title":"Tap threshold exceeded on monroe"}]},"cbnotify__NotificationOut":{"properties":{"id":{"type":"string","title":"Id","description":"Server-assigned UUID of the stored notification row.","examples":["550e8400-e29b-41d4-a716-446655440000"]},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp the notification row was persisted.","examples":["2026-06-21T14:32:00+00:00"]},"delivery":{"$ref":"#/components/schemas/cbnotify__DeliveryPlan","description":"Whether this row will actually be delivered, and why."}},"type":"object","required":["id","created_at","delivery"],"title":"NotificationOut","description":"Response payload for ``POST /notify``.\n\n**201** when a channel will carry the row, **202** when it was stored but\nnothing will deliver it. Either way ``delivery`` says which, and why.\n\nThere is deliberately no ``delivered_at`` field. One used to be here and it\nreturned the row's ``created_at`` — a persistence timestamp wearing a\ndelivery-shaped name, on a service that was delivering nothing. Read\n``delivery`` for the outlook, ``GET /notify`` for what actually happened.","examples":[{"created_at":"2026-06-21T14:32:00+00:00","delivery":{"channels":[],"detail":"email delivery disabled; 'normal' ranks below page floor 'high'","outlook":"no_channel_for_priority","will_deliver":false},"id":"550e8400-e29b-41d4-a716-446655440000"}]},"cbnotify__NotificationPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbnotify__NotificationRow"},"type":"array","title":"Items","description":"Matching rows, newest first."},"total":{"type":"integer","title":"Total","description":"Total rows matching the filters."},"limit":{"type":"integer","title":"Limit","description":"Page size applied."},"offset":{"type":"integer","title":"Offset","description":"Offset applied."},"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts","description":"Row counts by delivery_state across the whole store (unfiltered by state), so a surface can show the backlog without a second call."},"arming":{"$ref":"#/components/schemas/cbnotify__DeliveryArming","description":"Whether the delivery channels are switched on."}},"type":"object","required":["items","total","limit","offset","counts","arming"],"title":"NotificationPage","description":"Response payload for ``GET /notify`` — one page plus the totals."},"cbnotify__NotificationRow":{"properties":{"id":{"type":"string","title":"Id","description":"Server-assigned UUID of the row."},"channel":{"type":"string","title":"Channel","description":"Routing channel the producer published under."},"title":{"type":"string","title":"Title","description":"Short headline of the notification."},"priority":{"type":"string","title":"Priority","description":"low | normal | high | urgent."},"producer_email":{"type":"string","title":"Producer Email","description":"Identity that published the row, captured from X-User-Email at publish time. Answers 'who raised this?'."},"links":{"items":{},"type":"array","title":"Links","description":"Labeled action links attached at publish."},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 time the row was stored."},"dispatched_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dispatched At","description":"ISO-8601 time the row was handed to a delivery channel, or null. Present on DRAINED rows too — read `delivery_state`, not this."},"dispatch_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dispatch Error","description":"Last delivery error, or the drain reason."},"delivery_state":{"$ref":"#/components/schemas/cbnotify__DeliveryState","description":"pending | retrying | dispatched | drained."},"would_page":{"type":"boolean","title":"Would Page","description":"Whether this row's priority meets the paging floor. Combined with `arming.paging_armed`, distinguishes 'too quiet to page' from 'severe enough, but paging was disarmed'."}},"type":"object","required":["id","channel","title","priority","producer_email","created_at","delivery_state","would_page"],"title":"NotificationRow","description":"One notification as returned by ``GET /notify``.\n\n``body_markdown`` is omitted from the list payload — the surface that reads\nthis renders a table, and alert bodies run long. Fetch detail separately if\nit is ever needed."},"cbnotify__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbnotify__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbnotify__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbnotify__PriorityEnum":{"type":"string","enum":["low","normal","high","urgent"],"title":"PriorityEnum","description":"Notification priority levels."},"cbnotify__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbnotify__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbnotify__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbonboard__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbonboard__CreateDemoSessionRequest":{"properties":{"label":{"type":"string","maxLength":120,"minLength":1,"title":"Label","description":"Human-readable name shown to operators.","examples":["Ownership Demo — June Offsite"]},"rotation_seconds":{"type":"integer","title":"Rotation Seconds","description":"How often the QR `batch_token` rotates, in seconds.","default":30,"examples":[30]},"grace_seconds":{"type":"integer","title":"Grace Seconds","description":"Extra window a just-rotated token remains redeemable, in seconds (defeats the race between scan and rotation).","default":90,"examples":[90]},"max_redemptions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Redemptions","description":"Per-batch redemption cap, or null for unlimited.","default":25,"examples":[25]},"grant_groups":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Grant Groups","description":"cbauth groups a user is elevated into when they complete SMS verification through this session. The session owns 'what does finishing this flow grant you' — e.g. an event session grants its own scoped group instead of the generic `verified`.","default":["verified"],"examples":[["election-night-2026"]]},"landing_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Landing Url","description":"Where the signup PWA sends the person after they finish — the session owns 'where does finishing this flow take you' (cbonboard#4). Must be https on campaignbrain.dev or a subdomain; null keeps the PWA's default landing.","examples":["https://svi.campaignbrain.dev"]}},"type":"object","required":["label"],"title":"CreateDemoSessionRequest","description":"Request body for ``POST /admin/demo-sessions`` — open a new demo session.","example":{"grace_seconds":90,"grant_groups":["verified"],"label":"Ownership Demo — June Offsite","max_redemptions":25,"rotation_seconds":30}},"cbonboard__DemoSession":{"properties":{"id":{"type":"string","title":"Id","description":"Demo session id.","examples":["ds_01HX..."]},"label":{"type":"string","title":"Label","description":"Operator-facing name.","examples":["Ownership Demo"]},"created_by":{"type":"string","title":"Created By","description":"Principal that created the session.","examples":["admin"]},"rotation_seconds":{"type":"integer","title":"Rotation Seconds","description":"QR token rotation interval in seconds.","examples":[30]},"grace_seconds":{"type":"integer","title":"Grace Seconds","description":"Post-rotation grace window in seconds.","examples":[90]},"max_redemptions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Redemptions","description":"Per-batch redemption cap, or null.","examples":[25]},"active":{"type":"boolean","title":"Active","description":"Whether the session is currently active.","examples":[true]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp.","examples":["2026-06-21T18:00:00Z"]},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When the session auto-expires, or null if open-ended.","examples":["2026-06-21T20:00:00Z"]},"grant_groups":{"items":{"type":"string"},"type":"array","title":"Grant Groups","description":"cbauth groups granted on verify success (see CreateDemoSessionRequest.grant_groups).","default":["verified"],"examples":[["election-night-2026"]]},"landing_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Landing Url","description":"Post-signup destination for this session's invitees, or null for the PWA default (see CreateDemoSessionRequest).","examples":["https://svi.campaignbrain.dev"]}},"type":"object","required":["id","label","created_by","rotation_seconds","grace_seconds","max_redemptions","active","created_at","expires_at"],"title":"DemoSession","description":"A demo session — the container that mints rotating QR batches.\n\nRepresents one live event. Returned by the admin demo-session endpoints."},"cbonboard__FingerprintBlob":{"properties":{"hash":{"type":"string","maxLength":128,"minLength":16,"title":"Hash","description":"Stable, opaque device-fingerprint digest computed client-side.","examples":["fp_9f2c1a7be0d4c3a18b6e5f0a2d7c9e14"]},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform","description":"Reported OS / platform string, if available.","examples":["iOS","Android","Windows"]},"device_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Type","description":"Coarse device category, if derivable from the user agent.","examples":["mobile","tablet","desktop"]},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone","description":"IANA timezone reported by the browser.","examples":["America/Chicago"]}},"type":"object","required":["hash"],"title":"FingerprintBlob","description":"Best-effort device fingerprint captured by the signup PWA at scan time.\n\nUsed to de-duplicate redemptions and to label roster entries on the big\nscreen. The fingerprint is a soft signal — it identifies a device, not a\nperson, and is never treated as an identity claim."},"cbonboard__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbonboard__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbonboard__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbonboard__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbonboard__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbonboard__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbonboard__Invite":{"properties":{"name":{"type":"string","title":"Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"url":{"type":"string","title":"Url","description":"The long, self-contained onboarding link."},"short_url":{"type":"string","title":"Short Url","description":"The short link to actually send — resolves to `url`. Same single-use invite behind both."},"code":{"type":"string","title":"Code","description":"The short code itself (8 chars)."},"invite_id":{"type":"string","title":"Invite Id"},"expires_at":{"type":"string","title":"Expires At","description":"ISO-8601 UTC."}},"type":"object","required":["name","url","short_url","code","invite_id","expires_at"],"title":"Invite","description":"A minted link, ready to paste into an email or text."},"cbonboard__InviteList":{"properties":{"demo_session_id":{"type":"string","title":"Demo Session Id"},"count":{"type":"integer","title":"Count"},"expires_at":{"type":"string","title":"Expires At"},"invites":{"items":{"$ref":"#/components/schemas/cbonboard__Invite"},"type":"array","title":"Invites"}},"type":"object","required":["demo_session_id","count","expires_at","invites"],"title":"InviteList"},"cbonboard__InviteRecipient":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name","description":"Display name."},"email":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"title":"Phone"}},"type":"object","required":["name"],"title":"InviteRecipient","description":"One person on Kevin's list. Only `name` is required — the phone is\ncollected in the signup form (it must be the handset that receives the SMS\ncode, so a list value could not be trusted anyway). The email is returned\nfor mail-merge AND persisted with the code (cbonboard#6): it prefills the\nsignup form and is the address the cbauth account is created under."},"cbonboard__InviteResolve":{"properties":{"batch_token":{"type":"string","title":"Batch Token"},"landing_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Landing Url","description":"The session's post-signup destination (see CreateDemoSessionRequest.landing_url), or null when the session has none."},"landing_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Landing Label","description":"The session's label, for naming the destination. Null when `landing_url` is null."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"The address the invite was minted for (cbonboard#6) — prefilled on the signup form, editable there. Null for invites minted without one."}},"type":"object","required":["batch_token"],"title":"InviteResolve","description":"Public code→token exchange used by the signup PWA when it is opened\nwith `?c=<code>` instead of the full `?t=<token>`.\n\nCarries the session's `landing_url` too (cbonboard#5): a SPENT link never\nreaches redeem, which is otherwise where the PWA learns the destination —\nso without this the \"already used\" page could only point at the platform\ngrid. Resolve is a non-burning GET, so the PWA holds the door before it\ntries the handle."},"cbonboard__LiveQr":{"properties":{"batch_token":{"type":"string","title":"Batch Token","description":"Current signed token to encode in the QR.","examples":["eyJhbGciOiJIUzI1Ni1.batch..."]},"batch_id":{"type":"string","title":"Batch Id","description":"Identifier of the active QR batch.","examples":["batch_01HX..."]},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"When this `batch_token` becomes invalid.","examples":["2026-06-21T18:30:00Z"]},"rotates_at":{"type":"string","format":"date-time","title":"Rotates At","description":"When the kiosk should fetch the next token.","examples":["2026-06-21T18:29:30Z"]},"url":{"type":"string","title":"Url","description":"Full scan URL embedded in the QR (PWA entry point).","examples":["https://onboard.campaignbrain.dev/s/eyJhbGci..."]},"redemptions":{"type":"integer","title":"Redemptions","description":"Number of redemptions against this batch so far.","examples":[7]},"max_redemptions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Redemptions","description":"Redemption cap for the batch, or null when uncapped.","examples":[25]}},"type":"object","required":["batch_token","batch_id","expires_at","rotates_at","url","redemptions","max_redemptions"],"title":"LiveQr","description":"Live QR rendering payload for the kiosk / big screen.\n\nReturned by ``GET /qr/live/{demo_session_id}``; carries the current rotating\ntoken, its rotation/expiry schedule, the scan URL, and the running\nredemption count so cbloom can draw and rotate the on-screen QR."},"cbonboard__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbonboard__MintInvitesRequest":{"properties":{"demo_session_id":{"type":"string","title":"Demo Session Id","description":"Session whose grants the links confer."},"recipients":{"items":{"$ref":"#/components/schemas/cbonboard__InviteRecipient"},"type":"array","maxItems":500,"minItems":1,"title":"Recipients"},"ttl_hours":{"type":"integer","maximum":336.0,"minimum":1.0,"title":"Ttl Hours","description":"Link lifetime. Default 72h covers 'mailed tonight, opened election night'.","default":72}},"type":"object","required":["demo_session_id","recipients"],"title":"MintInvitesRequest","description":"Mint one durable onboarding link per recipient."},"cbonboard__OnboardingTier":{"type":"integer","enum":[0,1,2,3],"title":"OnboardingTier","description":"Trust tier in the progressive onboarding ladder.\n\nA user climbs the ladder one rung per interaction. Each tier expands the\ngranted scopes and stabilizes the user's identity:\n\n- ``scanned`` (0) — redeemed a QR; an ephemeral Tier-0 session, no identity.\n- ``provisional`` (1) — signed up; a disabled cbauth user exists, phone\n  not yet confirmed.\n- ``verified`` (2) — confirmed an SMS code; scopes elevated, phone confirmed.\n- ``assigned`` (3) — reserved for downstream role assignment."},"cbonboard__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbonboard__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbonboard__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbonboard__RedeemRequest":{"properties":{"batch_token":{"type":"string","title":"Batch Token","description":"Signed, rotating token encoded in the scanned QR code.","examples":["eyJhbGciOiJIUzI1Ni1.batch.eyJiYXRjaF9pZCI6..."]},"fingerprint":{"$ref":"#/components/schemas/cbonboard__FingerprintBlob","description":"Device fingerprint captured at scan time."},"ua_summary":{"additionalProperties":true,"type":"object","title":"Ua Summary","description":"Optional, free-form user-agent summary for roster labeling.","examples":[{"browser":"Safari","os":"iOS 18"}]}},"type":"object","required":["batch_token","fingerprint"],"title":"RedeemRequest","description":"Request body for ``POST /onboard/redeem``.\n\nSent by the signup PWA immediately after a QR scan to exchange the QR's\nshort-lived ``batch_token`` for a Tier-0 ``session_token``.","example":{"batch_token":"eyJhbGciOiJIUzI1Ni1.batch.eyJiYXRjaF9pZCI6...","fingerprint":{"device_type":"mobile","hash":"fp_9f2c1a7be0d4c3a18b6e5f0a2d7c9e14","platform":"iOS","timezone":"America/Chicago"},"ua_summary":{"browser":"Safari","os":"iOS 18"}}},"cbonboard__RedeemResponse":{"properties":{"session_token":{"type":"string","title":"Session Token","description":"Short-lived Tier-0 bearer token for the signup step.","examples":["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC19.session..."]},"token_type":{"type":"string","title":"Token Type","description":"Token scheme.","default":"Bearer","examples":["Bearer"]},"expires_in":{"type":"integer","title":"Expires In","description":"Lifetime of `session_token` in seconds (default 15 min).","default":900,"examples":[900]},"batch_id":{"type":"string","title":"Batch Id","description":"Identifier of the QR batch the token was redeemed against.","examples":["batch_01HX..."]},"demo_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Demo Session Id","description":"Demo session this redemption belongs to, when known.","examples":["ds_01HX..."]},"landing_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Landing Url","description":"Where the PWA should send this person after they finish the flow — the session's configured destination, validated at admin write time (cbonboard#4). Null = the PWA's default landing.","examples":["https://svi.campaignbrain.dev"]},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"For an invite link: the address it was minted for (cbonboard#6), so a full `?t=` link that skipped the resolve hop still prefills the form. Null for QR batches and email-less invites."}},"type":"object","required":["session_token","batch_id"],"title":"RedeemResponse","description":"Response for a successful ``POST /onboard/redeem``.\n\nCarries the minted Tier-0 ``session_token`` the PWA presents as a bearer\ntoken on the subsequent signup call.","example":{"batch_id":"batch_01HX...","demo_session_id":"ds_01HX...","expires_in":900,"landing_url":"https://svi.campaignbrain.dev","session_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC19.session...","token_type":"Bearer"}},"cbonboard__Redemption":{"properties":{"id":{"type":"string","title":"Id","description":"Redemption record id.","examples":["rdm_01HX..."]},"batch_id":{"type":"string","title":"Batch Id","description":"QR batch the redemption was made against.","examples":["batch_01HX..."]},"demo_session_id":{"type":"string","title":"Demo Session Id","description":"Owning demo session.","examples":["ds_01HX..."]},"fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fingerprint","description":"Device fingerprint hash, if captured.","examples":["fp_9f2c1a7be0d4..."]},"redeemed_at":{"type":"string","format":"date-time","title":"Redeemed At","description":"When the QR was redeemed.","examples":["2026-06-21T18:05:00Z"]},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"cbauth user id, once signup occurs.","examples":["usr_01HX..."]},"tier_reached":{"type":"integer","title":"Tier Reached","description":"Highest `OnboardingTier` value this redemption reached.","examples":[2]},"signed_up_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Signed Up At","description":"Signup timestamp, or null if not reached.","examples":["2026-06-21T18:05:30Z"]},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"Verification timestamp, or null if not reached.","examples":["2026-06-21T18:06:10Z"]}},"type":"object","required":["id","batch_id","demo_session_id","fingerprint","redeemed_at","user_id","tier_reached","signed_up_at","verified_at"],"title":"Redemption","description":"One row of the redemption audit ledger.\n\nTracks a single QR redemption and how far up the trust ladder the resulting\nuser climbed. Surfaced via ``GET /admin/redemptions``."},"cbonboard__RedemptionList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbonboard__Redemption"},"type":"array","title":"Items","description":"The redemption rows in this response."},"count":{"type":"integer","title":"Count","description":"Number of items returned (length of `items`).","examples":[12]}},"type":"object","required":["items","count"],"title":"RedemptionList","description":"Paged-style envelope returned by ``GET /admin/redemptions``."},"cbonboard__ResendRequest":{"properties":{"channel":{"type":"string","title":"Channel","description":"Channel to re-send the verification code on.","default":"sms","examples":["sms"]}},"type":"object","title":"ResendRequest","description":"Request body for ``POST /onboard/resend`` — re-issue a verification code.","example":{"channel":"sms"}},"cbonboard__SessionStatus":{"properties":{"user_id":{"type":"string","title":"User Id","description":"cbauth user id behind the bearer token.","examples":["usr_01HX..."]},"tier":{"$ref":"#/components/schemas/cbonboard__OnboardingTier","description":"Current trust tier of the user."},"verified":{"type":"boolean","title":"Verified","description":"True once the user has reached the `verified` tier.","examples":[false]}},"type":"object","required":["user_id","tier","verified"],"title":"SessionStatus","description":"Current verification state for the user behind a bearer token.\n\nReturned by GET /session/status — used by the signup PWA's verify page\nas a polling fallback when WebSocket is unavailable.","example":{"tier":1,"user_id":"usr_01HX...","verified":false}},"cbonboard__SignupRequest":{"properties":{"first_name":{"type":"string","maxLength":80,"minLength":1,"title":"First Name","description":"User's given name.","examples":["Alex"]},"last_name":{"type":"string","maxLength":80,"minLength":1,"title":"Last Name","description":"User's family name.","examples":["Johnson"]},"phone":{"type":"string","pattern":"^\\+?[1-9]\\d{7,14}$","title":"Phone","description":"E.164-style phone number; the SMS verification code is sent here. Required.","examples":["+15125551234"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"The person's real address — prefilled from the invite by the form, editable. When present (or when the invite carries one) the cbauth account is created under it and `password` is required.","examples":["alex@example.com"]},"password":{"anyOf":[{"type":"string","maxLength":200,"minLength":8},{"type":"null"}],"title":"Password","description":"Initial password, set on the cbauth user at create. Required whenever an email is known; ignored on the no-email kiosk path.","examples":["correct-horse-battery"]}},"type":"object","required":["first_name","last_name","phone"],"title":"SignupRequest","description":"Request body for ``POST /onboard/signup``.\n\nSent with the Tier-0 ``session_token`` as a bearer credential.\n\nTwo shapes board here (cbonboard#6). A person with an **email** — the\ninvite's, or one they typed — is created as a real cbauth user under that\naddress and MUST send a ``password``; the form asks for one \"no matter what\nafter name\". A session with genuinely no email (a kiosk QR scan that gave\nnone) is boarded as before: a provisional, password-less account under a\nsynthetic address, SMS its only door. That gate is the presence of an\nemail, not who is calling.","example":{"email":"alex@example.com","first_name":"Alex","last_name":"Johnson","phone":"+15125551234"}},"cbonboard__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbonboard__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbonboard__UpdateDemoSessionRequest":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Label","description":"New operator-facing label.","examples":["Ownership Demo — Day 2"]},"max_redemptions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Redemptions","description":"New per-batch redemption cap.","examples":[50]},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"New expiry timestamp for the session.","examples":["2026-06-21T22:00:00Z"]},"landing_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Landing Url","description":"New post-signup destination (same constraint as CreateDemoSessionRequest.landing_url). Omitted = unchanged.","examples":["https://svi.campaignbrain.dev"]}},"type":"object","title":"UpdateDemoSessionRequest","description":"Request body for ``PATCH /admin/demo-sessions/{id}``.\n\nA partial update — supply at least one field; omitted fields are unchanged.","example":{"label":"Ownership Demo — Day 2","max_redemptions":50}},"cbonboard__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbonboard__VerifyRequest":{"properties":{"channel":{"type":"string","title":"Channel","description":"Channel the code was delivered on.","examples":["sms","email"]},"code":{"type":"string","maxLength":8,"minLength":4,"title":"Code","description":"The numeric verification code the user received.","examples":["482917"]}},"type":"object","required":["channel","code"],"title":"VerifyRequest","description":"Request body for ``POST /onboard/verify``.\n\nSubmits the verification code the user received on a channel; sent with the\nprovisional user's bearer token.","example":{"channel":"sms","code":"482917"}},"cboverseer__AgentDispatchRequest":{"properties":{"agent_type":{"$ref":"#/components/schemas/cboverseer__AgentType","description":"Type of agent to dispatch"},"host":{"type":"string","title":"Host","description":"Host to monitor","default":"localhost"},"check_interval":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Check Interval","description":"Override default check interval"},"immediate":{"type":"boolean","title":"Immediate","description":"Run check immediately on dispatch","default":false}},"type":"object","required":["agent_type"],"title":"AgentDispatchRequest","description":"Request to dispatch a monitoring agent."},"cboverseer__AgentInfo":{"properties":{"agent_id":{"type":"string","title":"Agent Id","description":"Unique agent identifier"},"agent_type":{"$ref":"#/components/schemas/cboverseer__AgentType","description":"Type of agent"},"host":{"type":"string","title":"Host","description":"Host being monitored","default":"localhost"},"status":{"$ref":"#/components/schemas/cboverseer__AgentStatus","default":"idle"},"last_check":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Check","description":"Last successful check time"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error","description":"Last error message if any"},"check_interval":{"type":"integer","title":"Check Interval","description":"Check interval in seconds","default":60},"metrics_collected":{"type":"integer","title":"Metrics Collected","description":"Total metrics collected","default":0},"alerts_generated":{"type":"integer","title":"Alerts Generated","description":"Total alerts generated","default":0},"started_at":{"type":"string","format":"date-time","title":"Started At"}},"type":"object","required":["agent_id","agent_type"],"title":"AgentInfo","description":"Information about a monitoring agent."},"cboverseer__AgentStatus":{"type":"string","enum":["idle","running","error","disconnected"],"title":"AgentStatus","description":"Agent status states."},"cboverseer__AgentType":{"type":"string","enum":["system","services","database","network","nginx","files","user","cron","sites","leds"],"title":"AgentType","description":"Types of monitoring agents."},"cboverseer__Alert":{"properties":{"severity":{"$ref":"#/components/schemas/cboverseer__AlertSeverity"},"metric":{"type":"string","title":"Metric","description":"Name of the metric that triggered the alert"},"value":{"type":"number","title":"Value","description":"Current value of the metric"},"threshold":{"type":"number","title":"Threshold","description":"Threshold that was crossed"},"message":{"type":"string","title":"Message","description":"Human-readable alert message"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","required":["severity","metric","value","threshold","message"],"title":"Alert","description":"Alert triggered by a metric crossing a threshold."},"cboverseer__AlertSeverity":{"type":"string","enum":["info","warning","critical","resolved"],"title":"AlertSeverity","description":"Alert severity levels."},"cboverseer__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cboverseer__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cboverseer__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cboverseer__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cboverseer__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cboverseer__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cboverseer__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cboverseer__Incident":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"incident_id":{"type":"string","title":"Incident Id","description":"Unique incident identifier"},"service":{"type":"string","title":"Service","description":"Affected service name"},"datacenter":{"type":"string","title":"Datacenter","description":"Datacenter identifier (dc0, dc1)","default":""},"severity":{"$ref":"#/components/schemas/cboverseer__IncidentSeverity","description":"Incident severity"},"status":{"$ref":"#/components/schemas/cboverseer__IncidentStatus","default":"open"},"title":{"type":"string","title":"Title","description":"Brief incident description"},"description":{"type":"string","title":"Description","description":"Detailed incident description"},"metrics_snapshot":{"anyOf":[{"$ref":"#/components/schemas/cboverseer__MetricsSnapshot"},{"type":"null"}],"description":"Metrics at time of incident"},"alerts":{"items":{"$ref":"#/components/schemas/cboverseer__Alert"},"type":"array","title":"Alerts","description":"Alerts that triggered this incident"},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At","description":"Resolution timestamp"},"resolution":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution","description":"Resolution description"},"auto_fixed":{"type":"boolean","title":"Auto Fixed","description":"Was this auto-remediated?","default":false},"remediation_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Remediation Action","description":"Action taken if auto-fixed"},"similar_incident_ids":{"items":{"type":"string"},"type":"array","title":"Similar Incident Ids","description":"IDs of similar past incidents"},"cbscout_memory_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbscout Memory Id","description":"cbscout memory ID for this incident"},"root_cause":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Root Cause","description":"AI-determined root cause"},"diagnosis_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Diagnosis Confidence","description":"AI diagnosis confidence (0-100)"},"fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fingerprint","description":"Stable (service, incident:<metric>) signature — the mailbox key"},"recurrence_count":{"type":"integer","title":"Recurrence Count","description":"Occurrences across resolve→reopen cycles (mailbox count)","default":1}},"type":"object","required":["incident_id","service","severity","title","description"],"title":"Incident","description":"System incident record."},"cboverseer__IncidentCreate":{"properties":{"service":{"type":"string","title":"Service","description":"Affected service name"},"severity":{"$ref":"#/components/schemas/cboverseer__IncidentSeverity","description":"Incident severity"},"title":{"type":"string","title":"Title","description":"Brief incident description"},"description":{"type":"string","title":"Description","description":"Detailed incident description"},"metrics_snapshot":{"anyOf":[{"$ref":"#/components/schemas/cboverseer__MetricsSnapshot"},{"type":"null"}]},"alerts":{"items":{"$ref":"#/components/schemas/cboverseer__Alert"},"type":"array","title":"Alerts"}},"type":"object","required":["service","severity","title","description"],"title":"IncidentCreate","description":"Request to create a new incident."},"cboverseer__IncidentResolution":{"properties":{"resolution":{"type":"string","title":"Resolution","description":"Description of how the issue was fixed"},"auto_fixed":{"type":"boolean","title":"Auto Fixed","description":"Was this auto-remediated?","default":false},"remediation_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Remediation Action","description":"Action taken if auto-fixed"},"root_cause":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Root Cause","description":"Root cause if determined"}},"type":"object","required":["resolution"],"title":"IncidentResolution","description":"Request to resolve an incident."},"cboverseer__IncidentSeverity":{"type":"string","enum":["info","warning","critical"],"title":"IncidentSeverity","description":"Incident severity levels."},"cboverseer__IncidentStatus":{"type":"string","enum":["open","acknowledged","resolving","resolved","closed"],"title":"IncidentStatus","description":"Incident status states."},"cboverseer__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cboverseer__MetricsSnapshot":{"properties":{"memory_percent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Memory Percent","description":"Memory usage percentage (0-100)"},"memory_available_gb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Memory Available Gb","description":"Available memory in GB"},"disk_percent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Disk Percent","description":"Disk usage percentage (0-100)"},"disk_available_gb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Disk Available Gb","description":"Available disk space in GB"},"cpu_percent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cpu Percent","description":"CPU usage percentage (0-100)"},"load_1m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load 1M","description":"1-minute load average"},"load_5m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load 5M","description":"5-minute load average"},"load_15m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load 15M","description":"15-minute load average"},"process_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Process Count","description":"Total running processes"},"zombie_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Zombie Count","description":"Zombie process count"},"network_connections":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Network Connections","description":"Active network connections"},"network_bytes_sent":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Network Bytes Sent","description":"Bytes sent since boot"},"network_bytes_recv":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Network Bytes Recv","description":"Bytes received since boot"},"service_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service Name","description":"Service name if service-specific"},"response_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Response Time Ms","description":"Health endpoint response time"},"is_healthy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Healthy","description":"Health check status"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds","description":"Service uptime in seconds"}},"type":"object","title":"MetricsSnapshot","description":"System metrics at a point in time."},"cboverseer__PhoneNumberUpdate":{"properties":{"phone_numbers":{"items":{"type":"string"},"type":"array","title":"Phone Numbers"}},"type":"object","required":["phone_numbers"],"title":"PhoneNumberUpdate","description":"Request to update alert phone numbers."},"cboverseer__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cboverseer__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cboverseer__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cboverseer__RemediationActionType":{"type":"string","enum":["restart_service","clear_cache","rotate_logs","kill_zombie","nginx_reload","ban_ip"],"title":"RemediationActionType","description":"Types of automated remediation actions."},"cboverseer__RulingCreate":{"properties":{"fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fingerprint","description":"The (service:incident:<metric>) signature to rule.","examples":["cbtapwatch:incident:err_rate"]},"service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service","examples":["cbtapwatch"]},"metric":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metric","examples":["err_rate"]},"disposition":{"type":"string","title":"Disposition","description":"known_noise | acknowledged | muted (suppress) | watch (advisory).","examples":["known_noise"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","examples":["4xx auth noise, not a fault"]},"ruled_by":{"type":"string","title":"Ruled By","default":"operator"}},"type":"object","required":["disposition"],"title":"RulingCreate","description":"Rule a fingerprint. Provide ``fingerprint`` directly, or ``service``\n(+ optional ``metric``) to have it derived the same way alerts are keyed."},"cboverseer__SMSResponse":{"properties":{"incident_id":{"type":"string","title":"Incident Id"},"response":{"type":"string","title":"Response"}},"type":"object","required":["incident_id","response"],"title":"SMSResponse","description":"Request body for incoming SMS response."},"cboverseer__SMSWebhookRequest":{"properties":{"sender":{"type":"string","title":"Sender","description":"Sender phone number"},"message":{"type":"string","title":"Message","description":"Message content"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp","description":"Message timestamp"},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Id","description":"cbsms message ID"}},"type":"object","required":["sender","message"],"title":"SMSWebhookRequest","description":"Incoming SMS webhook request from cbsms."},"cboverseer__SMSWebhookResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether message was processed"},"reply":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reply","description":"Reply message to send"},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"Conversation ID"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if failed"}},"type":"object","required":["success"],"title":"SMSWebhookResponse","description":"Response to SMS webhook."},"cboverseer__ServiceDependency":{"properties":{"source_service":{"type":"string","title":"Source Service","description":"Service that depends on target"},"target_service":{"type":"string","title":"Target Service","description":"Service being depended upon"},"dependency_type":{"type":"string","title":"Dependency Type","description":"Type: 'api_call', 'database', 'message_queue'"},"discovered_at":{"type":"string","format":"date-time","title":"Discovered At"},"last_seen_at":{"type":"string","format":"date-time","title":"Last Seen At"},"call_frequency":{"type":"integer","title":"Call Frequency","description":"Observed call frequency","default":0},"is_healthy":{"type":"boolean","title":"Is Healthy","description":"Current health of dependency","default":true}},"type":"object","required":["source_service","target_service","dependency_type"],"title":"ServiceDependency","description":"Dependency relationship between services."},"cboverseer__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cboverseer__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cboverseer__TelemetryMessage":{"properties":{"type":{"type":"string","title":"Type","description":"Message type","default":"TELEMETRY"},"agent":{"type":"string","title":"Agent","description":"Agent identifier (e.g., 'agent-system')"},"host":{"type":"string","title":"Host","description":"Host the agent is monitoring","default":"localhost"},"datacenter":{"type":"string","title":"Datacenter","description":"Datacenter identifier (dc0, dc1)","default":""},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"metrics":{"$ref":"#/components/schemas/cboverseer__MetricsSnapshot","description":"Collected metrics"},"alerts":{"items":{"$ref":"#/components/schemas/cboverseer__Alert"},"type":"array","title":"Alerts","description":"Generated alerts"}},"type":"object","required":["agent","metrics"],"title":"TelemetryMessage","description":"Telemetry message from a monitoring agent."},"cboverseer__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbpayments__AccountListItem":{"properties":{"account_code":{"type":"string","title":"Account Code","description":"Unique account identifier","examples":["Y7J7HL"]},"committee_name":{"type":"string","title":"Committee Name","description":"Committee or organization name"},"company_name":{"type":"string","title":"Company Name","description":"Company name on file"},"campaign_count":{"type":"integer","title":"Campaign Count","description":"Count of active campaigns under this account"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the account is active"}},"type":"object","required":["account_code","committee_name","company_name","campaign_count","is_active"],"title":"AccountListItem","description":"Account summary for discovery listings."},"cbpayments__AccountListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbpayments__AccountListItem"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total","description":"Total matching accounts across all pages"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["items","total","limit","offset"],"title":"AccountListResponse","description":"Paginated account discovery listing."},"cbpayments__Body_login_auth_token_post":{"properties":{"grant_type":{"anyOf":[{"type":"string","pattern":"^password$"},{"type":"null"}],"title":"Grant Type"},"username":{"type":"string","title":"Username"},"password":{"type":"string","format":"password","title":"Password"},"scope":{"type":"string","title":"Scope","default":""},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"password","title":"Client Secret"}},"type":"object","required":["username","password"],"title":"Body_login_auth_token_post"},"cbpayments__CampaignDiscoveryItem":{"properties":{"campaign_code":{"type":"string","title":"Campaign Code","description":"Unique campaign identifier","examples":["CAMP2024Q1"]},"campaign_name":{"type":"string","title":"Campaign Name","description":"Display name","examples":["Spring Fundraiser 2024"]},"account_code":{"type":"string","title":"Account Code","description":"Owning account code","examples":["Y7J7HL"]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the campaign accepts donations"}},"type":"object","required":["campaign_code","campaign_name","account_code","is_active"],"title":"CampaignDiscoveryItem","description":"Campaign summary for cross-account discovery listings."},"cbpayments__CampaignDiscoveryResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbpayments__CampaignDiscoveryItem"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total","description":"Total matching campaigns across all pages"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["items","total","limit","offset"],"title":"CampaignDiscoveryResponse","description":"Paginated cross-account campaign discovery listing."},"cbpayments__CampaignInfoResponse":{"properties":{"title":{"type":"string","title":"Title","description":"Page title from DonationPageTitle attribute or campaign name"},"message":{"type":"string","title":"Message","description":"Page subtitle from DonationPageMessage or campaign description"},"disclaimer":{"type":"string","title":"Disclaimer","description":"Legal disclaimer text","default":""},"compliance_text":{"type":"string","title":"Compliance Text","description":"Campaign finance compliance notice","default":""},"custom1_heading":{"type":"string","title":"Custom1 Heading","description":"Label for custom field 1 (employer-related)","default":""},"custom2_heading":{"type":"string","title":"Custom2 Heading","description":"Label for custom field 2","default":""},"custom3_heading":{"type":"string","title":"Custom3 Heading","description":"Label for custom field 3","default":""},"is_custom1_required":{"type":"boolean","title":"Is Custom1 Required","description":"Whether custom field 1 is required","default":false},"is_custom2_required":{"type":"boolean","title":"Is Custom2 Required","description":"Whether custom field 2 is required","default":false},"is_custom3_required":{"type":"boolean","title":"Is Custom3 Required","description":"Whether custom field 3 is required","default":false},"requires_cvv":{"type":"boolean","title":"Requires Cvv","description":"Whether CVV is required for card payments","default":true},"is_variable_amount_allowed":{"type":"boolean","title":"Is Variable Amount Allowed","description":"If false, donor must choose a preset donation level","default":true},"donation_levels":{"items":{"$ref":"#/components/schemas/cbpayments__DonationLevelItem"},"type":"array","title":"Donation Levels","description":"Preset donation amount options","default":[]},"custom_dropdown_question":{"type":"string","title":"Custom Dropdown Question","description":"Custom dropdown question text","default":""},"custom_dropdown_options":{"type":"string","title":"Custom Dropdown Options","description":"Comma-separated options for custom dropdown","default":""},"theme_name":{"type":"string","title":"Theme Name","description":"CSS theme name","default":""},"theme_color":{"type":"string","title":"Theme Color","description":"Primary theme color hex code","default":""},"donation_target":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Donation Target","description":"Fundraising goal amount (null if no target)"}},"type":"object","required":["title","message"],"title":"CampaignInfoResponse","description":"Campaign metadata for rendering a donation form.\n\nIncludes EAV-driven customization (title, colors, custom fields)\nand donation level presets. Use this to build a complete donation page.","example":{"compliance_text":"Paid for by Friends of Example","disclaimer":"","donation_levels":[{"amount":25.0,"label":"Supporter"},{"amount":50.0,"label":"Champion"}],"donation_target":50000.0,"is_variable_amount_allowed":true,"message":"Every dollar makes a difference.","requires_cvv":true,"title":"Support Our Campaign"}},"cbpayments__CampaignListItem":{"properties":{"campaign_code":{"type":"string","title":"Campaign Code","description":"Unique campaign identifier","examples":["CAMP2024Q1"]},"campaign_name":{"type":"string","title":"Campaign Name","description":"Display name","examples":["Spring Fundraiser 2024"]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the campaign accepts donations"}},"type":"object","required":["campaign_code","campaign_name","is_active"],"title":"CampaignListItem","description":"Summary of a campaign in a listing."},"cbpayments__CampaignListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbpayments__CampaignListItem"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"CampaignListResponse","description":"List of active campaigns for an account."},"cbpayments__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbpayments__DonationLevelItem":{"properties":{"amount":{"type":"number","title":"Amount","description":"Dollar amount for this level","examples":[25.0,50.0,100.0]},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Display label (e.g. 'Bronze Supporter')","examples":["Bronze Supporter"]}},"type":"object","required":["amount"],"title":"DonationLevelItem","description":"A preset donation amount option for a campaign."},"cbpayments__DonationRequest":{"properties":{"first_name":{"type":"string","title":"First Name","description":"Donor first name","examples":["Jane"]},"last_name":{"type":"string","title":"Last Name","description":"Donor last name","examples":["Smith"]},"email":{"type":"string","title":"Email","description":"Donor email address","examples":["jane@example.com"]},"address1":{"type":"string","title":"Address1","description":"Street address line 1","examples":["123 Main St"]},"city":{"type":"string","title":"City","description":"City","examples":["Springfield"]},"state":{"type":"string","title":"State","description":"Two-letter state code","examples":["IL"]},"zip":{"type":"string","title":"Zip","description":"ZIP code","examples":["62704"]},"phone":{"type":"string","title":"Phone","description":"Phone number","default":""},"address2":{"type":"string","title":"Address2","description":"Street address line 2","default":""},"donation_amount":{"type":"number","exclusiveMinimum":0.0,"title":"Donation Amount","description":"Amount in dollars (minimum $3.00)"},"occupation":{"type":"string","title":"Occupation","description":"Donor occupation (may be required by campaign)","default":""},"employer":{"type":"string","title":"Employer","description":"Donor employer (may be required by campaign)","default":""},"tracking_code":{"type":"string","title":"Tracking Code","description":"Optional tracking/source code","default":""},"custom_answer":{"type":"string","title":"Custom Answer","description":"Answer to campaign's custom question","default":""},"employer_extra1":{"type":"string","title":"Employer Extra1","description":"Campaign-specific extra field 1","default":""},"employer_extra2":{"type":"string","title":"Employer Extra2","description":"Campaign-specific extra field 2","default":""},"employer_extra3":{"type":"string","title":"Employer Extra3","description":"Campaign-specific extra field 3","default":""},"credit_card":{"type":"string","title":"Credit Card","description":"Full card number (not stored)","examples":["4111111111111111"]},"expiry_month":{"type":"string","title":"Expiry Month","description":"Card expiry month (01-12)","examples":["12"]},"expiry_year":{"type":"string","title":"Expiry Year","description":"Card expiry year (YY or YYYY)","examples":["2026"]},"cvv":{"type":"string","title":"Cvv","description":"Card verification value (3-4 digits)","default":""},"name_on_card":{"type":"string","title":"Name On Card","description":"Name on card (defaults to first_name + last_name)","default":""},"bill_address1":{"type":"string","title":"Bill Address1","description":"Billing address (defaults to address1)","default":""},"bill_city":{"type":"string","title":"Bill City","description":"Billing city (defaults to city)","default":""},"bill_state":{"type":"string","title":"Bill State","description":"Billing state (defaults to state)","default":""},"bill_zip":{"type":"string","title":"Bill Zip","description":"Billing ZIP (defaults to zip)","default":""}},"type":"object","required":["first_name","last_name","email","address1","city","state","zip","donation_amount","credit_card","expiry_month","expiry_year"],"title":"DonationRequest","description":"Credit card donation submission.\n\nIncludes donor info, payment card details, and optional employment/custom fields.\nCard data is passed to Authorize.Net and never stored.","example":{"address1":"123 Main St","city":"Springfield","credit_card":"4111111111111111","cvv":"123","donation_amount":50.0,"email":"jane@example.com","expiry_month":"12","expiry_year":"2026","first_name":"Jane","last_name":"Smith","state":"IL","zip":"62704"}},"cbpayments__DonationResponse":{"properties":{"result_code":{"type":"integer","title":"Result Code","description":"0=success, 1=not found/bad key, 2=inactive, 3=bad amount, 4=level mismatch, 5=limit exceeded, 6=declined/rate limited"},"result_message":{"type":"string","title":"Result Message","description":"Human-readable result description"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data","description":"On success: {DonationID, TransactionID}"}},"type":"object","required":["result_code","result_message"],"title":"DonationResponse","description":"Result of a donation submission.\n\nCheck result_code to determine outcome:\n- 0 = success (data contains DonationID + TransactionID)\n- 1-6 = various failure reasons (see result_message)","example":{"data":{"DonationID":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","TransactionID":"60123456789"},"result_code":0,"result_message":"Success"}},"cbpayments__DonorItem":{"properties":{"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"email_address":{"type":"string","title":"Email Address"}},"type":"object","required":["first_name","last_name","email_address"],"title":"DonorItem"},"cbpayments__DonorsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbpayments__DonorItem"},"type":"array","title":"Items"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["items","limit","offset"],"title":"DonorsResponse"},"cbpayments__FeedEntry":{"properties":{"donor_name":{"type":"string","title":"Donor Name","description":"Donor display name (first name + last initial)","examples":["Jane S."]},"location":{"type":"string","title":"Location","description":"City, State","examples":["Springfield, IL"]},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount","description":"Donation amount (null if feed hides amounts)"},"date":{"type":"string","format":"date-time","title":"Date","description":"Donation date"}},"type":"object","required":["donor_name","location","date"],"title":"FeedEntry","description":"A single donation in the public feed."},"cbpayments__FeedResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbpayments__FeedEntry"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"FeedResponse","description":"Public donation feed for an account."},"cbpayments__ForgotPasswordRequest":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"ForgotPasswordRequest"},"cbpayments__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbpayments__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbpayments__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbpayments__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbpayments__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbpayments__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbpayments__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbpayments__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbpayments__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbpayments__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbpayments__ResetPasswordRequest":{"properties":{"token":{"type":"string","title":"Token"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["token","new_password"],"title":"ResetPasswordRequest"},"cbpayments__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbpayments__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbpayments__Token":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"JWT access token"},"token_type":{"type":"string","title":"Token Type","description":"Token type (always 'bearer')","default":"bearer"}},"type":"object","required":["access_token"],"title":"Token","description":"JWT token response from /auth/token.","example":{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","token_type":"bearer"}},"cbpayments__User":{"properties":{"user_id":{"type":"string","title":"User Id","description":"Unique user identifier (GUID)"},"user_name":{"type":"string","title":"User Name","description":"Login username","examples":["admin"]},"email_address":{"type":"string","title":"Email Address","description":"User email","examples":["admin@example.com"]},"name":{"type":"string","title":"Name","description":"Display name","examples":["Admin User"]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the user can log in"},"user_type":{"type":"integer","title":"User Type","description":"Portal access level: 0=Account, 1=Consultant, 2=Admin, 3=Donor"},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id","description":"Linked account ID (for user_type=0 merchant users)"},"consultant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consultant Id","description":"Linked consultant ID (for user_type=1 consultant users)"},"created_at_utc":{"type":"string","format":"date-time","title":"Created At Utc","description":"Account creation timestamp"}},"type":"object","required":["user_id","user_name","email_address","name","is_active","user_type","created_at_utc"],"title":"User","description":"Public user representation returned by /auth/me."},"cbpayments__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbpersonnel__AllocationOut":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/cbpersonnel__AllocationRowOut"},"type":"array","title":"Rows"},"total_people":{"type":"integer","title":"Total People"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_health":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Health"},"total_reimbursements":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Reimbursements"}},"type":"object","required":["rows","total_people","total_gross","total_health","total_reimbursements"],"title":"AllocationOut"},"cbpersonnel__AllocationRowOut":{"properties":{"entity_code":{"type":"string","title":"Entity Code"},"entity_name":{"type":"string","title":"Entity Name"},"people":{"type":"integer","title":"People"},"gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross"},"health":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Health"},"reimbursements":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Reimbursements"},"pct_of_payroll":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Pct Of Payroll"}},"type":"object","required":["entity_code","entity_name","people","gross","health","reimbursements","pct_of_payroll"],"title":"AllocationRowOut"},"cbpersonnel__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbpersonnel__EmployeeOut":{"properties":{"id":{"type":"string","title":"Id"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"worker_type":{"type":"string","title":"Worker Type"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"default_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Entity Id"},"rate_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Rate Amount"},"rate_type":{"type":"string","title":"Rate Type"},"status":{"type":"string","title":"Status"},"paychex_employee_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paychex Employee Id"},"paychex_worker_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paychex Worker Id"}},"type":"object","required":["id","first_name","last_name","worker_type","rate_amount","rate_type","status"],"title":"EmployeeOut"},"cbpersonnel__EmployeePatch":{"properties":{"default_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Entity Id"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"}},"type":"object","title":"EmployeePatch","description":"Only the two CB-owned fields are editable.\n\nEverything else belongs to Paychex (spec ruling 5) and a sync overwrites it,\nso accepting a write here would create edits that silently disappear."},"cbpersonnel__EntityOut":{"properties":{"id":{"type":"string","title":"Id"},"code":{"type":"string","title":"Code"},"legal_name":{"type":"string","title":"Legal Name"},"is_parent":{"type":"boolean","title":"Is Parent"},"is_active":{"type":"boolean","title":"Is Active"},"sort_order":{"type":"integer","title":"Sort Order"}},"type":"object","required":["id","code","legal_name","is_parent","is_active","sort_order"],"title":"EntityOut"},"cbpersonnel__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbpersonnel__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbpersonnel__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbpersonnel__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbpersonnel__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbpersonnel__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbpersonnel__LineAllocationIn":{"properties":{"entity_id":{"type":"string","title":"Entity Id"},"basis":{"type":"string","title":"Basis","description":"percent | flat | remainder"},"value":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Value"}},"type":"object","required":["entity_id","basis"],"title":"LineAllocationIn","description":"One requested share (cbpersonnel#4).\n\n`value` is required for `percent` and `flat` and must be absent for\n`remainder`, which is whatever the other rows did not take. The basis is\nsubmitted rather than inferred: 1500.00 could be a flat charge or a\npercentage of a very large line, and only the approval knows which."},"cbpersonnel__LineAllocationOut":{"properties":{"entity_id":{"type":"string","title":"Entity Id"},"entity_code":{"type":"string","title":"Entity Code"},"basis":{"type":"string","title":"Basis"},"value":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Value"},"amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Amount"},"approved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved By"},"approved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved At"}},"type":"object","required":["entity_id","entity_code","basis","amount"],"title":"LineAllocationOut"},"cbpersonnel__LineIn":{"properties":{"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"base_salary":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Base Salary","default":"0"},"hours":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Hours","default":"0"},"commission":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Commission","default":"0"},"tips":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Tips","default":"0"},"bonus":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Bonus","default":"0"},"health":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Health","default":"0"},"hsa":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Hsa","default":"0"},"other_post_tax":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Other Post Tax","default":"0"},"reimbursements":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Reimbursements","default":"0"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"LineIn"},"cbpersonnel__LineOut":{"properties":{"id":{"type":"string","title":"Id"},"employee_id":{"type":"string","title":"Employee Id"},"employee_name":{"type":"string","title":"Employee Name"},"worker_type":{"type":"string","title":"Worker Type"},"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"entity_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Code"},"base_salary":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Base Salary"},"hours":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Hours"},"hourly_rate_snapshot":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Hourly Rate Snapshot"},"commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission"},"tips":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Tips"},"bonus":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Bonus"},"health":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Health"},"hsa":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Hsa"},"other_post_tax":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Other Post Tax"},"reimbursements":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Reimbursements"},"gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross"},"net_pre_tax":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Net Pre Tax","description":"Gross less deductions plus reimbursements. NOT take-home pay."},"status":{"type":"string","title":"Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"allocations":{"items":{"$ref":"#/components/schemas/cbpersonnel__LineAllocationOut"},"type":"array","title":"Allocations"}},"type":"object","required":["id","employee_id","employee_name","worker_type","base_salary","hours","hourly_rate_snapshot","commission","tips","bonus","health","hsa","other_post_tax","reimbursements","gross","net_pre_tax","status"],"title":"LineOut"},"cbpersonnel__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbpersonnel__PeriodOut":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label"},"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"},"pay_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pay Date"},"status":{"type":"string","title":"Status"},"is_locked":{"type":"boolean","title":"Is Locked"},"exists":{"type":"boolean","title":"Exists"}},"type":"object","required":["key","label","period_start","period_end","status","is_locked","exists"],"title":"PeriodOut"},"cbpersonnel__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbpersonnel__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbpersonnel__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbpersonnel__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbpersonnel__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbpersonnel__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbprint__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbprint__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbprint__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbprint__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbprint__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbprint__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbprint__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbprint__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbprint__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbprint__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbprint__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbprint__PrinterPatch":{"properties":{"info":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Info","description":"Display name (printer-info)."},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location","description":"printer-location."},"shared":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Shared","description":"Advertise on the LAN."},"accepting":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Accepting","description":"Accept new jobs."},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled","description":"Queue enabled (not paused)."},"is_default":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Default","description":"True = make this the system default printer."},"color_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color Mode","description":"print-color-mode-default: auto|color|monochrome."},"sides":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sides","description":"sides-default: one-sided|two-sided-long-edge|two-sided-short-edge."},"media":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Media","description":"media-default, e.g. iso_a4_210x297mm."}},"type":"object","title":"PrinterPatch","description":"Editable CUPS-side settings; omitted fields are untouched."},"cbprint__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbprint__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbprint__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbpublish__Artifact":{"properties":{"id":{"type":"integer","title":"Id","description":"Artifact id."},"tenant":{"type":"string","title":"Tenant","description":"Owning tenant slug."},"name":{"type":"string","title":"Name","description":"Human-readable artifact name."},"kind":{"type":"string","enum":["structured","semistructured"],"title":"Kind","description":"Derived from content_type."},"content_type":{"type":"string","enum":["json","csv","markdown","html","text"],"title":"Content Type","description":"Input format of the body."},"content":{"type":"string","title":"Content","description":"Raw artifact body."},"source_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Ref","description":"Optional provenance / cbfiles reference."},"created_at":{"type":"string","title":"Created At","description":"UTC creation timestamp."}},"type":"object","required":["id","tenant","name","kind","content_type","content","created_at"],"title":"Artifact","description":"A stored artifact — raw ingested content plus derived kind."},"cbpublish__ArtifactInput":{"properties":{"name":{"type":"string","title":"Name","description":"Human-readable artifact name.","examples":["q3-fundraising.json"]},"content_type":{"type":"string","enum":["json","csv","markdown","html","text"],"title":"Content Type","description":"Input format of the body.","default":"text","examples":["json"]},"content":{"type":"string","title":"Content","description":"Raw artifact body (inline)."},"tenant":{"type":"string","title":"Tenant","description":"Owning tenant slug (lowercased).","default":"default","examples":["ky04"]},"source_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Ref","description":"Optional provenance / cbfiles reference."}},"type":"object","required":["name","content"],"title":"ArtifactInput","description":"Request body for ingesting an artifact (inline content, tenant-scoped)."},"cbpublish__ArtifactList":{"properties":{"artifacts":{"items":{"$ref":"#/components/schemas/cbpublish__Artifact"},"type":"array","title":"Artifacts","description":"Matching artifacts."},"count":{"type":"integer","title":"Count","description":"Number of artifacts returned."}},"type":"object","required":["artifacts","count"],"title":"ArtifactList","description":"Artifact listing envelope."},"cbpublish__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbpublish__DocumentRequest":{"properties":{"body_artifact_id":{"type":"integer","title":"Body Artifact Id","description":"Artifact holding the report body (markdown)."},"entity_artifact_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Entity Artifact Id","description":"Artifact holding an entity-grid JSON → back-matter index."},"profile_artifact_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Profile Artifact Id","description":"Artifact holding a per-claim-sourced JSON → citations."},"content_type":{"type":"string","title":"Content Type","description":"Document shape: 'oppo-book' | 'memo' | 'technical-brief' | 'generic'.","default":"oppo-book","examples":["oppo-book"]},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Cover title; defaults to the body's H1.","examples":["Opposition Research: Tudor Dixon"]},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Cover subtitle / subject line.","examples":["2022 Michigan Republican Gubernatorial Nominee"]},"preface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preface","description":"Markdown preface override; else a content-type default."},"theme":{"type":"string","title":"Theme","description":"Visual theme (CSS token set).","default":"campaign-brain","examples":["campaign-brain","white-label"]},"brand":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand","description":"Wordmark override (per-tenant white-label)."},"classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification","description":"Handling banner; null to omit.","default":"CONFIDENTIAL"},"prepared_for":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prepared For","description":"Cover 'Prepared for' attribution.","examples":["Michigan 2026 Coordinated"]},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version","description":"Document version, shown verbatim in the running footer (bottom-center) on every page; null to omit.","examples":["v1.5"]},"page_size":{"type":"string","title":"Page Size","description":"Paper size: Letter | Legal | Tabloid | A4 | A3 | A5.","default":"Letter","examples":["Letter","A4"]},"tenant":{"type":"string","title":"Tenant","description":"Owning tenant slug (lowercased).","default":"default","examples":["mi2026"]},"format":{"type":"string","enum":["pdf","html"],"title":"Format","description":"Output format.","default":"pdf"},"html_math":{"type":"string","title":"Html Math","description":"For format='html', how LaTeX math renders: 'katex' (browser-side, sharper + fuller coverage, needs JS) or 'svg' (baked inline SVG, fully self-contained). PDF always uses SVG. Ignored for PDF.","default":"katex","examples":["katex","svg"]}},"type":"object","required":["body_artifact_id"],"title":"DocumentRequest","description":"Assemble a full publication — cover, preface, TOC, body, sources, index\n— from ingested artifacts, and render it to PDF or self-contained HTML.\n\nArtifacts are referenced by *role*: ``body`` is the report markdown, the\noptional ``entity`` artifact (an entity-grid JSON) drives the index, and the\noptional ``profile`` artifact (a per-claim-sourced JSON) enriches the\ncitations. Only ``body`` is required; missing optional roles simply omit\ntheir section."},"cbpublish__DocumentResponse":{"properties":{"work_product_id":{"type":"integer","title":"Work Product Id","description":"Work product holding the rendered document."},"tenant":{"type":"string","title":"Tenant","description":"Owning tenant slug."},"title":{"type":"string","title":"Title","description":"Resolved document title."},"content_type":{"type":"string","title":"Content Type","description":"Document shape (e.g. 'oppo-book')."},"format":{"type":"string","enum":["pdf","html"],"title":"Format","description":"Rendered output format."},"theme":{"type":"string","title":"Theme","description":"Theme the document was rendered with."},"n_sections":{"type":"integer","title":"N Sections","description":"Table-of-contents entries."},"n_sources":{"type":"integer","title":"N Sources","description":"Citations / sources entries."},"n_index_terms":{"type":"integer","title":"N Index Terms","description":"Back-matter index terms."},"size_bytes":{"type":"integer","title":"Size Bytes","description":"Rendered payload size in bytes."},"raw_url":{"type":"string","title":"Raw Url","description":"Path to fetch the rendered document."}},"type":"object","required":["work_product_id","tenant","title","content_type","format","theme","n_sections","n_sources","n_index_terms","size_bytes","raw_url"],"title":"DocumentResponse","description":"Metadata for an assembled document. The rendered bytes are retrieved\nfrom ``GET /api/v1/workproducts/{id}/raw`` (kept out of this response so it\nstays light for a multi-hundred-KB PDF)."},"cbpublish__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbpublish__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbpublish__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbpublish__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbpublish__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbpublish__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbpublish__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbpublish__NormalizedArtifact":{"properties":{"id":{"type":"integer","title":"Id","description":"Source artifact id."},"kind":{"type":"string","enum":["structured","semistructured"],"title":"Kind","description":"structured | semistructured."},"content_type":{"type":"string","enum":["json","csv","markdown","html","text"],"title":"Content Type","description":"Original input format."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Extracted title, if any."},"text":{"type":"string","title":"Text","description":"Plain-text rendering of the content.","default":""},"data":{"anyOf":[{},{"type":"null"}],"title":"Data","description":"Parsed structured payload (json/csv rows)."},"sections":{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array","title":"Sections","description":"Heading-delimited sections of the text."}},"type":"object","required":["id","kind","content_type"],"title":"NormalizedArtifact","description":"Canonical intermediate representation an artifact normalizes to."},"cbpublish__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbpublish__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbpublish__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbpublish__PublishRequest":{"properties":{"template_id":{"type":"integer","title":"Template Id","description":"Template to render with."},"artifact_ids":{"items":{"type":"integer"},"type":"array","minItems":1,"title":"Artifact Ids","description":"Source artifacts, in render order."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Work-product title; defaults to the template name.","examples":["Q3 Fundraising Brief"]},"tenant":{"type":"string","title":"Tenant","description":"Tenant the render runs as (lowercased).","default":"default","examples":["ky04"]},"format":{"anyOf":[{"type":"string","enum":["markdown","html","text","pdf","xlsx"]},{"type":"null"}],"title":"Format","description":"Override the template's declared output format."}},"type":"object","required":["template_id","artifact_ids"],"title":"PublishRequest","description":"Request body for the one-shot publish pipeline (artifacts + template)."},"cbpublish__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbpublish__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbpublish__Template":{"properties":{"id":{"type":"integer","title":"Id","description":"Template id."},"tenant":{"type":"string","title":"Tenant","description":"Owning tenant slug ('default' = shared)."},"slug":{"type":"string","title":"Slug","description":"URL-safe identifier, unique per tenant."},"name":{"type":"string","title":"Name","description":"Display name."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Human-readable summary."},"format":{"type":"string","enum":["markdown","html","text","pdf","xlsx"],"title":"Format","description":"Declared output format."},"body":{"type":"string","title":"Body","description":"Jinja2 template source."},"created_at":{"type":"string","title":"Created At","description":"UTC creation timestamp."},"updated_at":{"type":"string","title":"Updated At","description":"UTC last-update timestamp."}},"type":"object","required":["id","tenant","slug","name","format","body","created_at","updated_at"],"title":"Template","description":"A stored Jinja2 work-product template (tenant-aware)."},"cbpublish__TemplateInput":{"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","title":"Slug","description":"URL-safe identifier, unique per tenant (lowercased).","examples":["one-pager"]},"name":{"type":"string","title":"Name","description":"Display name.","examples":["Campaign One-Pager"]},"body":{"type":"string","title":"Body","description":"Jinja2 template source."},"format":{"type":"string","enum":["markdown","html","text","pdf","xlsx"],"title":"Format","description":"Declared output format of the rendered product.","default":"markdown"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional human-readable summary."},"tenant":{"type":"string","title":"Tenant","description":"Owning tenant slug ('default' = shared; lowercased).","default":"default","examples":["ky04"]}},"type":"object","required":["slug","name","body"],"title":"TemplateInput","description":"Request body for creating or updating a work-product template."},"cbpublish__TemplateList":{"properties":{"templates":{"items":{"$ref":"#/components/schemas/cbpublish__Template"},"type":"array","title":"Templates","description":"Tenant's templates plus shared 'default' ones."},"count":{"type":"integer","title":"Count","description":"Number of templates returned."}},"type":"object","required":["templates","count"],"title":"TemplateList","description":"Template listing envelope."},"cbpublish__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbpublish__WorkProduct":{"properties":{"id":{"type":"integer","title":"Id","description":"Work-product id."},"tenant":{"type":"string","title":"Tenant","description":"Owning tenant slug."},"title":{"type":"string","title":"Title","description":"Work-product title."},"template_id":{"type":"integer","title":"Template Id","description":"Template rendered with."},"artifact_ids":{"items":{"type":"integer"},"type":"array","title":"Artifact Ids","description":"Source artifacts, in order."},"format":{"type":"string","enum":["markdown","html","text","pdf","xlsx"],"title":"Format","description":"Resolved output format."},"content":{"type":"string","title":"Content","description":"Rendered body ('' in list responses; base64 for PDF)."},"created_at":{"type":"string","title":"Created At","description":"UTC creation timestamp."}},"type":"object","required":["id","tenant","title","template_id","artifact_ids","format","content","created_at"],"title":"WorkProduct","description":"A rendered work product (persisted pipeline output)."},"cbpublish__WorkProductList":{"properties":{"work_products":{"items":{"$ref":"#/components/schemas/cbpublish__WorkProduct"},"type":"array","title":"Work Products","description":"Matching work products (metadata only)."},"count":{"type":"integer","title":"Count","description":"Number of work products returned."}},"type":"object","required":["work_products","count"],"title":"WorkProductList","description":"Work-product listing envelope (rows are metadata-only, content='')."},"cbpulse__AISuggestionResponse":{"properties":{"suggestion":{"type":"string","title":"Suggestion","description":"Suggested reply text"},"knowledge_articles_used":{"items":{"type":"string"},"type":"array","title":"Knowledge Articles Used","description":"Titles of KB articles used as context"},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning","description":"Warning if AI service was unavailable"}},"type":"object","required":["suggestion"],"title":"AISuggestionResponse","description":"AI-generated reply suggestion with context."},"cbpulse__AISummaryResponse":{"properties":{"date":{"type":"string","title":"Date","description":"Summary date in YYYY-MM-DD format"},"flash_report":{"$ref":"#/components/schemas/cbpulse__FlashReportResponse","description":"Full flash report with portfolio and business KPIs"},"exceptions":{"items":{"$ref":"#/components/schemas/cbpulse__ExceptionItem"},"type":"array","title":"Exceptions","description":"All exceptions requiring owner attention"},"variances":{"items":{"$ref":"#/components/schemas/cbpulse__VarianceReport"},"type":"array","title":"Variances","description":"Per-business variance reports with anomalies"},"cross_business_patterns":{"items":{"$ref":"#/components/schemas/cbpulse__CrossBusinessPattern"},"type":"array","title":"Cross Business Patterns","description":"Anomaly patterns spanning multiple businesses"}},"type":"object","required":["date","flash_report","exceptions","variances","cross_business_patterns"],"title":"AISummaryResponse","example":{"cross_business_patterns":[],"date":"2026-02-15","exceptions":[],"flash_report":{},"variances":[]}},"cbpulse__ActionSignalRequest":{"properties":{"notes":{"type":"string","title":"Notes","description":"Free-text notes explaining the action taken","default":"","examples":["Created flash sale campaign targeting weekend occupancy gap"]}},"type":"object","title":"ActionSignalRequest","description":"Payload to action a signal with optional notes."},"cbpulse__AgreementCreate":{"properties":{"agreement_type":{"type":"string","enum":["at_will","fixed_term","contractor","seasonal","management","employment","other"],"title":"Agreement Type"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"effective_date":{"type":"string","title":"Effective Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"status":{"type":"string","enum":["active","ended"],"title":"Status","default":"active"},"rate_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate Amount"},"rate_unit":{"anyOf":[{"type":"string","enum":["hourly","salary","annual","per_shift","commission"]},{"type":"null"}],"title":"Rate Unit"},"rate_currency":{"type":"string","title":"Rate Currency","default":"USD"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["agreement_type","effective_date"],"title":"AgreementCreate"},"cbpulse__AgreementDocument":{"properties":{"id":{"type":"string","title":"Id"},"agreement_id":{"type":"string","title":"Agreement Id"},"file_id":{"type":"string","title":"File Id"},"doc_role":{"type":"string","title":"Doc Role"},"filename":{"type":"string","title":"Filename"},"content_type":{"type":"string","title":"Content Type"},"size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size Bytes"},"uploaded_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uploaded By"},"uploaded_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uploaded At"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","required":["id","agreement_id","file_id","doc_role","filename","content_type","size_bytes","uploaded_by","uploaded_at"],"title":"AgreementDocument"},"cbpulse__AgreementUpdate":{"properties":{"agreement_type":{"anyOf":[{"type":"string","enum":["at_will","fixed_term","contractor","seasonal","management","employment","other"]},{"type":"null"}],"title":"Agreement Type"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"effective_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effective Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"status":{"anyOf":[{"type":"string","enum":["active","ended"]},{"type":"null"}],"title":"Status"},"rate_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate Amount"},"rate_unit":{"anyOf":[{"type":"string","enum":["hourly","salary","annual","per_shift","commission"]},{"type":"null"}],"title":"Rate Unit"},"rate_currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Currency"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"AgreementUpdate"},"cbpulse__AlertResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique alert identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this alert belongs to"},"alert_type":{"type":"string","title":"Alert Type","description":"Rule that triggered this alert, e.g. 'labor_spike', 'low_occupancy'","examples":["labor_spike","low_occupancy","overdue_invoice"]},"severity":{"type":"string","title":"Severity","description":"Alert severity: critical, warning, or info","examples":["critical","warning","info"]},"status":{"type":"string","title":"Status","description":"Lifecycle status: new, acknowledged, assigned, or resolved","examples":["new","acknowledged","assigned","resolved"]},"message":{"type":"string","title":"Message","description":"Human-readable alert description","examples":["Labor cost at The Fred exceeded 35% of revenue for lunch shift","The Saint occupancy dropped below 40% for 3 consecutive days"]},"is_blocking":{"type":"boolean","title":"Is Blocking","description":"If true, blocks certain workflows until resolved"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Rule-specific context data attached when the alert was created"},"acknowledged_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Acknowledged At","description":"ISO-8601 timestamp when the alert was acknowledged"},"acknowledged_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Acknowledged By","description":"User ID who acknowledged the alert"},"assigned_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To","description":"User ID the alert is assigned to for resolution"},"assigned_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned At","description":"ISO-8601 timestamp when the alert was assigned"},"resolved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved At","description":"ISO-8601 timestamp when the alert was resolved"},"resolved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved By","description":"User ID who resolved the alert"},"resolved_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved Notes","description":"Free-text notes added at resolution time"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the alert was created"},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last state change"}},"type":"object","required":["id","business_id","alert_type","severity","status","message","is_blocking","created_at","updated_at"],"title":"AlertResponse","description":"Single alert with full lifecycle state and metadata.","example":{"alert_type":"labor_spike","business_id":"biz_thefred","created_at":"2026-02-15T14:30:00+00:00","id":"alt_8f3a2b1c","is_blocking":false,"message":"Labor cost at The Fred exceeded 35% of revenue for lunch shift","metadata":{"labor_percent":38.2,"shift":"lunch","threshold":35.0},"severity":"warning","status":"new","updated_at":"2026-02-15T14:30:00+00:00"}},"cbpulse__AnnualPackageResponse":{"properties":{"year":{"type":"integer","title":"Year"},"conditions":{"items":{"$ref":"#/components/schemas/cbpulse__ConditionStatus"},"type":"array","title":"Conditions"},"coverage_pct":{"type":"number","title":"Coverage Pct"},"missing_count":{"type":"integer","title":"Missing Count"}},"type":"object","required":["year","conditions","coverage_pct","missing_count"],"title":"AnnualPackageResponse"},"cbpulse__AnomalyItem":{"properties":{"metric":{"type":"string","title":"Metric","description":"KPI name that triggered the anomaly"},"current":{"type":"number","title":"Current","description":"Current observed metric value"},"baseline":{"type":"number","title":"Baseline","description":"Historical baseline value for comparison"},"deviation_pct":{"type":"number","title":"Deviation Pct","description":"Percentage deviation from baseline"},"direction":{"type":"string","title":"Direction","description":"Deviation direction: up or down"},"severity":{"type":"string","title":"Severity","description":"Anomaly severity: critical, warning, or info"}},"type":"object","required":["metric","current","baseline","deviation_pct","direction","severity"],"title":"AnomalyItem","example":{"baseline":32.0,"current":38.5,"deviation_pct":20.3,"direction":"up","metric":"labor_percent","severity":"warning"}},"cbpulse__ApplyVoiceRequest":{"properties":{"text":{"type":"string","title":"Text","description":"Original text to transform with brand voice","examples":["Check out our new menu items for the spring season."]},"source_type":{"type":"string","title":"Source Type","description":"Type of content being transformed (social_post, email, etc.)","default":"manual","examples":["social_post","email","listing"]},"source_id":{"type":"string","title":"Source Id","description":"ID of the source content record","default":"","examples":["post_01HXYZ"]},"field":{"type":"string","title":"Field","description":"Specific field being transformed (caption, subject, body, etc.)","default":"","examples":["caption","subject","body"]}},"type":"object","required":["text"],"title":"ApplyVoiceRequest","example":{"field":"caption","source_id":"post_01HXYZ","source_type":"social_post","text":"Check out our new menu items for the spring season."}},"cbpulse__ApplyVoiceResponse":{"properties":{"original_text":{"type":"string","title":"Original Text","description":"The original input text","examples":["Check out our new menu items for the spring season."]},"transformed_text":{"type":"string","title":"Transformed Text","description":"Text rewritten with brand voice applied","examples":["Island flavors, reimagined. Discover our fresh spring menu at The Fred."]},"edit_id":{"type":"string","title":"Edit Id","description":"ID of the recorded voice edit for audit trail","examples":["ve_01HXYZ"]},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning","description":"Warning message if AI transformation was unavailable","examples":["CBAI unavailable — original text returned unchanged"]}},"type":"object","required":["original_text","transformed_text","edit_id"],"title":"ApplyVoiceResponse","example":{"edit_id":"ve_01HXYZ","original_text":"Check out our new menu items for the spring season.","transformed_text":"Island flavors, reimagined. Discover our fresh spring menu at The Fred — crafted with love, served with a view."}},"cbpulse__ApproveRequest":{"properties":{"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"ApproveRequest"},"cbpulse__ArticleKind":{"type":"string","enum":["news","feature"],"title":"ArticleKind","description":"Which QA prompt profile an article is judged under (cbpulse#577).\n\nA **typed** switch key, deliberately separate from the freeform\n``article_type`` / ``suggested_article_type`` strings, which stay as the\nhuman-readable label. Those are raw LLM output — article ``0b3203b3``\nproduced ``\"Local news / beach feature hybrid\"`` — so they can never be\ncompared against reliably.\n\nOnly two values, and only because gates 6, 7 and 10 must judge them\ndifferently:\n\n- ``FEATURE`` — beach features, evergreen pieces, restaurant write-ups. Most\n  of the archive. Local voice, no formal lede, citations feel out of place.\n- ``NEWS`` — a ranking, an award, an announcement. The lede IS the sourced\n  fact, and inline attribution of the originating publication is *expected*.\n\n``FEATURE`` is the default everywhere so existing behaviour is the fallback\nand nothing regresses: an article is judged as news only when something\npositively says so. Note this means drafts written before #577 stay\n``FEATURE`` until re-planned — deliberate, because the alternative is\nguessing from the freeform string this enum exists to replace."},"cbpulse__ArticleResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique article identifier","examples":["kb_a1b2c3d4"]},"business_id":{"type":"string","title":"Business Id","description":"Business/property this article belongs to","examples":["biz_thefred"]},"title":{"type":"string","title":"Title","description":"Article title","examples":["Pool Hours & Rules"]},"body":{"type":"string","title":"Body","description":"Article body content","examples":["The pool is open daily from 7 AM to 10 PM..."]},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Article category, e.g. 'amenities', 'faq', 'procedures'","examples":["amenities"]},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"List of tags for the article","examples":[["pool","hours","rules"]]},"is_published":{"type":"boolean","title":"Is Published","description":"Whether the article is published and visible","examples":[true]},"is_deleted":{"type":"boolean","title":"Is Deleted","description":"Whether the article has been soft-deleted","examples":[false]},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"User ID who created the article","examples":["user_mgr_01"]},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the article was created","examples":["2026-02-10T09:00:00+00:00"]},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last update","examples":["2026-02-10T09:00:00+00:00"]}},"type":"object","required":["id","business_id","title","body","is_published","is_deleted","created_at","updated_at"],"title":"ArticleResponse","description":"Full knowledge base article.","example":{"body":"The pool is open daily from 7 AM to 10 PM...","business_id":"biz_thefred","category":"amenities","created_at":"2026-02-10T09:00:00+00:00","created_by":"user_mgr_01","id":"kb_a1b2c3d4","is_deleted":false,"is_published":true,"tags":["pool","hours","rules"],"title":"Pool Hours & Rules","updated_at":"2026-02-10T09:00:00+00:00"}},"cbpulse__AssignAgentRequest":{"properties":{"assigned_to":{"type":"string","title":"Assigned To","description":"User ID to assign the conversation to"}},"type":"object","required":["assigned_to"],"title":"AssignAgentRequest","description":"Payload for assigning a conversation to a staff member."},"cbpulse__AttachProofRequest":{"properties":{"document_ids":{"items":{"type":"string"},"type":"array","title":"Document Ids"},"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}},"type":"object","required":["document_ids"],"title":"AttachProofRequest"},"cbpulse__AuditLogResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique audit log entry identifier"},"user_id":{"type":"string","title":"User Id","description":"ID of the user who performed the action"},"user_email":{"type":"string","title":"User Email","description":"Email of the user who performed the action","examples":["maria@thefred.vi","james@thesaint.vi","ops@lovango.vi"]},"action_type":{"type":"string","title":"Action Type","description":"Action performed, e.g. 'order_create', 'alert_resolve', 'invoice_approve'","examples":["alert_resolve","alert_acknowledge","order_create","invoice_approve","log_entry_create"]},"target_type":{"type":"string","title":"Target Type","description":"Type of entity acted upon: order, alert, invoice, ticket, etc.","examples":["alert","order","invoice","manager_log"]},"target_id":{"type":"string","title":"Target Id","description":"ID of the entity that was acted upon","examples":["alt_8f3a2b1c","ord_lunch_0215","inv_sysco_0215"]},"business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id","description":"Business context for the action, if applicable","examples":["biz_thefred","biz_thesaint","biz_lovango"]},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details","description":"Additional action-specific context data"},"ip_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip Address","description":"IP address of the request origin"},"user_agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent","description":"User-Agent header from the request"},"timestamp":{"type":"string","title":"Timestamp","description":"ISO-8601 timestamp when the action occurred"}},"type":"object","required":["id","user_id","user_email","action_type","target_type","target_id","timestamp"],"title":"AuditLogResponse","description":"Immutable record of a user action with target, context, and origin metadata.","example":{"action_type":"alert_resolve","business_id":"biz_thefred","details":{"notes":"Adjusted staffing for Friday dinner rush"},"id":"alog_5f8c2a3b","ip_address":"192.168.1.42","target_id":"alt_8f3a2b1c","target_type":"alert","timestamp":"2026-02-15T16:45:00+00:00","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)","user_email":"maria@thefred.vi","user_id":"usr_maria_gomez"}},"cbpulse__BatchAccepted":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Parent batch job id (poll GET /api/nosj/jobs/{id})"},"child_job_ids":{"items":{"type":"string"},"type":"array","title":"Child Job Ids","description":"One child job id per requested topic"}},"type":"object","required":["job_id"],"title":"BatchAccepted","description":"Async handoff returned by the batch endpoint (the job is pollable)."},"cbpulse__BatchCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Batch display name"},"created_by":{"type":"string","title":"Created By","description":"Who created: claude / manual","default":"claude"},"items":{"items":{"$ref":"#/components/schemas/cbpulse__BatchItemCreate"},"type":"array","title":"Items","description":"Posts to create"}},"type":"object","required":["name","items"],"title":"BatchCreate"},"cbpulse__BatchCreateResponse":{"properties":{"batch_id":{"type":"string","title":"Batch Id"},"posts_created":{"type":"integer","title":"Posts Created"}},"type":"object","required":["batch_id","posts_created"],"title":"BatchCreateResponse"},"cbpulse__BatchDetail":{"properties":{"batch_id":{"type":"string","title":"Batch Id"},"name":{"type":"string","title":"Name"},"created_by":{"type":"string","title":"Created By"},"posts":{"items":{"$ref":"#/components/schemas/cbpulse__src__api__routes__content_queue__PostResponse"},"type":"array","title":"Posts"}},"type":"object","required":["batch_id","name","created_by","posts"],"title":"BatchDetail"},"cbpulse__BatchItemCreate":{"properties":{"vista_profile_id":{"type":"integer","title":"Vista Profile Id","description":"Vista Social numeric profile ID"},"content_type":{"type":"string","title":"Content Type","description":"atmosphere / event / food_drink / booking"},"message":{"type":"string","title":"Message","description":"Post copy"},"media_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Media Path","description":"cbfiles object path"},"media_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Media Url","description":"Pre-signed cbfiles URL"},"publish_at":{"type":"string","title":"Publish At","description":"ISO 8601 datetime with timezone"},"publish_as":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Publish As","description":"FEED / REELS / IMAGE / VIDEO / STORY / CAROUSEL"},"labels":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Labels","description":"Comma-separated Vista Social tags"}},"type":"object","required":["vista_profile_id","content_type","message","publish_at"],"title":"BatchItemCreate"},"cbpulse__BatchRequest":{"properties":{"topics":{"items":{"$ref":"#/components/schemas/cbpulse__BatchTopicRequest"},"type":"array","minItems":1,"title":"Topics","description":"The articles to produce (each new-topic or a refresh)."},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","description":"LLM provider override applied to every worker: ollama | anthropic"}},"type":"object","required":["topics"],"title":"BatchRequest","description":"Body for Mode 3 (batch production). At least one topic is required."},"cbpulse__BatchSummary":{"properties":{"batch_id":{"type":"string","title":"Batch Id"},"name":{"type":"string","title":"Name"},"created_by":{"type":"string","title":"Created By"},"post_count":{"type":"integer","title":"Post Count"},"status_counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Status Counts"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["batch_id","name","created_by","post_count","status_counts","created_at"],"title":"BatchSummary"},"cbpulse__BatchTopicRequest":{"properties":{"topic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Topic","description":"Article topic (Mode 1)"},"article_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Article Type","description":"Optional article-type hint"},"refresh_post_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Refresh Post Id","description":"WP post id to refresh (Mode 2 for this item) instead of new"}},"type":"object","title":"BatchTopicRequest","description":"One requested article in a batch.\n\n``refresh_post_id`` set -> that article is produced via Mode 2 (refresh) of\nthe named WP post; otherwise Mode 1 (single) on ``topic``. ``topic`` is\nrequired unless ``refresh_post_id`` is given."},"cbpulse__Body_attach_agreement_document_api_hr_agreements__agreement_id__documents_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"doc_role":{"type":"string","title":"Doc Role","default":"original"}},"type":"object","required":["file"],"title":"Body_attach_agreement_document_api_hr_agreements__agreement_id__documents_post"},"cbpulse__Body_attach_employee_document_api_hr_employees__employee_id__documents_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"category":{"type":"string","title":"Category"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"}},"type":"object","required":["file","category"],"title":"Body_attach_employee_document_api_hr_employees__employee_id__documents_post"},"cbpulse__Body_attach_separation_document_api_hr_separations__employee_id__documents_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"}},"type":"object","required":["file"],"title":"Body_attach_separation_document_api_hr_separations__employee_id__documents_post"},"cbpulse__Body_upload_separation_letter_api_hr_separations_letter_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_separation_letter_api_hr_separations_letter_post"},"cbpulse__BrandEventCreate":{"properties":{"title":{"type":"string","title":"Title","description":"Event title"},"event_type":{"type":"string","title":"Event Type","description":"Event category"},"confirmation_status":{"type":"string","title":"Confirmation Status","description":"proposed | confirmed","default":"proposed"},"event_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Event Date","description":"ONE-OFF day (mutually exclusive with rrule)"},"rrule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rrule","description":"RECURRING RFC-5545 RRULE, e.g. FREQ=WEEKLY;BYDAY=FR"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Recurrence anchor (required when rrule set)"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Optional recurrence upper bound"},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time","description":"Optional local HH:MM"},"end_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Time","description":"Optional local HH:MM"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Provenance"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes"},"active":{"type":"boolean","title":"Active","description":"Whether the event is active","default":true}},"type":"object","required":["title","event_type"],"title":"BrandEventCreate","example":{"confirmation_status":"confirmed","event_type":"fire_dancers","rrule":"FREQ=WEEKLY;BYDAY=FR","source":"handoff 2026-06-16","start_date":"2026-06-05","title":"Fire dancers"}},"cbpulse__BrandEventResponse":{"properties":{"id":{"type":"string","title":"Id"},"brand_slug":{"type":"string","title":"Brand Slug"},"title":{"type":"string","title":"Title"},"event_type":{"type":"string","title":"Event Type"},"confirmation_status":{"type":"string","title":"Confirmation Status"},"event_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Event Date"},"rrule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rrule"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time"},"end_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Time"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"active":{"type":"boolean","title":"Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","brand_slug","title","event_type","confirmation_status","event_date","rrule","start_date","end_date","start_time","end_time","source","notes","active","created_at","updated_at"],"title":"BrandEventResponse","description":"A stored brand_events row (NOT expanded)."},"cbpulse__BrandEventUpdate":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"event_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Type"},"confirmation_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Status","description":"proposed | confirmed"},"event_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Event Date"},"rrule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rrule"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time"},"end_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Time"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Active"}},"type":"object","title":"BrandEventUpdate"},"cbpulse__BrandProfileResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Brand profile record identifier","examples":["bp_01HXYZ"]},"business_id":{"type":"string","title":"Business Id","description":"Business this profile belongs to","examples":["biz_thefred"]},"voice_tone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice Tone","description":"Desired voice tone (e.g. warm, professional, playful)","examples":["warm, confident, island-casual"]},"voice_personality":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice Personality","description":"Brand personality archetype","examples":["knowledgeable local host"]},"brand_colors":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Brand Colors","description":"List of hex color codes for the brand palette","examples":[["#1A5276","#F4D03F","#FFFFFF"]]},"hashtags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Hashtags","description":"Preferred social media hashtags","examples":[["#TheFredVI","#StCroix"]]},"smart_snippets":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Smart Snippets","description":"Reusable text snippets keyed by purpose (welcome, cta, etc.)","examples":[{"cta":"Book your escape.","welcome":"Welcome to The Fred."}]},"target_audience":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Audience","description":"Description of the target audience","examples":["Luxury-seeking travelers aged 30-55"]},"dos_and_donts":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Dos And Donts","description":"Guidelines: 'dos' and 'donts' lists for brand voice","examples":[{"donts":["Avoid jargon"],"dos":["Use inclusive language"]}]},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL to the brand logo image","examples":["https://files.campaignbrain.dev/thefred/logo.png"]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the brand profile is active","default":true,"examples":[true]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["id","business_id","created_at","updated_at"],"title":"BrandProfileResponse","example":{"brand_colors":["#1A5276","#F4D03F","#FFFFFF"],"business_id":"biz_thefred","created_at":"2026-02-15T10:00:00","dos_and_donts":{"donts":["Avoid corporate jargon","Never use 'cheap'"],"dos":["Use inclusive language","Highlight local culture"]},"hashtags":["#TheFredVI","#StCroix","#USVILife"],"id":"bp_01HXYZ","is_active":true,"logo_url":"https://files.campaignbrain.dev/thefred/logo.png","smart_snippets":{"cta":"Book your island escape today.","welcome":"Welcome to The Fred — St. Croix's premier destination."},"target_audience":"Luxury-seeking travelers aged 30-55","updated_at":"2026-02-15T10:00:00","voice_personality":"knowledgeable local host","voice_tone":"warm, confident, island-casual"}},"cbpulse__BrandProfileUpsert":{"properties":{"voice_tone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice Tone","description":"Desired voice tone","examples":["warm, confident, island-casual"]},"voice_personality":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Voice Personality","description":"Brand personality archetype","examples":["knowledgeable local host"]},"brand_colors":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Brand Colors","description":"Hex color codes for brand palette","examples":[["#1A5276","#F4D03F"]]},"hashtags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Hashtags","description":"Preferred social media hashtags","examples":[["#TheFredVI","#StCroix"]]},"smart_snippets":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Smart Snippets","description":"Reusable text snippets keyed by purpose","examples":[{"welcome":"Welcome to The Fred."}]},"target_audience":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Audience","description":"Target audience description","examples":["Luxury-seeking travelers aged 30-55"]},"dos_and_donts":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Dos And Donts","description":"Brand voice dos and donts","examples":[{"donts":["Avoid jargon"],"dos":["Use inclusive language"]}]},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL to brand logo image","examples":["https://files.campaignbrain.dev/thefred/logo.png"]},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Whether the brand profile is active","examples":[true]}},"type":"object","title":"BrandProfileUpsert","example":{"brand_colors":["#1A5276","#F4D03F","#FFFFFF"],"hashtags":["#TheFredVI","#StCroix"],"target_audience":"Luxury-seeking travelers aged 30-55","voice_personality":"knowledgeable local host","voice_tone":"warm, confident, island-casual"}},"cbpulse__BrandRegistryResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Brand registry record identifier"},"slug":{"type":"string","title":"Slug","description":"cbkb-aligned brand slug","examples":["the_saint","lovango"]},"display_name":{"type":"string","title":"Display Name","description":"Human-readable brand name"},"active":{"type":"boolean","title":"Active","description":"Whether the brand is active","default":true},"platforms":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Platforms","description":"Target platforms for this brand","examples":[["instagram","facebook"]]},"quota_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quota Min","description":"Minimum posts-per-week quota"},"quota_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quota Max","description":"Maximum posts-per-week quota"},"vista_ig_profile_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Vista Ig Profile Id","description":"Vista Social Instagram profile id"},"vista_fb_profile_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Vista Fb Profile Id","description":"Vista Social Facebook profile id"},"vista_gbus_profile_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Vista Gbus Profile Id","description":"Vista Social Google Business profile id"},"vista_profile_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vista Profile Group Id","description":"Vista Social profile-group id"},"vista_workflow_gid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vista Workflow Gid","description":"Vista Social publishing workflow gid"},"cbpulse_business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbpulse Business Id","description":"Linked cbpulse businesses.id"},"drive_folder":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Drive Folder","description":"Asset Drive folder reference"},"brand_site":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand Site","description":"Public brand website"},"default_hashtags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Default Hashtags","description":"Default hashtags appended to posts"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["id","slug","display_name","created_at","updated_at"],"title":"BrandRegistryResponse","example":{"active":true,"cbpulse_business_id":"550e8400-e29b-41d4-a716-446655440002","created_at":"2026-06-17T10:00:00","display_name":"The Saint","id":"01HXYZ","platforms":["instagram","facebook"],"quota_max":7,"quota_min":5,"slug":"the_saint","updated_at":"2026-06-17T10:00:00","vista_fb_profile_id":496606,"vista_gbus_profile_id":651336,"vista_ig_profile_id":508467,"vista_profile_group_id":"52391d20-fb68-11ef-a14e-735f35eb9429","vista_workflow_gid":"69d52ed6954b577ec2e448f3"}},"cbpulse__BrandRegistryUpsert":{"properties":{"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name","description":"Human-readable name"},"active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Active","description":"Whether the brand is active"},"platforms":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Platforms","description":"Target platforms"},"quota_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quota Min","description":"Min posts-per-week"},"quota_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quota Max","description":"Max posts-per-week"},"vista_ig_profile_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Vista Ig Profile Id","description":"Vista IG id"},"vista_fb_profile_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Vista Fb Profile Id","description":"Vista FB id"},"vista_gbus_profile_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Vista Gbus Profile Id","description":"Vista GBus id"},"vista_profile_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vista Profile Group Id","description":"Vista profile-group id"},"vista_workflow_gid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vista Workflow Gid","description":"Vista workflow gid"},"cbpulse_business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbpulse Business Id","description":"Linked cbpulse businesses.id"},"drive_folder":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Drive Folder","description":"Drive folder reference"},"brand_site":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand Site","description":"Public brand website"},"default_hashtags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Default Hashtags","description":"Default hashtags"}},"type":"object","title":"BrandRegistryUpsert","example":{"active":true,"display_name":"The Saint","platforms":["instagram","facebook"],"quota_max":7,"quota_min":5,"vista_fb_profile_id":496606,"vista_ig_profile_id":508467,"vista_profile_group_id":"52391d20-fb68-11ef-a14e-735f35eb9429","vista_workflow_gid":"69d52ed6954b577ec2e448f3"}},"cbpulse__BudgetResponse":{"properties":{"id":{"type":"integer","title":"Id"},"businessId":{"type":"string","title":"Businessid"},"month":{"type":"string","title":"Month"},"amount":{"type":"number","title":"Amount"},"foodAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Foodamount"},"drinkAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Drinkamount"},"method":{"type":"string","title":"Method"},"costPct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Costpct"},"salesForecast":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Salesforecast"},"targetCogsPct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Targetcogspct"},"weeklyBudget":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Weeklybudget"}},"type":"object","required":["id","businessId","month","amount","foodAmount","drinkAmount","method","costPct"],"title":"BudgetResponse"},"cbpulse__BudgetSetBody":{"properties":{"amount":{"type":"number","exclusiveMinimum":0.0,"title":"Amount"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["amount"],"title":"BudgetSetBody","description":"Request body for setting a supply budget — amount and optional notes only.\n\nbusiness_id and year_month come from the path."},"cbpulse__BudgetUpsertRequest":{"properties":{"businessId":{"type":"string","title":"Businessid"},"month":{"type":"string","title":"Month"},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount"},"foodAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Foodamount"},"drinkAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Drinkamount"},"method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Method"},"costPct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Costpct"},"salesForecast":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Salesforecast"},"targetCogsPct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Targetcogspct"}},"type":"object","required":["businessId","month"],"title":"BudgetUpsertRequest"},"cbpulse__BusinessBrandStatus":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Business identifier"},"business_name":{"type":"string","title":"Business Name","description":"Business display name"},"has_brand_profile":{"type":"boolean","title":"Has Brand Profile","description":"Whether a brand profile exists"},"brand_active":{"type":"boolean","title":"Brand Active","description":"Whether the brand profile is active (false if missing)"}},"type":"object","required":["business_id","business_name","has_brand_profile","brand_active"],"title":"BusinessBrandStatus","description":"Per-business brand profile readiness.","example":{"brand_active":true,"business_id":"biz_thefred","business_name":"The Fred","has_brand_profile":true}},"cbpulse__BusinessHeadcount":{"properties":{"business_id":{"type":"string","title":"Business Id"},"business_name":{"type":"string","title":"Business Name"},"current_headcount":{"type":"integer","title":"Current Headcount"},"history":{"items":{"$ref":"#/components/schemas/cbpulse__HeadcountHistoryEntry"},"type":"array","title":"History"},"change_90d":{"type":"integer","title":"Change 90D"}},"type":"object","required":["business_id","business_name","current_headcount","history","change_90d"],"title":"BusinessHeadcount"},"cbpulse__CalendarEntry":{"properties":{"obligation_title":{"type":"string","title":"Obligation Title"},"obligation_id":{"type":"string","title":"Obligation Id"},"filing_id":{"type":"string","title":"Filing Id"},"due_date":{"type":"string","title":"Due Date"},"status":{"type":"string","title":"Status"},"days_until_due":{"type":"integer","title":"Days Until Due"},"urgency":{"type":"string","title":"Urgency"}},"type":"object","required":["obligation_title","obligation_id","filing_id","due_date","status","days_until_due","urgency"],"title":"CalendarEntry"},"cbpulse__CampaignChannelResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Channel content record ID"},"channel":{"type":"string","title":"Channel","description":"Delivery channel name","examples":["email","social_instagram","social_facebook","sms"]},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Content subject / headline"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Content body text"},"cta_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cta Text","description":"Call-to-action button text"},"cta_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cta Url","description":"Call-to-action URL"},"media_urls":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Media Urls","description":"Attached media URLs"},"is_approved":{"type":"boolean","title":"Is Approved","description":"Whether this channel content is approved"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["id","channel","is_approved","created_at","updated_at"],"title":"CampaignChannelResponse","description":"Channel-specific content for a campaign proposal.","example":{"body":"Discover St. Croix's premier boutique hotel...","channel":"email","created_at":"2026-02-17T10:00:00","cta_text":"Book Now","cta_url":"https://thefredvi.com/book","id":"ch_01HXYZ","is_approved":false,"media_urls":[],"subject":"Escape to The Fred — Your Island Retreat Awaits","updated_at":"2026-02-17T10:00:00"}},"cbpulse__CampaignListResponse":{"properties":{"proposals":{"items":{"$ref":"#/components/schemas/cbpulse__CampaignProposalResponse"},"type":"array","title":"Proposals","description":"List of campaign proposals"},"total":{"type":"integer","title":"Total","description":"Total matching proposals","examples":[42]}},"type":"object","required":["proposals","total"],"title":"CampaignListResponse"},"cbpulse__CampaignPipeline":{"properties":{"proposals_by_status":{"additionalProperties":{"type":"integer"},"type":"object","title":"Proposals By Status","description":"Campaign proposal counts grouped by lifecycle status"},"total_proposals":{"type":"integer","title":"Total Proposals","description":"Total number of campaign proposals"},"email_campaigns_sent":{"type":"integer","title":"Email Campaigns Sent","description":"Number of email campaigns with status 'sent'"},"email_total_recipients":{"type":"integer","title":"Email Total Recipients","description":"Total email recipients across all sent campaigns"},"email_avg_open_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Email Avg Open Rate","description":"Weighted average email open rate across sent campaigns (percentage)"},"email_avg_click_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Email Avg Click Rate","description":"Weighted average email click rate across sent campaigns (percentage)"},"sms_campaigns_sent":{"type":"integer","title":"Sms Campaigns Sent","description":"Number of SMS campaigns with status 'sent'"},"sms_total_recipients":{"type":"integer","title":"Sms Total Recipients","description":"Total SMS recipients across all sent campaigns"},"sms_total_delivered":{"type":"integer","title":"Sms Total Delivered","description":"Total SMS messages successfully delivered"}},"type":"object","required":["proposals_by_status","total_proposals","email_campaigns_sent","email_total_recipients","sms_campaigns_sent","sms_total_recipients","sms_total_delivered"],"title":"CampaignPipeline","description":"Campaign proposal pipeline status and email/SMS aggregate metrics.","example":{"email_avg_click_rate":4.8,"email_avg_open_rate":32.5,"email_campaigns_sent":12,"email_total_recipients":4500,"proposals_by_status":{"approved":2,"draft":3,"review":1},"sms_campaigns_sent":4,"sms_total_delivered":1150,"sms_total_recipients":1200,"total_proposals":6}},"cbpulse__CampaignProposalResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Campaign proposal ID"},"business_id":{"type":"string","title":"Business Id","description":"Business this campaign belongs to"},"signal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signal Id","description":"Marketing signal that triggered this proposal"},"title":{"type":"string","title":"Title","description":"Campaign title"},"objective":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Objective","description":"Campaign objective"},"status":{"type":"string","title":"Status","description":"Lifecycle status: draft, review, approved, rejected, scheduled, sent","examples":["draft","review","approved","rejected"]},"target_segment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Segment Id","description":"Target guest segment ID"},"scheduled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled At","description":"Scheduled send time"},"approved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved By","description":"User ID who approved"},"approved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Approved At","description":"Approval timestamp"},"rejected_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejected By","description":"User ID who rejected"},"rejected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Rejected At","description":"Rejection timestamp"},"rejected_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejected Reason","description":"Reason for rejection"},"metadata_json":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata Json","description":"Generation metadata (count, asset IDs, warnings)"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Creator user ID"},"channels":{"items":{"$ref":"#/components/schemas/cbpulse__CampaignChannelResponse"},"type":"array","title":"Channels","description":"Per-channel content"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["id","business_id","title","status","created_at","updated_at"],"title":"CampaignProposalResponse","description":"Full campaign proposal with nested channel content.","example":{"business_id":"biz_thefred","channels":[],"created_at":"2026-02-17T10:00:00","created_by":"usr_01HXYZ","id":"cp_01HXYZ","metadata_json":{"generation_count":1,"suggested_asset_ids":[]},"objective":"Drive midweek bookings with a flash sale","status":"draft","title":"Drive midweek bookings with a flash sale","updated_at":"2026-02-17T10:00:00"}},"cbpulse__CategoryBreakdown":{"properties":{"name":{"type":"string","title":"Name","description":"Category name"},"revenue":{"type":"number","title":"Revenue","description":"Total category revenue"},"pct_of_total":{"type":"number","title":"Pct Of Total","description":"Percentage of total revenue"},"prior_period_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prior Period Pct","description":"Category's share of prior period revenue"}},"type":"object","required":["name","revenue","pct_of_total"],"title":"CategoryBreakdown"},"cbpulse__CategoryCount":{"properties":{"category":{"type":"string","title":"Category","description":"Category name","examples":["amenities"]},"count":{"type":"integer","title":"Count","description":"Number of articles in this category","examples":[5]}},"type":"object","required":["category","count"],"title":"CategoryCount","description":"Category with article count."},"cbpulse__CategoryMappingCreate":{"properties":{"vip_category":{"type":"string","title":"Vip Category","description":"VIP category (food, bar, retail, labor, etc.)","examples":["food","bar","retail","labor"]},"qb_account_id":{"type":"string","title":"Qb Account Id","description":"QB account ID to map to","examples":["4200"]},"qb_account_name":{"type":"string","title":"Qb Account Name","description":"QB account display name","examples":["Food & Beverage Revenue"]},"mapping_type":{"type":"string","title":"Mapping Type","description":"Mapping type: revenue, expense, or cogs","examples":["revenue","expense","cogs"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","examples":["Maps Clover food sales to QB income account"]}},"type":"object","required":["vip_category","qb_account_id","qb_account_name","mapping_type"],"title":"CategoryMappingCreate","example":{"mapping_type":"revenue","notes":"Maps Clover food sales to QB income account","qb_account_id":"4200","qb_account_name":"Food & Beverage Revenue","vip_category":"food"}},"cbpulse__CategoryMappingResponse":{"properties":{"id":{"type":"string","title":"Id","examples":["map_01HXYZ"]},"business_id":{"type":"string","title":"Business Id","examples":["biz_thefred"]},"vip_category":{"type":"string","title":"Vip Category","examples":["food"]},"qb_account_id":{"type":"string","title":"Qb Account Id","examples":["4200"]},"qb_account_name":{"type":"string","title":"Qb Account Name","examples":["Food & Beverage Revenue"]},"mapping_type":{"type":"string","title":"Mapping Type","examples":["revenue"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","examples":["Maps Clover food sales to QB income account"]}},"type":"object","required":["id","business_id","vip_category","qb_account_id","qb_account_name","mapping_type"],"title":"CategoryMappingResponse","example":{"business_id":"biz_thefred","id":"map_01HXYZ","mapping_type":"revenue","notes":"Maps Clover food sales to QB income account","qb_account_id":"4200","qb_account_name":"Food & Beverage Revenue","vip_category":"food"}},"cbpulse__CategoryMappingUpdate":{"properties":{"qb_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Qb Account Id","examples":["5100"]},"qb_account_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Qb Account Name","examples":["Cost of Goods Sold"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","examples":["Updated to match new QB chart of accounts"]}},"type":"object","title":"CategoryMappingUpdate","example":{"notes":"Updated to match new QB chart of accounts","qb_account_id":"5100","qb_account_name":"Cost of Goods Sold"}},"cbpulse__ChannelMessageCreate":{"properties":{"body":{"type":"string","maxLength":4000,"minLength":1,"title":"Body"}},"type":"object","required":["body"],"title":"ChannelMessageCreate"},"cbpulse__ChannelMessageOut":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"channel":{"type":"string","title":"Channel"},"body":{"type":"string","title":"Body"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","business_id","channel","body","created_by","created_by_name","created_at"],"title":"ChannelMessageOut"},"cbpulse__ChannelOut":{"properties":{"channel":{"type":"string","title":"Channel"},"label":{"type":"string","title":"Label"},"last_body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Body"},"last_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last By Name"},"last_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last At"},"count_today":{"type":"integer","title":"Count Today","default":0}},"type":"object","required":["channel","label"],"title":"ChannelOut"},"cbpulse__ChecklistCreate":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name"},"kind":{"type":"string","title":"Kind"},"dept":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"},"room_type_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Type Name"},"items":{"items":{"type":"string"},"type":"array","title":"Items"}},"type":"object","required":["name","kind"],"title":"ChecklistCreate"},"cbpulse__ChecklistItemOut":{"properties":{"id":{"type":"string","title":"Id"},"label":{"type":"string","title":"Label"},"position":{"type":"integer","title":"Position"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","label","position","is_active"],"title":"ChecklistItemOut"},"cbpulse__ChecklistItemsPut":{"properties":{"items":{"items":{"type":"string"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"ChecklistItemsPut","description":"The full ordered list. Labels that already exist keep their id; new\nlabels get one; missing labels are deactivated (never deleted — a run\nitem points at them)."},"cbpulse__ChecklistOut":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"name":{"type":"string","title":"Name"},"kind":{"type":"string","title":"Kind"},"dept":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"},"room_type_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Type Name"},"is_active":{"type":"boolean","title":"Is Active"},"position":{"type":"integer","title":"Position"},"items":{"items":{"$ref":"#/components/schemas/cbpulse__ChecklistItemOut"},"type":"array","title":"Items","default":[]}},"type":"object","required":["id","business_id","name","kind","dept","room_type_name","is_active","position"],"title":"ChecklistOut"},"cbpulse__ChecklistPatch":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"dept":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"},"room_type_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Type Name"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Position"}},"type":"object","title":"ChecklistPatch","description":"What management may change about a template after it exists. ``kind``\nis deliberately absent: a run is keyed by it (per room per day, per day,\nper week), so changing it would orphan every run the template has."},"cbpulse__CollisionGroupResponse":{"properties":{"business_id":{"type":"string","title":"Business Id"},"business_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Slug"},"business_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Name"},"normalized_name":{"type":"string","title":"Normalized Name"},"rows":{"items":{"$ref":"#/components/schemas/cbpulse__EmployeeRowResponse"},"type":"array","title":"Rows"},"recommended_canonical_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommended Canonical Id","description":"Heuristic recommendation (most writeups + has email). NULL on tie — reviewer must pick."}},"type":"object","required":["business_id","business_slug","business_name","normalized_name","rows"],"title":"CollisionGroupResponse"},"cbpulse__CommentCreate":{"properties":{"body":{"type":"string","maxLength":4000,"minLength":1,"title":"Body"}},"type":"object","required":["body"],"title":"CommentCreate"},"cbpulse__CommissionConfigCreate":{"properties":{"source_business_id":{"type":"string","title":"Source Business Id"},"dest_business_id":{"type":"string","title":"Dest Business Id"},"commission_pct":{"type":"number","maximum":100.0,"exclusiveMinimum":0.0,"title":"Commission Pct"},"room_types":{"items":{"type":"string"},"type":"array","title":"Room Types"},"is_active":{"type":"boolean","title":"Is Active","default":true},"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}},"type":"object","required":["source_business_id","dest_business_id","commission_pct","room_types"],"title":"CommissionConfigCreate"},"cbpulse__CommissionConfigResponse":{"properties":{"id":{"type":"string","title":"Id"},"source_business_id":{"type":"string","title":"Source Business Id"},"dest_business_id":{"type":"string","title":"Dest Business Id"},"commission_pct":{"type":"number","title":"Commission Pct"},"room_types":{"items":{"type":"string"},"type":"array","title":"Room Types"},"is_active":{"type":"boolean","title":"Is Active"},"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}},"type":"object","required":["id","source_business_id","dest_business_id","commission_pct","room_types","is_active","start_date","end_date"],"title":"CommissionConfigResponse"},"cbpulse__CommissionConfigUpdate":{"properties":{"commission_pct":{"anyOf":[{"type":"number","maximum":100.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Commission Pct"},"room_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Room Types"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}},"type":"object","title":"CommissionConfigUpdate"},"cbpulse__CommissionSplitResponse":{"properties":{"id":{"type":"string","title":"Id"},"config_id":{"type":"string","title":"Config Id"},"source_business_id":{"type":"string","title":"Source Business Id"},"dest_business_id":{"type":"string","title":"Dest Business Id"},"report_date":{"type":"string","title":"Report Date"},"qualifying_revenue":{"type":"number","title":"Qualifying Revenue"},"commission_pct":{"type":"number","title":"Commission Pct"},"commission_amount":{"type":"number","title":"Commission Amount"},"reservation_count":{"type":"integer","title":"Reservation Count"},"reservation_ids":{"items":{"type":"string"},"type":"array","title":"Reservation Ids"}},"type":"object","required":["id","config_id","source_business_id","dest_business_id","report_date","qualifying_revenue","commission_pct","commission_amount","reservation_count","reservation_ids"],"title":"CommissionSplitResponse"},"cbpulse__ComplianceAlternative":{"properties":{"action":{"type":"string","title":"Action"},"pros":{"items":{"type":"string"},"type":"array","title":"Pros"},"cons":{"items":{"type":"string"},"type":"array","title":"Cons"}},"type":"object","required":["action","pros","cons"],"title":"ComplianceAlternative"},"cbpulse__ComplianceAnalyzeRequest":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"business_id":{"type":"string","title":"Business Id"},"violation_type":{"type":"string","title":"Violation Type","description":"Category key — one of the 9 live categories: attendance, performance, conduct, safety, theft, drugs, violence, harassment, policy. All map to a VI §76 ground; severity is always progressive."},"incident_description":{"type":"string","maxLength":5000,"minLength":1,"title":"Incident Description"},"incident_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Incident Date","description":"ISO date the incident occurred. Defaults to today."},"create_draft":{"type":"boolean","title":"Create Draft","default":false}},"type":"object","required":["employee_id","business_id","violation_type","incident_description"],"title":"ComplianceAnalyzeRequest"},"cbpulse__ComplianceAnalyzeResponse":{"properties":{"handbook_citation":{"$ref":"#/components/schemas/cbpulse__HandbookCitation"},"vi_statute_ground":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vi Statute Ground"},"vi_statute_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vi Statute Label"},"prior_writeups":{"type":"integer","title":"Prior Writeups"},"matrix_position":{"type":"string","title":"Matrix Position"},"recommended_severity":{"type":"string","title":"Recommended Severity"},"defensibility":{"type":"string","title":"Defensibility"},"defensibility_reasoning":{"type":"string","title":"Defensibility Reasoning"},"alternatives":{"items":{"$ref":"#/components/schemas/cbpulse__ComplianceAlternative"},"type":"array","title":"Alternatives"},"documentation_gaps":{"items":{"type":"string"},"type":"array","title":"Documentation Gaps"},"draft_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Draft Id"}},"type":"object","required":["handbook_citation","vi_statute_ground","vi_statute_label","prior_writeups","matrix_position","recommended_severity","defensibility","defensibility_reasoning","alternatives","documentation_gaps"],"title":"ComplianceAnalyzeResponse"},"cbpulse__ComplianceDocResponse":{"properties":{"id":{"type":"string","title":"Id"},"employee_id":{"type":"string","title":"Employee Id"},"business_id":{"type":"string","title":"Business Id"},"document_type":{"type":"string","title":"Document Type","description":"work_permit, training_cert, handbook_ack, food_handler"},"status":{"type":"string","title":"Status","description":"pending, valid, expiring, expired, missing"},"issued_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issued Date"},"expiry_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiry Date"},"document_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Url","description":"Link to uploaded document"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","employee_id","business_id","document_type","status","created_at","updated_at"],"title":"ComplianceDocResponse","description":"Employee compliance document (work permit, training cert, etc.)."},"cbpulse__ComplianceDocumentURLResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"source":{"type":"string","title":"Source"}},"type":"object","required":["url","source"],"title":"ComplianceDocumentURLResponse","description":"Fresh PDF URL for a compliance document. cbpulse#493.\n\nsource: ``archive`` (cbesig presigned) | ``live`` (cbpulse stream proxy) |\n``legacy`` (raw stored cbfiles path, for non-cbesig document_urls)."},"cbpulse__ComplianceItemCreate":{"properties":{"subject_type":{"type":"string","title":"Subject Type"},"subject_id":{"type":"string","title":"Subject Id"},"type":{"type":"string","title":"Type"},"issue_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issue Date"},"expiry_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiry Date"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"document_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Url"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"},"alert_recipients":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Alert Recipients"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"period_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Period Year"},"filed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Filed"},"filed_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed Date"},"filed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed By"}},"type":"object","required":["subject_type","subject_id","type"],"title":"ComplianceItemCreate"},"cbpulse__ComplianceItemResponse":{"properties":{"id":{"type":"string","title":"Id"},"subject_type":{"type":"string","title":"Subject Type"},"subject_id":{"type":"string","title":"Subject Id"},"type":{"type":"string","title":"Type"},"issue_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issue Date"},"expiry_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiry Date"},"status":{"type":"string","title":"Status"},"stored_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stored Status"},"days_until_expiry":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Until Expiry"},"document_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Url"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"},"alert_recipients":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Alert Recipients"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"period_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Period Year"},"filed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Filed"},"filed_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed Date"},"filed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed By"},"program":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Program"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"cadence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cadence"},"due_rule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Rule"},"edc_due_rule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edc Due Rule"},"agency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agency"},"proof_of_compliance":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proof Of Compliance"},"statutory_basis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Statutory Basis"},"amount_usd":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount Usd"},"periods":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Periods"}},"type":"object","required":["id","subject_type","subject_id","type","issue_date","expiry_date","status","stored_status","days_until_expiry","document_url","owner","alert_recipients","notes","period_year","filed","filed_date","filed_by","program","category","cadence","due_rule","edc_due_rule","agency","proof_of_compliance","statutory_basis","amount_usd","periods"],"title":"ComplianceItemResponse"},"cbpulse__ComplianceItemUpdate":{"properties":{"issue_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issue Date"},"expiry_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiry Date"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"document_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Url"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"},"alert_recipients":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Alert Recipients"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"period_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Period Year"},"filed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Filed"},"filed_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed Date"},"filed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed By"}},"type":"object","title":"ComplianceItemUpdate"},"cbpulse__ComplianceUpdateRequest":{"properties":{"document_type":{"type":"string","title":"Document Type","description":"Type: work_permit, training_cert, handbook_ack, food_handler"},"status":{"type":"string","title":"Status","description":"Status: pending, valid, expiring, expired, missing"},"issued_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issued Date"},"expiry_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiry Date"},"document_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Url"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["document_type","status"],"title":"ComplianceUpdateRequest"},"cbpulse__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbpulse__ConditionStatus":{"properties":{"condition_number":{"type":"integer","title":"Condition Number"},"title":{"type":"string","title":"Title"},"status":{"type":"string","title":"Status"},"proof_document_ids":{"items":{},"type":"array","title":"Proof Document Ids"},"has_proof":{"type":"boolean","title":"Has Proof"}},"type":"object","required":["condition_number","title","status","proof_document_ids","has_proof"],"title":"ConditionStatus"},"cbpulse__ContactPatchRequest":{"properties":{"email":{"anyOf":[{"type":"string","maxLength":200,"minLength":3},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string","maxLength":30,"minLength":3},{"type":"null"}],"title":"Phone"}},"type":"object","title":"ContactPatchRequest","description":"Manager-scoped contact-info edit (cbpulse#489).\n\nBoth fields optional; at least one must be present. Empty string is\nrejected — to clear a value use a real correction, not a blank."},"cbpulse__ConversationDetailResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique conversation identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this conversation belongs to"},"channel":{"type":"string","title":"Channel","description":"Channel: meta_dm, email, chat, sms, whatsapp"},"status":{"type":"string","title":"Status","description":"Status: open, snoozed, closed"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Conversation subject line"},"guest_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Name","description":"Guest display name"},"guest_identifier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Identifier","description":"Guest email/phone/handle"},"guest_profile_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Profile Id","description":"Linked guest profile ID"},"assigned_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To","description":"Assigned staff user ID"},"assigned_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned At","description":"ISO-8601 assignment timestamp"},"snoozed_until":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snoozed Until","description":"ISO-8601 snooze expiry"},"last_message_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message At","description":"ISO-8601 last message timestamp"},"message_count":{"type":"integer","title":"Message Count","description":"Total messages in conversation"},"unread_count":{"type":"integer","title":"Unread Count","description":"Unread message count"},"ai_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Summary","description":"AI-generated conversation summary"},"metadata_json":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata Json","description":"Arbitrary metadata"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the conversation was created"},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last update"},"messages":{"items":{"$ref":"#/components/schemas/cbpulse__MessageResponse"},"type":"array","title":"Messages","description":"Messages in chronological order"}},"type":"object","required":["id","business_id","channel","status","message_count","unread_count","created_at","updated_at"],"title":"ConversationDetailResponse","description":"Conversation with eagerly loaded messages."},"cbpulse__ConversationResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique conversation identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this conversation belongs to"},"channel":{"type":"string","title":"Channel","description":"Channel: meta_dm, email, chat, sms, whatsapp"},"status":{"type":"string","title":"Status","description":"Status: open, snoozed, closed"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Conversation subject line"},"guest_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Name","description":"Guest display name"},"guest_identifier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Identifier","description":"Guest email/phone/handle"},"guest_profile_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Profile Id","description":"Linked guest profile ID"},"assigned_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To","description":"Assigned staff user ID"},"assigned_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned At","description":"ISO-8601 assignment timestamp"},"snoozed_until":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snoozed Until","description":"ISO-8601 snooze expiry"},"last_message_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message At","description":"ISO-8601 last message timestamp"},"message_count":{"type":"integer","title":"Message Count","description":"Total messages in conversation"},"unread_count":{"type":"integer","title":"Unread Count","description":"Unread message count"},"ai_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Summary","description":"AI-generated conversation summary"},"metadata_json":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata Json","description":"Arbitrary metadata"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the conversation was created"},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last update"}},"type":"object","required":["id","business_id","channel","status","message_count","unread_count","created_at","updated_at"],"title":"ConversationResponse","description":"Inbox conversation thread summary (no messages)."},"cbpulse__CountdownResponse":{"properties":{"next_regular_payroll":{"type":"string","title":"Next Regular Payroll","description":"YYYY-MM-DD next regular pay date"},"days_until_regular":{"type":"integer","title":"Days Until Regular"},"next_contractor_pay":{"type":"string","title":"Next Contractor Pay","description":"YYYY-MM-DD next contractor pay date"},"days_until_contractor":{"type":"integer","title":"Days Until Contractor"},"urgency":{"type":"string","title":"Urgency","description":"green (>7d), amber (3-7d), red (<3d)"}},"type":"object","required":["next_regular_payroll","days_until_regular","next_contractor_pay","days_until_contractor","urgency"],"title":"CountdownResponse","description":"Payroll deadline countdown widget."},"cbpulse__CreateArticleRequest":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Business/property to create the article for","examples":["biz_thefred"]},"title":{"type":"string","title":"Title","description":"Article title","examples":["Check-in Procedures"]},"body":{"type":"string","title":"Body","description":"Article body content","examples":["1. Greet the guest warmly..."]},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Article category","examples":["procedures"]},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"List of tags","examples":[["check-in","front-desk"]]},"is_published":{"type":"boolean","title":"Is Published","description":"Whether the article should be published immediately","default":false,"examples":[false]}},"type":"object","required":["business_id","title","body"],"title":"CreateArticleRequest","description":"Payload for creating a new knowledge base article.","example":{"body":"1. Greet the guest warmly...","business_id":"biz_thefred","category":"procedures","is_published":false,"tags":["check-in","front-desk"],"title":"Check-in Procedures"}},"cbpulse__CreateIncidentRequest":{"properties":{"business_id":{"type":"string","title":"Business Id"},"employee_id":{"type":"string","title":"Employee Id"},"incident_date":{"type":"string","title":"Incident Date"},"description":{"type":"string","title":"Description"},"severity":{"type":"string","title":"Severity","description":"minor, moderate, major, critical","default":"minor"},"wda_ground":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wda Ground"}},"type":"object","required":["business_id","employee_id","incident_date","description"],"title":"CreateIncidentRequest"},"cbpulse__CreateInvoiceRequest":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Business to create the invoice for","examples":["biz_thesaint"]},"vendor_name":{"type":"string","title":"Vendor Name","description":"Vendor or supplier name","examples":["Caribbean Produce Co."]},"invoice_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Number","description":"Vendor's invoice number for reference","examples":["CPC-2026-1234"]},"invoice_date":{"type":"string","title":"Invoice Date","description":"Date on the invoice (YYYY-MM-DD)","examples":["2026-02-10"]},"due_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Date","description":"Payment due date (YYYY-MM-DD)","examples":["2026-03-10"]},"amount":{"type":"number","title":"Amount","description":"Invoice total amount","examples":[1875.5]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-text notes about the invoice","examples":["Fresh produce delivery for restaurant week"]},"order_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order Id","description":"Linked purchase order ID, if this invoice matches an order","examples":["ord_e5f6g7h8"]}},"type":"object","required":["business_id","vendor_name","invoice_date","amount"],"title":"CreateInvoiceRequest","description":"Payload for creating a new invoice manually.","example":{"amount":1875.5,"business_id":"biz_thesaint","due_date":"2026-03-10","invoice_date":"2026-02-10","invoice_number":"CPC-2026-1234","notes":"Fresh produce delivery for restaurant week","order_id":"ord_e5f6g7h8","vendor_name":"Caribbean Produce Co."}},"cbpulse__CreateManagerLogRequest":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Business to create the log entry for","examples":["biz_thefred","biz_thesaint","biz_lovango"]},"entry_type":{"type":"string","title":"Entry Type","description":"Log type: shift_note, incident, observation, or handoff","examples":["shift_note","incident","observation","handoff"]},"title":{"type":"string","title":"Title","description":"Short summary of the log entry","examples":["Friday dinner rush — short-staffed","Guest complaint: room 204 AC failure"]},"body":{"type":"string","title":"Body","description":"Full narrative content of the log entry","examples":["Only 3 servers for 45 covers. Called in backup from lunch team. Service recovered by 8pm.","Guest in room 204 reported AC not cooling. Maintenance replaced compressor relay. Room back to normal by 3pm."]},"tags":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Tags","description":"Categorization tags, e.g. ['staffing', 'guest-complaint']"},"shift_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shift Date","description":"Date of the shift this entry covers (YYYY-MM-DD)","examples":["2026-02-14","2026-02-15"]},"is_flagged":{"type":"boolean","title":"Is Flagged","description":"Flag this entry for follow-up or escalation","default":false}},"type":"object","required":["business_id","entry_type","title","body"],"title":"CreateManagerLogRequest","description":"Payload for creating a new manager log entry.","example":{"body":"Only 3 servers for 45 covers. Called in backup from lunch team. Service recovered by 8pm.","business_id":"biz_thefred","entry_type":"shift_note","is_flagged":true,"shift_date":"2026-02-14","tags":["staffing","service-delay"],"title":"Friday dinner rush — short-staffed"}},"cbpulse__CreateSegmentRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Segment name (unique per business)"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Human-readable description"},"criteria":{"additionalProperties":true,"type":"object","title":"Criteria","description":"Filter criteria for segment evaluation"},"business_id":{"type":"string","title":"Business Id","description":"Business ID to scope this segment to"}},"type":"object","required":["name","criteria","business_id"],"title":"CreateSegmentRequest","description":"Payload to create a new guest segment.","example":{"business_id":"biz_thefred","criteria":{"min_stay_count":2,"spend_tier":"gold"},"description":"Guests with gold+ tier who stayed more than once","name":"High-Value Returners"}},"cbpulse__CreateTicketRequest":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Business/property to create the ticket for","examples":["biz_thesaint"]},"title":{"type":"string","title":"Title","description":"Short summary of the maintenance issue","examples":["AC not cooling in Lobby"]},"description":{"type":"string","title":"Description","description":"Detailed description of the issue and its impact","examples":["Lobby thermostat reads 82F despite being set to 74F. Guests have complained."]},"priority":{"type":"string","title":"Priority","description":"Ticket priority: low, medium, high, or urgent","default":"medium","examples":["urgent"]},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Issue category, e.g. 'plumbing', 'electrical', 'hvac', 'general'","examples":["hvac"]},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location","description":"Specific location within the property, e.g. 'Room 204', 'Pool deck'","examples":["Lobby"]},"photos":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Photos","description":"List of photo URLs documenting the issue","examples":[["https://files.campaignbrain.dev/tickets/upload/lobby_thermostat.jpg"]]}},"type":"object","required":["business_id","title","description"],"title":"CreateTicketRequest","description":"Payload for creating a new maintenance ticket.","example":{"business_id":"biz_thesaint","category":"hvac","description":"Lobby thermostat reads 82F despite being set to 74F. Guests have complained. Unit making clicking noise.","location":"Lobby","photos":["https://files.campaignbrain.dev/tickets/upload/lobby_thermostat.jpg"],"priority":"urgent","title":"AC not cooling in Lobby"}},"cbpulse__CreateWriteUpRequest":{"properties":{"business_id":{"type":"string","title":"Business Id"},"employee_id":{"type":"string","title":"Employee Id"},"incident_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incident Id"},"violation_category":{"type":"string","title":"Violation Category"},"wda_ground":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wda Ground"},"description":{"type":"string","title":"Description"},"corrective_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Corrective Action"},"consequences":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consequences"},"discipline_level":{"type":"string","title":"Discipline Level","description":"verbal_warning, first_written, final_written, suspension, termination_rec"}},"type":"object","required":["business_id","employee_id","violation_category","description","discipline_level"],"title":"CreateWriteUpRequest"},"cbpulse__CrossBusinessPattern":{"properties":{"metric":{"type":"string","title":"Metric","description":"KPI exhibiting a cross-business pattern"},"direction":{"type":"string","title":"Direction","description":"Common deviation direction: up or down"},"businesses":{"items":{"type":"string"},"type":"array","title":"Businesses","description":"Businesses sharing this anomaly pattern"},"hypothesis":{"type":"string","title":"Hypothesis","description":"Suggested explanation for the shared pattern"}},"type":"object","required":["metric","direction","businesses","hypothesis"],"title":"CrossBusinessPattern","example":{"businesses":["The Fred","The Saint","Lovango"],"direction":"up","hypothesis":"Island-wide staffing surge due to peak tourism season","metric":"labor_percent"}},"cbpulse__DataSourceSpec":{"properties":{"key":{"type":"string","title":"Key","description":"Logical name of the data source (e.g. 'occupancy')"},"type":{"type":"string","title":"Type","description":"Mapper type that knows how to fetch the data"},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Mapper-specific parameters"}},"type":"object","required":["key","type"],"title":"DataSourceSpec","description":"Identifies one data source to feed into a report template."},"cbpulse__DeadlineCreate":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Business to create the deadline for"},"title":{"type":"string","title":"Title","description":"Short name of the financial deadline"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional details about the obligation"},"category":{"type":"string","title":"Category","description":"Deadline category: tax, insurance, license, etc."},"recurrence_type":{"type":"string","title":"Recurrence Type","description":"Schedule: one_time, monthly, quarterly, etc."},"next_due_date":{"type":"string","title":"Next Due Date","description":"Initial due date (YYYY-MM-DD)"},"estimated_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Estimated Amount","description":"Estimated payment amount in USD"},"reminder_days":{"type":"integer","title":"Reminder Days","description":"Days before due date to trigger reminder","default":7}},"type":"object","required":["business_id","title","category","recurrence_type","next_due_date"],"title":"DeadlineCreate","example":{"business_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","category":"utility","description":"Monthly electricity bill for The Fred property","estimated_amount":4200.0,"next_due_date":"2026-03-01","recurrence_type":"monthly","reminder_days":7,"title":"WAPA Electricity Payment"}},"cbpulse__DeadlineItem":{"properties":{"id":{"type":"string","title":"Id","description":"Unique deadline record identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this deadline applies to"},"title":{"type":"string","title":"Title","description":"Short name of the financial deadline"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional details about the obligation"},"category":{"type":"string","title":"Category","description":"Deadline category, e.g. tax, insurance, license"},"recurrence_type":{"type":"string","title":"Recurrence Type","description":"Recurrence schedule, e.g. monthly or annually"},"next_due_date":{"type":"string","title":"Next Due Date","description":"Next due date (YYYY-MM-DD)"},"estimated_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Estimated Amount","description":"Estimated payment amount in USD"},"reminder_days":{"type":"integer","title":"Reminder Days","description":"Days before due date to trigger reminder"},"days_until_due":{"type":"integer","title":"Days Until Due","description":"Days remaining until next due date"}},"type":"object","required":["id","business_id","title","description","category","recurrence_type","next_due_date","estimated_amount","reminder_days","days_until_due"],"title":"DeadlineItem","example":{"business_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","category":"tax","days_until_due":28,"description":"Monthly 5% gross receipts tax filing with BIR","estimated_amount":9250.0,"id":"dl-20260301-001","next_due_date":"2026-03-15","recurrence_type":"monthly","reminder_days":7,"title":"USVI Gross Receipts Tax"}},"cbpulse__DeadlinesResponse":{"properties":{"overdue":{"items":{"$ref":"#/components/schemas/cbpulse__DeadlineItem"},"type":"array","title":"Overdue","description":"Deadlines past their due date"},"this_week":{"items":{"$ref":"#/components/schemas/cbpulse__DeadlineItem"},"type":"array","title":"This Week","description":"Deadlines due within this week"},"this_month":{"items":{"$ref":"#/components/schemas/cbpulse__DeadlineItem"},"type":"array","title":"This Month","description":"Deadlines due within this month"},"total":{"type":"integer","title":"Total","description":"Total number of upcoming deadlines"}},"type":"object","required":["overdue","this_week","this_month","total"],"title":"DeadlinesResponse","example":{"overdue":[],"this_month":[],"this_week":[],"total":5}},"cbpulse__DedupKeyCoverage":{"properties":{"status":{"type":"string","title":"Status"},"total":{"type":"integer","title":"Total"},"content_hash":{"type":"integer","title":"Content Hash","description":"rows carrying a content_hash"},"message_id":{"type":"integer","title":"Message Id","description":"rows carrying a message_id"},"order_number":{"type":"integer","title":"Order Number","description":"rows carrying an order_number (#568)"},"invoice_number":{"type":"integer","title":"Invoice Number","description":"rows carrying an invoice_number"},"no_usable_key":{"type":"integer","title":"No Usable Key","description":"rows with NONE of the four — dedup cannot fire at all"},"no_usable_key_amount":{"type":"number","title":"No Usable Key Amount"},"residual_duplicate_groups":{"type":"integer","title":"Residual Duplicate Groups","description":"groups still sharing (business_id, invoice_number, amount)"},"residual_extra_copies":{"type":"integer","title":"Residual Extra Copies"},"residual_excess_amount":{"type":"number","title":"Residual Excess Amount","description":"what approving every extra copy would over-book"}},"type":"object","required":["status","total","content_hash","message_id","order_number","invoice_number","no_usable_key","no_usable_key_amount","residual_duplicate_groups","residual_extra_copies","residual_excess_amount"],"title":"DedupKeyCoverage","description":"How many invoices each dedup key could actually discriminate."},"cbpulse__DisciplineStepResponse":{"properties":{"id":{"type":"string","title":"Id"},"employee_id":{"type":"string","title":"Employee Id"},"business_id":{"type":"string","title":"Business Id"},"writeup_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Writeup Id"},"step_level":{"type":"string","title":"Step Level","description":"verbal_warning, first_written, final_written, suspension, termination_rec"},"wda_ground":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wda Ground","description":"VI WDA §76 ground code (e.g. 76a1-76a9)"},"description":{"type":"string","title":"Description"},"issued_by":{"type":"string","title":"Issued By","description":"User ID who issued this step"},"issued_date":{"type":"string","title":"Issued Date"},"is_active":{"type":"boolean","title":"Is Active","description":"False if overridden or expired"},"override_justification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Override Justification"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","employee_id","business_id","step_level","description","issued_by","issued_date","is_active","created_at","updated_at"],"title":"DisciplineStepResponse","description":"Progressive discipline ladder step."},"cbpulse__DismissSignalRequest":{"properties":{"reason":{"type":"string","title":"Reason","description":"Reason for dismissing the signal","default":"","examples":["Expected seasonal dip, no action needed"]}},"type":"object","title":"DismissSignalRequest","description":"Payload to dismiss a signal with optional reason."},"cbpulse__DocKeyBackfillReport":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run"},"scanned":{"type":"integer","title":"Scanned"},"already_full_key":{"type":"integer","title":"Already Full Key"},"normalized":{"type":"integer","title":"Normalized"},"repaired":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Repaired"},"orphans":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Orphans"},"ambiguous":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Ambiguous"}},"type":"object","required":["dry_run","scanned","already_full_key","normalized","repaired","orphans","ambiguous"],"title":"DocKeyBackfillReport","description":"Outcome of the #496 doc-key backfill."},"cbpulse__DocumentCreate":{"properties":{"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"vip_business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vip Business Id"},"title":{"type":"string","title":"Title"},"document_type_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Type Code"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"source_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Path"},"s3_key":{"type":"string","title":"S3 Key"},"s3_url":{"type":"string","title":"S3 Url"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Type"},"file_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size Bytes"},"effective_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiration Date"},"extracted_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extracted Text"},"needs_review":{"type":"boolean","title":"Needs Review","default":false},"edc_condition_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Edc Condition Number"},"edc_period":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edc Period"}},"type":"object","required":["title","s3_key","s3_url"],"title":"DocumentCreate"},"cbpulse__DocumentDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"vip_business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vip Business Id"},"title":{"type":"string","title":"Title"},"document_type_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Type Code"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"source_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Path"},"s3_key":{"type":"string","title":"S3 Key"},"s3_url":{"type":"string","title":"S3 Url"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Type"},"file_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size Bytes"},"effective_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiration Date"},"ai_classification":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ai Classification"},"ai_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ai Confidence"},"needs_review":{"type":"boolean","title":"Needs Review"},"edc_condition_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Edc Condition Number"},"edc_period":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edc Period"},"uploaded_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uploaded By"},"lifecycle_status":{"type":"string","title":"Lifecycle Status"},"urgency_tier":{"type":"string","title":"Urgency Tier"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"extracted_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extracted Text"}},"type":"object","required":["id","entity_id","vip_business_id","title","document_type_code","source","source_path","s3_key","s3_url","file_type","file_size_bytes","effective_date","expiration_date","ai_classification","ai_confidence","needs_review","edc_condition_number","edc_period","uploaded_by","lifecycle_status","urgency_tier","created_at","updated_at","extracted_text"],"title":"DocumentDetailResponse"},"cbpulse__DocumentExpiringResponse":{"properties":{"overdue":{"items":{"$ref":"#/components/schemas/cbpulse__ExpiringDocument"},"type":"array","title":"Overdue"},"critical":{"items":{"$ref":"#/components/schemas/cbpulse__ExpiringDocument"},"type":"array","title":"Critical"},"high":{"items":{"$ref":"#/components/schemas/cbpulse__ExpiringDocument"},"type":"array","title":"High"},"medium":{"items":{"$ref":"#/components/schemas/cbpulse__ExpiringDocument"},"type":"array","title":"Medium"},"low":{"items":{"$ref":"#/components/schemas/cbpulse__ExpiringDocument"},"type":"array","title":"Low"}},"type":"object","required":["overdue","critical","high","medium","low"],"title":"DocumentExpiringResponse"},"cbpulse__DocumentResponse":{"properties":{"id":{"type":"string","title":"Id"},"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"vip_business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vip Business Id"},"title":{"type":"string","title":"Title"},"document_type_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Type Code"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"source_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Path"},"s3_key":{"type":"string","title":"S3 Key"},"s3_url":{"type":"string","title":"S3 Url"},"file_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Type"},"file_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size Bytes"},"effective_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiration Date"},"ai_classification":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ai Classification"},"ai_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ai Confidence"},"needs_review":{"type":"boolean","title":"Needs Review"},"edc_condition_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Edc Condition Number"},"edc_period":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edc Period"},"uploaded_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uploaded By"},"lifecycle_status":{"type":"string","title":"Lifecycle Status"},"urgency_tier":{"type":"string","title":"Urgency Tier"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","entity_id","vip_business_id","title","document_type_code","source","source_path","s3_key","s3_url","file_type","file_size_bytes","effective_date","expiration_date","ai_classification","ai_confidence","needs_review","edc_condition_number","edc_period","uploaded_by","lifecycle_status","urgency_tier","created_at","updated_at"],"title":"DocumentResponse"},"cbpulse__DocumentUpdate":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"document_type_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Type Code"},"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"effective_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiration Date"},"lifecycle_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lifecycle Status"},"edc_condition_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Edc Condition Number"},"edc_period":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edc Period"},"needs_review":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Needs Review"}},"type":"object","title":"DocumentUpdate"},"cbpulse__DraftResponse":{"properties":{"id":{"type":"string","title":"Id"},"brand_slug":{"type":"string","title":"Brand Slug"},"content_type":{"type":"string","title":"Content Type"},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id"},"focus_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Focus Text"},"copy_ig":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Copy Ig"},"copy_fb":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Copy Fb"},"photo_candidates":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Photo Candidates"},"selected_photo_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Selected Photo Path"},"violations":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Violations"},"suggested_publish_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suggested Publish At"},"status":{"type":"string","title":"Status"},"generation_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generation Error"},"vista_post_ids":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Vista Post Ids"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["id","brand_slug","content_type","event_id","focus_text","copy_ig","copy_fb","photo_candidates","selected_photo_path","violations","suggested_publish_at","status","generation_error","vista_post_ids","created_by","created_at"],"title":"DraftResponse"},"cbpulse__EdcApproveRequest":{"properties":{"manual_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Manual Data","description":"Optional manual data to merge before approval"}},"type":"object","title":"EdcApproveRequest","example":{"manual_data":{"notes":"Verified headcount with HR records"}}},"cbpulse__EdcDraftReport":{"properties":{"id":{"type":"string","title":"Id","description":"Unique EDC report identifier"},"report_type":{"type":"string","title":"Report Type","description":"Type of EDC report, e.g. annual or quarterly"},"period_start":{"type":"string","title":"Period Start","description":"Reporting period start date (YYYY-MM-DD)"},"period_end":{"type":"string","title":"Period End","description":"Reporting period end date (YYYY-MM-DD)"},"status":{"type":"string","title":"Status","description":"Report status: draft or approved"},"auto_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Auto Data","description":"Auto-populated data from system records"},"manual_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Manual Data","description":"User-supplied overrides or additions"},"generated_at":{"type":"string","title":"Generated At","description":"ISO 8601 timestamp of report generation"}},"type":"object","required":["id","report_type","period_start","period_end","status","auto_data","manual_data","generated_at"],"title":"EdcDraftReport","example":{"auto_data":{"total_employees":24,"total_wages":156000.0},"generated_at":"2026-02-15T10:30:00+00:00","id":"edc-rpt-2026-q1","period_end":"2026-03-31","period_start":"2026-01-01","report_type":"quarterly","status":"draft"}},"cbpulse__EdcObligation":{"properties":{"deadline_id":{"type":"string","title":"Deadline Id","description":"Linked financial deadline identifier"},"title":{"type":"string","title":"Title","description":"Name of the EDC obligation"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional details about the obligation"},"recurrence_type":{"type":"string","title":"Recurrence Type","description":"Filing schedule, e.g. quarterly or annually"},"next_due_date":{"type":"string","title":"Next Due Date","description":"Next filing due date (YYYY-MM-DD)"},"reminder_days":{"type":"integer","title":"Reminder Days","description":"Days before due date to trigger reminder"},"days_until_due":{"type":"integer","title":"Days Until Due","description":"Days remaining until next due date"},"period_start":{"type":"string","title":"Period Start","description":"Reporting period start date (YYYY-MM-DD)"},"period_end":{"type":"string","title":"Period End","description":"Reporting period end date (YYYY-MM-DD)"},"report_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Report Status","description":"Latest report status: draft, approved, or null"},"report_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Report Id","description":"Latest generated report ID, if any"}},"type":"object","required":["deadline_id","title","description","recurrence_type","next_due_date","reminder_days","days_until_due","period_start","period_end","report_status","report_id"],"title":"EdcObligation","example":{"days_until_due":59,"deadline_id":"dl-edc-q1-2026","description":"Quarterly headcount and wage report for EDC beneficiary","next_due_date":"2026-04-15","period_end":"2026-03-31","period_start":"2026-01-01","recurrence_type":"quarterly","reminder_days":14,"report_id":"edc-rpt-2026-q1","report_status":"draft","title":"EDC Quarterly Employment Report"}},"cbpulse__EdcObligationsResponse":{"properties":{"obligations":{"items":{"$ref":"#/components/schemas/cbpulse__EdcObligation"},"type":"array","title":"Obligations","description":"List of EDC compliance obligations"},"total":{"type":"integer","title":"Total","description":"Total number of obligations"}},"type":"object","required":["obligations","total"],"title":"EdcObligationsResponse","example":{"obligations":[],"total":2}},"cbpulse__EdcProgramResponse":{"properties":{"id":{"type":"string","title":"Id"},"entity_id":{"type":"string","title":"Entity Id"},"beneficiary_legal_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Beneficiary Legal Name"},"operating_business":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operating Business"},"operating_business_cbpulse_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operating Business Cbpulse Id"},"island":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Island"},"certificate_signed_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Certificate Signed Date"},"certificate_signed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Certificate Signed By"},"counsel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Counsel"},"capital_investment_requirement_usd":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Capital Investment Requirement Usd"},"capital_investment_window":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Capital Investment Window"},"employment_requirement":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employment Requirement"},"jobs_financial_program_trigger":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jobs Financial Program Trigger"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","required":["id","entity_id","beneficiary_legal_name","operating_business","operating_business_cbpulse_id","island","certificate_signed_date","certificate_signed_by","counsel","capital_investment_requirement_usd","capital_investment_window","employment_requirement","jobs_financial_program_trigger","note"],"title":"EdcProgramResponse","description":"EDC program header for an entity (the beneficiary's certificate facts)."},"cbpulse__EditChannelContentRequest":{"properties":{"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Updated subject / headline"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Updated body text"},"cta_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cta Text","description":"Updated call-to-action text"},"cta_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cta Url","description":"Updated call-to-action URL"}},"type":"object","title":"EditChannelContentRequest"},"cbpulse__EditDraftRequest":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"New article title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"New article body (markdown)"}},"type":"object","title":"EditDraftRequest","description":"Body for editing a draft before publish — both fields optional."},"cbpulse__EditorialPlanRequest":{"properties":{"horizon_days":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Horizon Days","description":"Scope window for the VI-news scan + refresh staleness floor. Smaller biases toward fresher, time-pegged opportunities."},"focus_categories":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Focus Categories","description":"Restrict the archive scan to these evergreen category names (e.g. ['Beaches', 'Tips']). Omit for all."}},"type":"object","title":"EditorialPlanRequest","description":"Body for Mode 4 (editorial planning). Both fields scope the scan."},"cbpulse__EditorialSlate":{"properties":{"generated_at":{"type":"string","format":"date-time","title":"Generated At"},"archive_total":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Archive Total"},"published_last_30_days":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Published Last 30 Days"},"stale_categories":{"items":{"type":"string"},"type":"array","title":"Stale Categories"},"recommendations":{"items":{"$ref":"#/components/schemas/cbpulse__SlateItem"},"type":"array","title":"Recommendations"},"quick_wins":{"items":{"type":"string"},"type":"array","title":"Quick Wins"},"timely_opportunities":{"items":{"type":"string"},"type":"array","title":"Timely Opportunities"},"content_gaps":{"items":{"type":"string"},"type":"array","title":"Content Gaps"},"sources_consulted":{"items":{"type":"string"},"type":"array","title":"Sources Consulted"},"ranking_degraded":{"type":"boolean","title":"Ranking Degraded","description":"True when the LLM scorer was unavailable and the deterministic fallback ranked this slate (cbpulse#583). A flat ranking that looks scored is worse than one labelled degraded: the 40-candidate call returned every score as 3.5 with every sub-score null and said nothing. A real field rather than a property, so external API consumers actually see it.","default":false},"archive_rejected_not_refreshable":{"additionalProperties":{"type":"integer"},"type":"object","title":"Archive Rejected Not Refreshable","description":"Archive posts dropped at gather time because a dated event notice is not refreshable — the event happened (cbpulse#588, CJ's ruling). Keyed by reason (specific_date, explicit_year, weekday, recurring_event, news_event, time_relative, occasion, no_title) so a rule can be argued with rather than just totalled. Reported for the same reason as ranking_degraded: a filter that drops candidates silently makes a short slate indistinguishable from a scan that found nothing, and 13 of 15 recommendations were once 20-year-old event notices. Empty dict means nothing was rejected."}},"type":"object","title":"EditorialSlate","description":"Prioritized editorial recommendations (Mode 4). Produced by #347."},"cbpulse__EmailSpec":{"properties":{"to":{"type":"string","title":"To","description":"Comma-separated recipient addresses"},"cc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"string"},{"type":"null"}],"title":"Cc","description":"Optional CC addresses (array or comma-separated string)"},"subject_template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Template","description":"Jinja2 template string for the subject line"},"front_channel_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Front Channel Id","description":"CBFront channel ID (required when delivery='front')"}},"type":"object","required":["to"],"title":"EmailSpec","description":"Email addressing and subject template for report delivery."},"cbpulse__EmployeeAgreement":{"properties":{"id":{"type":"string","title":"Id"},"employee_id":{"type":"string","title":"Employee Id"},"business_id":{"type":"string","title":"Business Id"},"agreement_type":{"type":"string","title":"Agreement Type"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"status":{"type":"string","title":"Status"},"effective_date":{"type":"string","title":"Effective Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"rate_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate Amount"},"rate_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Unit"},"rate_currency":{"type":"string","title":"Rate Currency"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"documents":{"items":{"$ref":"#/components/schemas/cbpulse__AgreementDocument"},"type":"array","title":"Documents","default":[]}},"type":"object","required":["id","employee_id","business_id","agreement_type","status","effective_date","end_date","rate_amount","rate_unit","rate_currency","notes","created_by","created_at","updated_at"],"title":"EmployeeAgreement"},"cbpulse__EmployeeDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"source":{"type":"string","title":"Source","description":"Integration source: clover, homebase, manual"},"external_id":{"type":"string","title":"External Id","description":"ID in the source system"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Position"},"hourly_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Hourly Rate","description":"Current hourly pay rate in USD"},"employment_type":{"type":"string","title":"Employment Type","description":"hourly, salary, or contractor"},"hire_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hire Date"},"termination_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Termination Date"},"is_active":{"type":"boolean","title":"Is Active","description":"False means terminated or inactive"},"additional_businesses":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Additional Businesses","description":"Other business IDs this employee works at"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"compliance_docs":{"items":{"$ref":"#/components/schemas/cbpulse__ComplianceDocResponse"},"type":"array","title":"Compliance Docs"}},"type":"object","required":["id","business_id","source","external_id","first_name","last_name","employment_type","is_active","created_at","updated_at"],"title":"EmployeeDetailResponse","example":{"business_id":"biz-the-fred","created_at":"2026-01-15T10:00:00","department":"Food & Beverage","email":"maria@example.com","employment_type":"hourly","external_id":"HB-1234","first_name":"Maria","hire_date":"2024-06-15","hourly_rate":15.5,"id":"emp-abc123","is_active":true,"last_name":"Santos","phone":"+13405551234","position":"Server","source":"homebase","updated_at":"2026-02-18T08:30:00"}},"cbpulse__EmployeeDocument":{"properties":{"id":{"type":"string","title":"Id"},"employee_id":{"type":"string","title":"Employee Id"},"business_id":{"type":"string","title":"Business Id"},"file_id":{"type":"string","title":"File Id"},"category":{"type":"string","title":"Category"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"filename":{"type":"string","title":"Filename"},"content_type":{"type":"string","title":"Content Type"},"size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size Bytes"},"uploaded_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uploaded By"},"uploaded_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uploaded At"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","required":["id","employee_id","business_id","file_id","category","label","filename","content_type","size_bytes","uploaded_by","uploaded_at"],"title":"EmployeeDocument"},"cbpulse__EmployeePatchRequest":{"properties":{"first_name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Last Name"},"email":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string","maxLength":30},{"type":"null"}],"title":"Phone"},"position":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Position"},"department":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Department"},"hourly_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Hourly Rate"},"hire_date":{"anyOf":[{"type":"string","maxLength":10,"minLength":10},{"type":"null"}],"title":"Hire Date"},"business_id":{"anyOf":[{"type":"string","maxLength":36,"minLength":36},{"type":"null"}],"title":"Business Id"}},"type":"object","title":"EmployeePatchRequest"},"cbpulse__EmployeeResponse":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"source":{"type":"string","title":"Source","description":"Integration source: clover, homebase, manual"},"external_id":{"type":"string","title":"External Id","description":"ID in the source system"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Position"},"hourly_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Hourly Rate","description":"Current hourly pay rate in USD"},"employment_type":{"type":"string","title":"Employment Type","description":"hourly, salary, or contractor"},"hire_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hire Date"},"termination_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Termination Date"},"is_active":{"type":"boolean","title":"Is Active","description":"False means terminated or inactive"},"additional_businesses":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Additional Businesses","description":"Other business IDs this employee works at"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","business_id","source","external_id","first_name","last_name","employment_type","is_active","created_at","updated_at"],"title":"EmployeeResponse","description":"Employee directory entry.","example":{"business_id":"biz-the-fred","created_at":"2026-01-15T10:00:00","department":"Food & Beverage","email":"maria@example.com","employment_type":"hourly","external_id":"HB-1234","first_name":"Maria","hire_date":"2024-06-15","hourly_rate":15.5,"id":"emp-abc123","is_active":true,"last_name":"Santos","phone":"+13405551234","position":"Server","source":"homebase","updated_at":"2026-02-18T08:30:00"}},"cbpulse__EmployeeRowResponse":{"properties":{"id":{"type":"string","title":"Id"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"source":{"type":"string","title":"Source"},"external_id":{"type":"string","title":"External Id"},"position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Position"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"hire_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hire Date"},"termination_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Termination Date"},"status":{"type":"string","title":"Status"},"is_active":{"type":"boolean","title":"Is Active"},"business_id":{"type":"string","title":"Business Id"},"business_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Slug"},"business_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Name"},"writeup_count":{"type":"integer","title":"Writeup Count"},"incident_count":{"type":"integer","title":"Incident Count"},"onboarding_count":{"type":"integer","title":"Onboarding Count"},"last_touched":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Touched"}},"type":"object","required":["id","first_name","last_name","email","phone","source","external_id","position","department","hire_date","termination_date","status","is_active","business_id","business_slug","business_name","writeup_count","incident_count","onboarding_count","last_touched"],"title":"EmployeeRowResponse"},"cbpulse__EmployeeSummaryRow":{"properties":{"id":{"type":"string","title":"Id"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"business_id":{"type":"string","title":"Business Id"},"position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Position"},"department":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Department"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"hire_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hire Date"},"is_active":{"type":"boolean","title":"Is Active"},"status":{"type":"string","title":"Status"},"has_signed_onboarding":{"type":"boolean","title":"Has Signed Onboarding"},"compliance_doc_count":{"type":"integer","title":"Compliance Doc Count"},"compliance_valid_count":{"type":"integer","title":"Compliance Valid Count"},"compliance_expired_count":{"type":"integer","title":"Compliance Expired Count"},"compliance_missing_count":{"type":"integer","title":"Compliance Missing Count"},"open_writeup_count":{"type":"integer","title":"Open Writeup Count"},"draft_writeup_count":{"type":"integer","title":"Draft Writeup Count"},"last_writeup_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Writeup Date"},"arbitration_status":{"type":"string","title":"Arbitration Status"},"arbitration_signed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Arbitration Signed At"}},"type":"object","required":["id","external_id","first_name","last_name","business_id","position","department","email","phone","hire_date","is_active","status","has_signed_onboarding","compliance_doc_count","compliance_valid_count","compliance_expired_count","compliance_missing_count","open_writeup_count","draft_writeup_count","last_writeup_date","arbitration_status","arbitration_signed_at"],"title":"EmployeeSummaryRow","description":"Per-employee rollup powering cbloom's Employee File preset filters\n(cbpulse#407).\n\nAll summary fields are pre-aggregated server-side so the UI can render\n\"Needs onboarding\" / \"Compliance gaps\" / \"Recent hires\" / \"Open writeups\"\nchips without fanning out 250+ ``/employees/{id}/file`` calls."},"cbpulse__EntityAttributesUpdate":{"properties":{"ein":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ein"},"date_formed":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date Formed"},"island":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Island"},"legal_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legal Name"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"EntityAttributesUpdate"},"cbpulse__EntityDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"legal_name":{"type":"string","title":"Legal Name"},"dba_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dba Name"},"vip_business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vip Business Id"},"entity_type":{"type":"string","title":"Entity Type"},"role_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role Description"},"ein":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ein"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"island":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Island"},"parent_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Entity Id"},"is_edc_holder":{"type":"boolean","title":"Is Edc Holder"},"is_active":{"type":"boolean","title":"Is Active"},"trade_names":{"items":{"$ref":"#/components/schemas/cbpulse__TradeNameResponse"},"type":"array","title":"Trade Names"},"document_count":{"type":"integer","title":"Document Count"}},"type":"object","required":["id","legal_name","dba_name","vip_business_id","entity_type","role_description","ein","address","island","parent_entity_id","is_edc_holder","is_active","trade_names","document_count"],"title":"EntityDetailResponse"},"cbpulse__EntityResponse":{"properties":{"id":{"type":"string","title":"Id"},"legal_name":{"type":"string","title":"Legal Name"},"dba_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dba Name"},"vip_business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vip Business Id"},"entity_type":{"type":"string","title":"Entity Type"},"role_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role Description"},"ein":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ein"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"island":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Island"},"parent_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Entity Id"},"is_edc_holder":{"type":"boolean","title":"Is Edc Holder"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","legal_name","dba_name","vip_business_id","entity_type","role_description","ein","address","island","parent_entity_id","is_edc_holder","is_active"],"title":"EntityResponse"},"cbpulse__EntitySearchResult":{"properties":{"id":{"type":"string","title":"Id"},"legal_name":{"type":"string","title":"Legal Name"},"dba_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dba Name"},"entity_type":{"type":"string","title":"Entity Type"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","legal_name","dba_name","entity_type","is_active"],"title":"EntitySearchResult"},"cbpulse__EscalateIncidentRequest":{"properties":{"violation_category":{"type":"string","title":"Violation Category"},"discipline_level":{"type":"string","title":"Discipline Level","description":"verbal_warning, first_written, final_written, suspension, termination_rec"},"wda_ground":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wda Ground"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["violation_category","discipline_level"],"title":"EscalateIncidentRequest","description":"Promote an incident into a draft writeup. Pre-fills from the incident\nso the manager just refines language and clicks through to send."},"cbpulse__EventCadenceResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Event cadence ID"},"business_id":{"type":"string","title":"Business Id","description":"Business this event belongs to"},"name":{"type":"string","title":"Name","description":"Event name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Event description"},"event_date":{"type":"string","title":"Event Date","description":"Event date (YYYY-MM-DD)"},"steps":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Steps","description":"Cadence steps"},"status":{"type":"string","title":"Status","description":"Event status (draft, active, completed, cancelled)"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the event is active"},"campaign_proposal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Proposal Id","description":"Linked campaign proposal ID"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Creator user ID"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["id","business_id","name","event_date","status","is_active","created_at","updated_at"],"title":"EventCadenceResponse","description":"Full event cadence with optional linked campaign."},"cbpulse__EventCadenceStep":{"properties":{"offset_days":{"type":"integer","title":"Offset Days","description":"Days relative to event date (negative = before)"},"channel":{"type":"string","title":"Channel","description":"Delivery channel (email, sms, social_instagram, etc.)"},"action":{"type":"string","title":"Action","description":"Action description (e.g. 'Send teaser email')"}},"type":"object","required":["offset_days","channel","action"],"title":"EventCadenceStep","description":"A single step in an event marketing cadence."},"cbpulse__EventCreateRequest":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Target business ID"},"name":{"type":"string","maxLength":200,"title":"Name","description":"Event name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Event description"},"event_date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","title":"Event Date","description":"Event date (YYYY-MM-DD)"},"steps":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbpulse__EventCadenceStep"},"type":"array"},{"type":"null"}],"title":"Steps","description":"Cadence steps"},"channels":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Channels","description":"Channels for auto-generated campaign (defaults to email + social)"}},"type":"object","required":["business_id","name","event_date"],"title":"EventCreateRequest"},"cbpulse__EventListResponse":{"properties":{"events":{"items":{"$ref":"#/components/schemas/cbpulse__EventCadenceResponse"},"type":"array","title":"Events","description":"List of event cadences"},"total":{"type":"integer","title":"Total","description":"Total matching events"}},"type":"object","required":["events","total"],"title":"EventListResponse"},"cbpulse__EventOccurrenceResponse":{"properties":{"date":{"type":"string","format":"date","title":"Date","description":"Calendar day of this occurrence (AST)"},"brand_slug":{"type":"string","title":"Brand Slug","description":"Brand slug"},"title":{"type":"string","title":"Title","description":"Event title"},"event_type":{"type":"string","title":"Event Type","description":"Event category, e.g. fire_dancers, brunch"},"status":{"type":"string","title":"Status","description":"proposed | confirmed"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Provenance of this event"},"is_recurring":{"type":"boolean","title":"Is Recurring","description":"True if expanded from a recurring rule"},"event_id":{"type":"string","title":"Event Id","description":"Source brand_events row id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes"}},"type":"object","required":["date","brand_slug","title","event_type","status","is_recurring","event_id"],"title":"EventOccurrenceResponse","description":"One materialised occurrence of a brand event on a single calendar day (AST)."},"cbpulse__EventUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"event_date":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Event Date"},"steps":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbpulse__EventCadenceStep"},"type":"array"},{"type":"null"}],"title":"Steps"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"regenerate_campaign":{"type":"boolean","title":"Regenerate Campaign","description":"If true and key fields changed, regenerate linked campaign content","default":false}},"type":"object","title":"EventUpdateRequest"},"cbpulse__ExceptionFeedResponse":{"properties":{"date":{"type":"string","title":"Date","description":"Report date in YYYY-MM-DD format"},"exceptions":{"items":{"$ref":"#/components/schemas/cbpulse__ExceptionItem"},"type":"array","title":"Exceptions","description":"All exceptions sorted by severity"},"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts","description":"Exception counts grouped by severity level"}},"type":"object","required":["date","exceptions","counts"],"title":"ExceptionFeedResponse","example":{"counts":{"critical":1,"info":2,"warning":3},"date":"2026-02-15","exceptions":[]}},"cbpulse__ExceptionItem":{"properties":{"type":{"type":"string","title":"Type","description":"Exception category: anomaly, alert, or integration"},"severity":{"type":"string","title":"Severity","description":"Severity level: critical, warning, or info"},"business_name":{"type":"string","title":"Business Name","description":"Name of the affected business"},"message":{"type":"string","title":"Message","description":"Human-readable summary of the exception"},"detail":{"type":"string","title":"Detail","description":"Additional context or recommended action"},"link":{"type":"string","title":"Link","description":"Deep link to the relevant dashboard or record"}},"type":"object","required":["type","severity","business_name","message","detail","link"],"title":"ExceptionItem","example":{"business_name":"The Saint","detail":"Current: 38.5% vs baseline: 32.0%. Review staffing schedule.","link":"/portfolio/the-saint/financials","message":"Labor cost 20% above 30-day average","severity":"warning","type":"anomaly"}},"cbpulse__ExcludeRequest":{"properties":{"reason":{"type":"string","maxLength":500,"minLength":3,"title":"Reason","description":"Why this row is not staff (goes in the audit trail)."},"clear_email":{"type":"boolean","title":"Clear Email","description":"Also NULL the email — the usual case: the row was synthesized from a vendor/role address that must not read as a person's.","default":true}},"type":"object","required":["reason"],"title":"ExcludeRequest"},"cbpulse__ExcludeResponse":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status"},"is_active":{"type":"boolean","title":"Is Active"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"cleared_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cleared Email","description":"The email that was removed, for the audit echo."}},"type":"object","required":["id","status","is_active","email"],"title":"ExcludeResponse"},"cbpulse__ExpiringDocument":{"properties":{"id":{"type":"string","title":"Id"},"title":{"type":"string","title":"Title"},"document_type_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Type Code"},"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"expiration_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiration Date"},"days_until_expiration":{"type":"integer","title":"Days Until Expiration"},"urgency_tier":{"type":"string","title":"Urgency Tier"}},"type":"object","required":["id","title","document_type_code","entity_id","expiration_date","days_until_expiration","urgency_tier"],"title":"ExpiringDocument"},"cbpulse__FBApproveResponse":{"properties":{"invoice_id":{"type":"string","title":"Invoice Id"},"fb_order_id":{"type":"string","title":"Fb Order Id"},"total":{"type":"number","title":"Total"},"month":{"type":"string","title":"Month"}},"type":"object","required":["invoice_id","fb_order_id","total","month"],"title":"FBApproveResponse"},"cbpulse__FBInvoiceCorrect":{"properties":{"vendor_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Vendor Name"},"amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Amount"},"business_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}],"title":"Business Id"},"purchase_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purchase Category","description":"One of fb_food / fb_drink / fb_other / general."}},"type":"object","title":"FBInvoiceCorrect","description":"Partial correction of a mis-extracted / mis-routed F&B invoice."},"cbpulse__FBInvoiceDetail":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"business_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Name"},"vendor_name":{"type":"string","title":"Vendor Name"},"bill_to_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bill To Name"},"invoice_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Number"},"invoice_date":{"type":"string","title":"Invoice Date"},"amount":{"type":"number","title":"Amount"},"purchase_category":{"type":"string","title":"Purchase Category"},"status":{"type":"string","title":"Status"},"source_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Email"},"fb_order_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fb Order Id"},"line_items":{"items":{"$ref":"#/components/schemas/cbpulse__FBInvoiceLineItem"},"type":"array","title":"Line Items"},"source_document":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document","description":"Signed URL to the source invoice file (TTL-limited)"},"source_images":{"items":{"type":"string"},"type":"array","title":"Source Images","description":"Signed URLs to source page images, if any"},"ocr_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ocr Confidence"},"ocr_text_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ocr Text Preview"}},"type":"object","required":["id","business_id","vendor_name","invoice_date","amount","purchase_category","status"],"title":"FBInvoiceDetail","description":"The detail view (cbpulse#513) — topline + line items + source document.\n\nLets a manager verify the extraction against the actual invoice: the parsed\nline items, a signed URL to the stored source document (+ any page images),\nand the OCR confidence so low-confidence extractions are visible."},"cbpulse__FBInvoiceLineItem":{"properties":{"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"quantity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quantity"},"unit_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Unit Price"},"total":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total"}},"type":"object","title":"FBInvoiceLineItem","description":"One parsed line item from the invoice extraction (best-effort shape)."},"cbpulse__FBInvoiceReject":{"properties":{"reason":{"type":"string","maxLength":2000,"minLength":1,"title":"Reason"}},"type":"object","required":["reason"],"title":"FBInvoiceReject"},"cbpulse__FBInvoiceSummary":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"business_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Name"},"vendor_name":{"type":"string","title":"Vendor Name"},"bill_to_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bill To Name"},"invoice_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Number"},"invoice_date":{"type":"string","title":"Invoice Date"},"amount":{"type":"number","title":"Amount"},"purchase_category":{"type":"string","title":"Purchase Category"},"status":{"type":"string","title":"Status"},"source_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Email"},"fb_order_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fb Order Id"}},"type":"object","required":["id","business_id","vendor_name","invoice_date","amount","purchase_category","status"],"title":"FBInvoiceSummary","description":"A pending F&B invoice as shown in the review lane."},"cbpulse__FBStatementApproveResponse":{"properties":{"statement_id":{"type":"string","title":"Statement Id"},"orders_created":{"type":"integer","title":"Orders Created"},"booked_amount":{"type":"number","title":"Booked Amount"},"dates":{"items":{"type":"string"},"type":"array","title":"Dates"}},"type":"object","required":["statement_id","orders_created","booked_amount","dates"],"title":"FBStatementApproveResponse"},"cbpulse__FBStatementDetail":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"business_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Name"},"vendor_name":{"type":"string","title":"Vendor Name"},"statement_date":{"type":"string","title":"Statement Date"},"period_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Start"},"period_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period End"},"total_amount":{"type":"number","title":"Total Amount"},"status":{"type":"string","title":"Status"},"purchase_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purchase Category"},"source_file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source File Path"},"reference_count":{"type":"integer","title":"Reference Count"},"booked_amount":{"type":"number","title":"Booked Amount"},"cost_lines":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Cost Lines"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"}},"type":"object","required":["id","business_id","vendor_name","statement_date","period_start","period_end","total_amount","status","purchase_category","source_file_path","reference_count","booked_amount","cost_lines"],"title":"FBStatementDetail"},"cbpulse__FBStatementSummary":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"business_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Name"},"vendor_name":{"type":"string","title":"Vendor Name"},"statement_date":{"type":"string","title":"Statement Date"},"period_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Start"},"period_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period End"},"total_amount":{"type":"number","title":"Total Amount"},"status":{"type":"string","title":"Status"},"purchase_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purchase Category"},"source_file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source File Path"},"reference_count":{"type":"integer","title":"Reference Count"},"booked_amount":{"type":"number","title":"Booked Amount"}},"type":"object","required":["id","business_id","vendor_name","statement_date","period_start","period_end","total_amount","status","purchase_category","source_file_path","reference_count","booked_amount"],"title":"FBStatementSummary"},"cbpulse__FBUserResponse":{"properties":{"id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Id"},"name":{"type":"string","title":"Name"},"role":{"type":"string","title":"Role"},"business_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Business Ids"},"actor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor"},"source":{"anyOf":[{"type":"string","enum":["cbauth","fb-key","fb-pin"]},{"type":"null"}],"title":"Source"}},"type":"object","required":["id","name","role"],"title":"FBUserResponse"},"cbpulse__FilingCreate":{"properties":{"obligation_id":{"type":"string","title":"Obligation Id"},"period":{"type":"string","title":"Period"},"due_date":{"type":"string","title":"Due Date"},"status":{"type":"string","title":"Status","default":"upcoming"},"proof_document_ids":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Proof Document Ids"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["obligation_id","period","due_date"],"title":"FilingCreate"},"cbpulse__FilingResponse":{"properties":{"id":{"type":"string","title":"Id"},"obligation_id":{"type":"string","title":"Obligation Id"},"period":{"type":"string","title":"Period"},"due_date":{"type":"string","title":"Due Date"},"status":{"type":"string","title":"Status"},"proof_document_ids":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Proof Document Ids"},"filed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Filed At"},"filed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed By"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","obligation_id","period","due_date","status","proof_document_ids","filed_at","filed_by","notes","created_at","updated_at"],"title":"FilingResponse"},"cbpulse__FilingUpdate":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"proof_document_ids":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Proof Document Ids"},"filed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed At"},"filed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed By"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"FilingUpdate"},"cbpulse__FinanceException":{"properties":{"type":{"type":"string","title":"Type","description":"Exception type, e.g. overdue_invoice or deadline"},"severity":{"type":"string","title":"Severity","description":"Severity level: critical, warning, or info"},"business_name":{"type":"string","title":"Business Name","description":"Business affected by this exception"},"message":{"type":"string","title":"Message","description":"Human-readable summary of the exception"},"detail":{"type":"string","title":"Detail","description":"Additional context or amounts involved"},"link":{"type":"string","title":"Link","description":"Frontend route to resolve the exception"}},"type":"object","required":["type","severity","business_name","message","detail","link"],"title":"FinanceException","example":{"business_name":"The Fred","detail":"$3,450.00 past due since 2026-02-01","link":"/finance/payables","message":"Caribbean Produce Co. invoice 14 days overdue","severity":"critical","type":"overdue_invoice"}},"cbpulse__FinancialsRecordRequest":{"properties":{"report_date":{"type":"string","title":"Report Date","description":"Date this record covers (YYYY-MM-DD)"},"period":{"type":"string","title":"Period","description":"Reporting period granularity: daily, weekly, or monthly","default":"daily"},"total_revenue":{"type":"number","title":"Total Revenue","description":"Gross revenue across all streams","default":0.0},"room_revenue":{"type":"number","title":"Room Revenue","description":"Revenue from room bookings and accommodations","default":0.0},"fb_revenue":{"type":"number","title":"Fb Revenue","description":"Food & beverage revenue","default":0.0},"other_revenue":{"type":"number","title":"Other Revenue","description":"Revenue from spa, retail, and other sources","default":0.0},"labor_cost":{"type":"number","title":"Labor Cost","description":"Total labor/payroll cost","default":0.0},"cogs":{"type":"number","title":"Cogs","description":"Cost of goods sold","default":0.0},"occupancy_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Occupancy Rate","description":"Hotel room occupancy rate (0-100)"},"adr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Adr","description":"Average daily rate for rooms"},"revpar":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Revpar","description":"Revenue per available room"},"sources":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sources","description":"Data sources, e.g. ['manual', 'clover']"}},"type":"object","required":["report_date"],"title":"FinancialsRecordRequest","description":"Payload for manually recording a financial data point for a business.","example":{"adr":285.0,"cogs":2125.0,"fb_revenue":4100.0,"labor_cost":2805.0,"occupancy_rate":87.5,"other_revenue":1200.0,"period":"daily","report_date":"2026-02-15","revpar":249.38,"room_revenue":3200.0,"sources":["manual"],"total_revenue":8500.0}},"cbpulse__FinancialsResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique financials record identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this financial snapshot belongs to"},"report_date":{"type":"string","title":"Report Date","description":"Date this snapshot covers (YYYY-MM-DD)"},"period":{"type":"string","title":"Period","description":"Reporting period granularity: daily, weekly, or monthly"},"total_revenue":{"type":"number","title":"Total Revenue","description":"Gross revenue across all streams for this period"},"room_revenue":{"type":"number","title":"Room Revenue","description":"Revenue from room bookings and accommodations"},"fb_revenue":{"type":"number","title":"Fb Revenue","description":"Food & beverage revenue"},"other_revenue":{"type":"number","title":"Other Revenue","description":"Revenue from spa, retail, and other non-room/F&B sources"},"labor_cost":{"type":"number","title":"Labor Cost","description":"Total labor/payroll cost for this period"},"cogs":{"type":"number","title":"Cogs","description":"Cost of goods sold (food, beverage, supplies)"},"gross_profit":{"type":"number","title":"Gross Profit","description":"Total revenue minus labor and COGS"},"labor_percent":{"type":"number","title":"Labor Percent","description":"Labor cost as a percentage of total revenue (0-100)"},"cogs_percent":{"type":"number","title":"Cogs Percent","description":"COGS as a percentage of total revenue (0-100)"},"occupancy_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Occupancy Rate","description":"Hotel room occupancy rate (0-100), null for non-hotel businesses"},"adr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Adr","description":"Average daily rate for rooms, null for non-hotel businesses"},"revpar":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Revpar","description":"Revenue per available room, null for non-hotel businesses"},"transaction_count":{"type":"integer","title":"Transaction Count","description":"Number of POS transactions in this period","default":0},"labor_hours":{"type":"number","title":"Labor Hours","description":"Total labor hours worked in this period","default":0.0},"health_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Health Score","description":"Computed financial health score (1-100), null if not yet calculated"},"sources":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sources","description":"Data sources that contributed to this snapshot, e.g. ['clover', 'cloudbeds']"}},"type":"object","required":["id","business_id","report_date","period","total_revenue","room_revenue","fb_revenue","other_revenue","labor_cost","cogs","gross_profit","labor_percent","cogs_percent"],"title":"FinancialsResponse","description":"Financial snapshot for a single business and reporting period.","example":{"adr":285.0,"business_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","cogs":2125.0,"cogs_percent":25.0,"fb_revenue":4100.0,"gross_profit":3570.0,"health_score":85,"id":"fin-20260215-a1b2c3d4","labor_cost":2805.0,"labor_hours":86.5,"labor_percent":33.0,"occupancy_rate":87.5,"other_revenue":1200.0,"period":"daily","report_date":"2026-02-15","revpar":249.38,"room_revenue":3200.0,"sources":["clover","cloudbeds"],"total_revenue":8500.0,"transaction_count":142}},"cbpulse__FlashReportBusiness":{"properties":{"id":{"type":"string","title":"Id","description":"Unique business identifier"},"name":{"type":"string","title":"Name","description":"Business display name"},"slug":{"type":"string","title":"Slug","description":"URL-safe business identifier"},"island":{"type":"string","title":"Island","description":"USVI island where business operates"},"total_revenue":{"type":"number","title":"Total Revenue","description":"Combined revenue across all channels","default":0.0},"fb_revenue":{"type":"number","title":"Fb Revenue","description":"Food and beverage revenue","default":0.0},"room_revenue":{"type":"number","title":"Room Revenue","description":"Hotel room revenue","default":0.0},"other_revenue":{"type":"number","title":"Other Revenue","description":"Revenue from spa, retail, and other sources","default":0.0},"labor_cost":{"type":"number","title":"Labor Cost","description":"Total labor cost for the period","default":0.0},"cogs":{"type":"number","title":"Cogs","description":"Cost of goods sold","default":0.0},"gross_profit":{"type":"number","title":"Gross Profit","description":"Revenue minus COGS and labor","default":0.0},"labor_percent":{"type":"number","title":"Labor Percent","description":"Labor cost as percentage of revenue","default":0.0},"cogs_percent":{"type":"number","title":"Cogs Percent","description":"COGS as percentage of revenue","default":0.0},"transaction_count":{"type":"integer","title":"Transaction Count","description":"Number of POS transactions","default":0},"labor_hours":{"type":"number","title":"Labor Hours","description":"Total staff hours worked","default":0.0},"prime_cost_pct":{"type":"number","title":"Prime Cost Pct","description":"Labor plus COGS as percentage of revenue","default":0.0},"food_cost_pct":{"type":"number","title":"Food Cost Pct","description":"Food cost as percentage of F&B revenue","default":0.0},"avg_check":{"type":"number","title":"Avg Check","description":"Average transaction amount","default":0.0},"rev_per_labor_hour":{"type":"number","title":"Rev Per Labor Hour","description":"Revenue generated per labor hour","default":0.0},"health_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Health Score","description":"Financial health score from 1 to 100"},"wow":{"$ref":"#/components/schemas/cbpulse__WoWChange","description":"Week-over-week percentage changes"}},"type":"object","required":["id","name","slug","island"],"title":"FlashReportBusiness","description":"Single business KPIs within a flash report.","example":{"avg_check":59.86,"cogs":2125.0,"cogs_percent":25.0,"fb_revenue":4100.0,"food_cost_pct":28.5,"gross_profit":3570.0,"health_score":85,"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","island":"St. Croix","labor_cost":2805.0,"labor_hours":86.5,"labor_percent":33.0,"name":"The Fred","other_revenue":1200.0,"prime_cost_pct":58.0,"rev_per_labor_hour":98.27,"room_revenue":3200.0,"slug":"the-fred","total_revenue":8500.0,"transaction_count":142,"wow":{"avg_check":4.3,"cogs":5.1,"health_score":2.0,"labor_cost":-3.2,"labor_percent":-1.8,"prime_cost_pct":-0.7,"rev_per_labor_hour":8.9,"total_revenue":12.5}}},"cbpulse__FlashReportResponse":{"properties":{"date":{"type":"string","title":"Date","description":"Report reference date in YYYY-MM-DD format"},"period":{"type":"string","title":"Period","description":"Aggregation period: day, wtd, or ptd"},"period_start":{"type":"string","title":"Period Start","description":"Start date of the reporting period"},"period_end":{"type":"string","title":"Period End","description":"End date of the reporting period"},"portfolio_totals":{"$ref":"#/components/schemas/cbpulse__PortfolioTotals","description":"Aggregated KPIs across all businesses"},"businesses":{"items":{"$ref":"#/components/schemas/cbpulse__FlashReportBusiness"},"type":"array","title":"Businesses","description":"Per-business KPI breakdowns"}},"type":"object","required":["date","period","period_start","period_end","portfolio_totals","businesses"],"title":"FlashReportResponse","description":"Full flash report with portfolio totals and per-business breakdowns.","example":{"businesses":[],"date":"2026-02-15","period":"day","period_end":"2026-02-15","period_start":"2026-02-15","portfolio_totals":{}}},"cbpulse__ForceCancelRequest":{"properties":{"reason":{"type":"string","maxLength":2000,"minLength":1,"title":"Reason","description":"Why the writeup is being force-cancelled. Stored on the audit row."},"void_envelope":{"type":"boolean","title":"Void Envelope","description":"If True (default) and the writeup has a ``boldsign_doc_id``, attempt to revoke the BoldSign envelope via cbesig. Failures are logged but do not block the local cancel — recovery from inconsistent state is the whole reason this endpoint exists.","default":true}},"type":"object","required":["reason"],"title":"ForceCancelRequest"},"cbpulse__ForceCancelResponse":{"properties":{"id":{"type":"string","title":"Id"},"previous_status":{"type":"string","title":"Previous Status"},"new_status":{"type":"string","title":"New Status"},"boldsign_doc_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Boldsign Doc Id"},"envelope_void_attempted":{"type":"boolean","title":"Envelope Void Attempted"},"envelope_void_succeeded":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Envelope Void Succeeded"},"envelope_void_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Envelope Void Error"},"cancelled_at":{"type":"string","title":"Cancelled At"}},"type":"object","required":["id","previous_status","new_status","boldsign_doc_id","envelope_void_attempted","envelope_void_succeeded","envelope_void_error","cancelled_at"],"title":"ForceCancelResponse"},"cbpulse__ForgotPasswordRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Email address for the account to reset","examples":["gm@thefred.vi","admin@thesaint.vi"]}},"type":"object","required":["email"],"title":"ForgotPasswordRequest","description":"Request a password reset link."},"cbpulse__FormerEmployeeRow":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"name":{"type":"string","title":"Name"},"status":{"type":"string","title":"Status"},"business_id":{"type":"string","title":"Business Id"},"business_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Name"},"last_position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Position"},"hire_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hire Date"},"termination_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Termination Date"},"re_hire_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Re Hire Date"},"tenure_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tenure Days"},"statutory_ground":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Statutory Ground"},"statutory_ground_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Statutory Ground Label"},"termination_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Termination Id"},"decided_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decided By Name"},"has_signed_arbitration":{"type":"boolean","title":"Has Signed Arbitration"}},"type":"object","required":["employee_id","name","status","business_id","business_name","last_position","hire_date","termination_date","re_hire_date","tenure_days","statutory_ground","statutory_ground_label","termination_id","decided_by_name","has_signed_arbitration"],"title":"FormerEmployeeRow"},"cbpulse__FullPipelinePlanResponse":{"properties":{"generated_at":{"type":"string","format":"date-time","title":"Generated At"},"archive_total":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Archive Total"},"published_last_30_days":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Published Last 30 Days"},"stale_categories":{"items":{"type":"string"},"type":"array","title":"Stale Categories"},"recommendations":{"items":{"$ref":"#/components/schemas/cbpulse__SlateItem"},"type":"array","title":"Recommendations"},"quick_wins":{"items":{"type":"string"},"type":"array","title":"Quick Wins"},"timely_opportunities":{"items":{"type":"string"},"type":"array","title":"Timely Opportunities"},"content_gaps":{"items":{"type":"string"},"type":"array","title":"Content Gaps"},"sources_consulted":{"items":{"type":"string"},"type":"array","title":"Sources Consulted"},"ranking_degraded":{"type":"boolean","title":"Ranking Degraded","description":"True when the LLM scorer was unavailable and the deterministic fallback ranked this slate (cbpulse#583). A flat ranking that looks scored is worse than one labelled degraded: the 40-candidate call returned every score as 3.5 with every sub-score null and said nothing. A real field rather than a property, so external API consumers actually see it.","default":false},"archive_rejected_not_refreshable":{"additionalProperties":{"type":"integer"},"type":"object","title":"Archive Rejected Not Refreshable","description":"Archive posts dropped at gather time because a dated event notice is not refreshable — the event happened (cbpulse#588, CJ's ruling). Keyed by reason (specific_date, explicit_year, weekday, recurring_event, news_event, time_relative, occasion, no_title) so a rule can be argued with rather than just totalled. Reported for the same reason as ranking_degraded: a filter that drops candidates silently makes a short slate indistinguishable from a scan that found nothing, and 13 of 15 recommendations were once 20-year-old event notices. Empty dict means nothing was rejected."},"slate_id":{"type":"string","title":"Slate Id","description":"Persisted slate id — pass to /full-pipeline/produce"},"expires_at":{"type":"string","title":"Expires At","description":"ISO timestamp the slate expires (24h TTL)"}},"type":"object","required":["slate_id","expires_at"],"title":"FullPipelinePlanResponse","description":"A Mode 4 slate plus the ``slate_id`` produce references (Mode 5 plan)."},"cbpulse__FullPipelineProduceRequest":{"properties":{"slate_id":{"type":"string","title":"Slate Id","description":"Slate id returned by /full-pipeline/plan"},"approved_indices":{"items":{"type":"integer"},"type":"array","minItems":1,"title":"Approved Indices","description":"Indices into the slate's recommendations CJ approved for production."},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","description":"LLM provider override applied to every worker: ollama | anthropic"}},"type":"object","required":["slate_id","approved_indices"],"title":"FullPipelineProduceRequest","description":"Body for Mode 5 produce — the slate id + CJ's approved item indices."},"cbpulse__FunctionalRole":{"type":"string","enum":["owner","finance","payroll","hr","marketing","operations"],"title":"FunctionalRole","description":"Functional roles controlling module visibility.\n\nUsers may hold multiple functional roles simultaneously.\nThese are separate from the system access level (UserRole)."},"cbpulse__GenerateCampaignRequest":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Target business ID"},"objective":{"type":"string","title":"Objective","description":"Campaign objective / goal","examples":["Drive midweek bookings with a flash sale"]},"signal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signal Id","description":"Marketing signal that triggered this campaign"},"target_segment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Segment Id","description":"Guest segment to target"},"channels":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Channels","description":"Channels to generate content for (defaults to email + social)","examples":[["email","social_instagram","social_facebook"]]}},"type":"object","required":["business_id","objective"],"title":"GenerateCampaignRequest","example":{"business_id":"biz_thefred","channels":["email","social_instagram"],"objective":"Drive midweek bookings with a flash sale"}},"cbpulse__GoldIngestRequest":{"properties":{"reservations":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Reservations","description":"Raw reservation objects from the GuestCenter gold endpoint"},"business_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Slug","description":"Target business; defaults to settings.opentable_business_slug"},"window_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Window Start","description":"Fetch window start (audit/log only)"},"window_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Window End","description":"Fetch window end (audit/log only)"}},"type":"object","required":["reservations"],"title":"GoldIngestRequest"},"cbpulse__GoldIngestResult":{"properties":{"job":{"type":"string","title":"Job","default":"opentable_gold_ingest"},"status":{"type":"string","title":"Status"},"received":{"type":"integer","title":"Received"},"parsed":{"type":"integer","title":"Parsed"},"created":{"type":"integer","title":"Created"},"updated":{"type":"integer","title":"Updated"},"duration":{"type":"number","title":"Duration"}},"type":"object","required":["status","received","parsed","created","updated","duration"],"title":"GoldIngestResult"},"cbpulse__GuestCRM":{"properties":{"total_profiles":{"type":"integer","title":"Total Profiles","description":"Total active guest profiles"},"new_last_30d":{"type":"integer","title":"New Last 30D","description":"Guest profiles created in the last 30 days"},"by_tier":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Tier","description":"Guest profile counts grouped by spend/loyalty tier"},"consent_email_pct":{"type":"number","title":"Consent Email Pct","description":"Percentage of guests with email marketing consent"},"consent_sms_pct":{"type":"number","title":"Consent Sms Pct","description":"Percentage of guests with SMS marketing consent"},"active_segments":{"type":"integer","title":"Active Segments","description":"Number of active guest segments"}},"type":"object","required":["total_profiles","new_last_30d","by_tier","consent_email_pct","consent_sms_pct","active_segments"],"title":"GuestCRM","description":"Guest profile and segmentation summary.","example":{"active_segments":5,"by_tier":{"gold":72,"silver":150,"standard":600,"vip":20},"consent_email_pct":68.5,"consent_sms_pct":42.3,"new_last_30d":45,"total_profiles":842}},"cbpulse__GuestListResponse":{"properties":{"guests":{"items":{"$ref":"#/components/schemas/cbpulse__GuestProfileResponse"},"type":"array","title":"Guests","description":"List of guest profiles"},"total":{"type":"integer","title":"Total","description":"Total number of matching guests"}},"type":"object","required":["guests","total"],"title":"GuestListResponse","description":"Paginated list of guest profiles."},"cbpulse__GuestProfileResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique guest profile identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this guest belongs to"},"first_name":{"type":"string","title":"First Name","description":"Guest first name"},"last_name":{"type":"string","title":"Last Name","description":"Guest last name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Guest email address"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Guest phone number"},"source":{"type":"string","title":"Source","description":"Integration source that created this profile","examples":["cloudbeds","webrezpro"]},"external_id":{"type":"string","title":"External Id","description":"External system identifier"},"stay_count":{"type":"integer","title":"Stay Count","description":"Total number of stays/reservations"},"total_spend":{"type":"number","title":"Total Spend","description":"Lifetime spend in USD"},"spend_tier":{"type":"string","title":"Spend Tier","description":"Computed spend tier: standard, silver, gold, or vip","examples":["standard","silver","gold","vip"]},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Guest tags (auto-computed + manual)"},"property_affinity":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Property Affinity","description":"Property visit frequency map"},"consent_email":{"type":"boolean","title":"Consent Email","description":"Email marketing consent"},"consent_sms":{"type":"boolean","title":"Consent Sms","description":"SMS marketing consent"},"consent_whatsapp":{"type":"boolean","title":"Consent Whatsapp","description":"WhatsApp marketing consent"},"consent_updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consent Updated At","description":"ISO-8601 timestamp of last consent update"},"last_stay_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Stay Date","description":"Most recent stay date (YYYY-MM-DD)"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-text staff notes"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the profile is active"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 creation timestamp"},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 last-update timestamp"}},"type":"object","required":["id","business_id","first_name","last_name","source","external_id","stay_count","total_spend","spend_tier","consent_email","consent_sms","consent_whatsapp","is_active","created_at","updated_at"],"title":"GuestProfileResponse","description":"Full guest profile with spend tier, tags, consent, and stay history.","example":{"business_id":"biz_thefred","consent_email":true,"consent_sms":false,"consent_updated_at":"2026-01-15T10:30:00+00:00","consent_whatsapp":false,"created_at":"2025-12-01T08:00:00+00:00","email":"jane@example.com","external_id":"CB-12345","first_name":"Jane","id":"g_1a2b3c4d","is_active":true,"last_name":"Smith","last_stay_date":"2026-02-01","phone":"+13405551234","source":"cloudbeds","spend_tier":"gold","stay_count":4,"tags":["returning","high_value","recent"],"total_spend":3200.0,"updated_at":"2026-02-15T14:30:00+00:00"}},"cbpulse__GuestRequestCreate":{"properties":{"request":{"type":"string","maxLength":2000,"minLength":1,"title":"Request"},"dept":{"type":"string","title":"Dept"},"room_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Id"},"room_label":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Room Label"},"guest_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Guest Name"},"priority":{"type":"string","title":"Priority","default":"normal"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["request","dept"],"title":"GuestRequestCreate"},"cbpulse__GuestRequestOut":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"room_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Id"},"room_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Label"},"guest_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Name"},"request":{"type":"string","title":"Request"},"dept":{"type":"string","title":"Dept"},"priority":{"type":"string","title":"Priority"},"status":{"type":"string","title":"Status"},"source":{"type":"string","title":"Source"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name"},"assigned_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To"},"assigned_to_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To Name"},"done_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Done At"},"done_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Done By Name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","business_id","room_id","room_label","guest_name","request","dept","priority","status","source","created_by","created_by_name","assigned_to","assigned_to_name","done_at","done_by_name","notes","created_at","updated_at"],"title":"GuestRequestOut"},"cbpulse__GuestRequestPatch":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"dept":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"},"priority":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Priority"},"assigned_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To"},"assigned_to_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To Name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"GuestRequestPatch"},"cbpulse__HRAlertResolveRequest":{"properties":{"resolution":{"type":"string","pattern":"^(confirmed|dismissed)$","title":"Resolution","description":"One of 'confirmed' or 'dismissed'."},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes","description":"Optional context for the audit trail."}},"type":"object","required":["resolution"],"title":"HRAlertResolveRequest"},"cbpulse__HRAlertResponse":{"properties":{"id":{"type":"string","title":"Id"},"employee_id":{"type":"string","title":"Employee Id"},"employee_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employee Name"},"business_id":{"type":"string","title":"Business Id"},"business_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Name"},"alert_type":{"type":"string","title":"Alert Type"},"status":{"type":"string","title":"Status"},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details"},"resolution_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Notes"},"resolved_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved By User Id"},"resolved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved At"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","employee_id","employee_name","business_id","business_name","alert_type","status","details","resolution_notes","resolved_by_user_id","resolved_at","created_at"],"title":"HRAlertResponse"},"cbpulse__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbpulse__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbpulse__HandbookAskCitation":{"properties":{"section_title":{"type":"string","title":"Section Title"},"source_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Source Page"}},"type":"object","required":["section_title"],"title":"HandbookAskCitation"},"cbpulse__HandbookAskRequest":{"properties":{"business_id":{"type":"string","minLength":1,"title":"Business Id"},"question":{"type":"string","minLength":1,"title":"Question"}},"type":"object","required":["business_id","question"],"title":"HandbookAskRequest"},"cbpulse__HandbookAskResponse":{"properties":{"answer":{"type":"string","title":"Answer"},"citations":{"items":{"$ref":"#/components/schemas/cbpulse__HandbookAskCitation"},"type":"array","title":"Citations"},"covered":{"type":"boolean","title":"Covered"},"business_id":{"type":"string","title":"Business Id"}},"type":"object","required":["answer","citations","covered","business_id"],"title":"HandbookAskResponse"},"cbpulse__HandbookCitation":{"properties":{"policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Id"},"policy_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Policy Version"},"section_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Section Title"},"section_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Section Text"},"source_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Source Page"}},"type":"object","required":["policy_id","policy_version","section_title","section_text","source_page"],"title":"HandbookCitation"},"cbpulse__HeadcountHistoryEntry":{"properties":{"date":{"type":"string","title":"Date"},"active_count":{"type":"integer","title":"Active Count"}},"type":"object","required":["date","active_count"],"title":"HeadcountHistoryEntry"},"cbpulse__HeadcountHistoryResponse":{"properties":{"businesses":{"items":{"$ref":"#/components/schemas/cbpulse__BusinessHeadcount"},"type":"array","title":"Businesses"}},"type":"object","required":["businesses"],"title":"HeadcountHistoryResponse"},"cbpulse__HeadcountInterval":{"type":"string","enum":["daily","weekly","monthly"],"title":"HeadcountInterval"},"cbpulse__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbpulse__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbpulse__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbpulse__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbpulse__HousekeepingResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Internal record ID","examples":["hk_01HXYZ"]},"room_id":{"type":"string","title":"Room Id","description":"Room identifier from PMS","examples":["rm_204"]},"room_name":{"type":"string","title":"Room Name","description":"Room display name","examples":["Room 204"]},"room_type_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Type Name","description":"Room type (e.g. Trunk Suite)","examples":["Ocean View King","Garden Suite","Trunk Suite"]},"room_condition":{"type":"string","title":"Room Condition","description":"Cleaning status: clean, dirty, inspected","examples":["clean","dirty","inspected"]},"is_occupied":{"type":"boolean","title":"Is Occupied","description":"Whether a guest is currently in the room","examples":[true]},"is_blocked":{"type":"boolean","title":"Is Blocked","description":"Whether the room is blocked/out of service","examples":[false]},"frontdesk_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Frontdesk Status","description":"Front desk view: stayover, turnover, arrival, departure, vacant","examples":["stayover","turnover","arrival","departure","vacant"]},"housekeeper_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Housekeeper Name","description":"Assigned housekeeper","examples":["Maria Santos"]},"do_not_disturb":{"type":"boolean","title":"Do Not Disturb","description":"Guest has set DND","examples":[false]},"arrival_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Arrival Date","description":"Current guest arrival (YYYY-MM-DD)","examples":["2026-02-13"]},"departure_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Departure Date","description":"Current guest departure (YYYY-MM-DD)","examples":["2026-02-17"]},"room_comments":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Comments","description":"Staff notes on room","examples":["Extra towels requested"]},"updated_at":{"type":"string","title":"Updated At","description":"Last updated timestamp (ISO-8601)","examples":["2026-02-15T09:00:00Z"]}},"type":"object","required":["id","room_id","room_name","room_condition","is_occupied","is_blocked","do_not_disturb","updated_at"],"title":"HousekeepingResponse","description":"Current housekeeping status for a single room.","example":{"arrival_date":"2026-02-13","departure_date":"2026-02-17","do_not_disturb":false,"frontdesk_status":"stayover","housekeeper_name":"Maria Santos","id":"hk_01HXYZ","is_blocked":false,"is_occupied":true,"room_comments":"Extra towels requested","room_condition":"dirty","room_id":"rm_204","room_name":"Room 204","room_type_name":"Ocean View King","updated_at":"2026-02-15T09:00:00Z"}},"cbpulse__InboxActivity":{"properties":{"open_conversations":{"type":"integer","title":"Open Conversations","description":"Number of conversations in 'open' status"},"total_unread":{"type":"integer","title":"Total Unread","description":"Sum of unread messages across all open conversations"},"by_channel":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Channel","description":"Open conversation counts grouped by channel"}},"type":"object","required":["open_conversations","total_unread","by_channel"],"title":"InboxActivity","description":"Unified inbox conversation summary.","example":{"by_channel":{"chat":1,"email":4,"meta_dm":2,"sms":1},"open_conversations":8,"total_unread":15}},"cbpulse__IncidentResponse":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"employee_id":{"type":"string","title":"Employee Id"},"reported_by":{"type":"string","title":"Reported By","description":"User ID who reported the incident"},"incident_date":{"type":"string","title":"Incident Date"},"description":{"type":"string","title":"Description"},"severity":{"type":"string","title":"Severity","description":"minor, moderate, major, critical"},"wda_ground":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wda Ground","description":"VI WDA §76 ground code (e.g. 76a1-76a9)"},"status":{"type":"string","title":"Status","description":"reported, investigating, statement_collected, resolved, escalated"},"employee_statement":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employee Statement"},"statement_collected_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Statement Collected At"},"resolution_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Notes"},"resolved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved At"},"resolved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved By"},"linked_writeup_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linked Writeup Id"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","business_id","employee_id","reported_by","incident_date","description","severity","status","created_at","updated_at"],"title":"IncidentResponse","description":"HR incident record."},"cbpulse__IntegrationStatusResponse":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Business this integration is configured for","examples":["biz_thefred"]},"source":{"type":"string","title":"Source","description":"Integration source key: clover, homebase, cloudbeds, webrezpro, or email","examples":["clover","cloudbeds","webrezpro","homebase"]},"display_name":{"type":"string","title":"Display Name","description":"Human-readable integration name, e.g. 'Clover POS', 'Cloudbeds PMS'","examples":["Clover POS","Cloudbeds PMS","WebRezPro PMS"]},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether this integration is currently active and will sync","examples":[true]},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config","description":"Integration-specific configuration (credentials redacted)","examples":[{"merchant_id":"XXXXXX1234"}]},"last_sync_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Sync At","description":"ISO-8601 timestamp of the last sync attempt","examples":["2026-02-15T08:30:00Z"]},"last_status":{"type":"string","title":"Last Status","description":"Result of the last sync: ok, failed, or never_run","examples":["ok","failed","never_run"]},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error","description":"Error message from the last failed sync, if any","examples":[null]},"records_synced":{"type":"integer","title":"Records Synced","description":"Number of records synced in the last successful run","default":0,"examples":[147]},"consecutive_failures":{"type":"integer","title":"Consecutive Failures","description":"Number of consecutive failed sync attempts","default":0,"examples":[0]}},"type":"object","required":["business_id","source","display_name","is_enabled","last_status"],"title":"IntegrationStatusResponse","description":"Status of a single external integration for a specific business.","example":{"business_id":"biz_thefred","config":{"merchant_id":"XXXXXX1234"},"consecutive_failures":0,"display_name":"Clover POS","is_enabled":true,"last_status":"ok","last_sync_at":"2026-02-15T08:30:00Z","records_synced":147,"source":"clover"}},"cbpulse__InventoryAlertCreate":{"properties":{"dept":{"type":"string","title":"Dept"},"item":{"type":"string","maxLength":200,"minLength":1,"title":"Item"},"severity":{"type":"string","title":"Severity","default":"low"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["dept","item"],"title":"InventoryAlertCreate"},"cbpulse__InventoryAlertOut":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"dept":{"type":"string","title":"Dept"},"item":{"type":"string","title":"Item"},"severity":{"type":"string","title":"Severity"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"type":"string","title":"Status"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name"},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","business_id","dept","item","severity","notes","status","created_by_name","resolved_at","created_at","updated_at"],"title":"InventoryAlertOut"},"cbpulse__InventoryAlertPatch":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"InventoryAlertPatch"},"cbpulse__InvoiceAuditDetail":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"business_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Name"},"vendor_name":{"type":"string","title":"Vendor Name"},"invoice_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Number"},"invoice_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Date"},"due_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Date"},"amount":{"type":"number","title":"Amount"},"status":{"type":"string","title":"Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"source_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Email"},"source_file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source File Path"},"ocr_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ocr Confidence"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"doc_type":{"type":"string","title":"Doc Type","default":"unknown"},"purchase_category":{"type":"string","title":"Purchase Category","default":"general"},"ocr_text_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ocr Text Preview"},"parsed_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Parsed Data"},"email_meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Email Meta"},"fb_order_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fb Order Id"},"match_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Match Status"},"approved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved By"},"approved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved At"},"rejected_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejected By"},"rejected_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejected At"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"}},"type":"object","required":["id","business_id","vendor_name","amount","status","created_at","updated_at"],"title":"InvoiceAuditDetail","description":"Full invoice with source extraction data for audit review."},"cbpulse__InvoiceCorrectionRequest":{"properties":{"vendor_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Name"},"invoice_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Number"},"invoice_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Date"},"due_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Date"},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount"},"business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id"},"doc_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Doc Type"},"purchase_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purchase Category"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"line_items":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Line Items"}},"type":"object","title":"InvoiceCorrectionRequest","description":"Fields that staff can adjust on an invoice."},"cbpulse__InvoiceResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique invoice record identifier","examples":["inv_x7y8z9"]},"business_id":{"type":"string","title":"Business Id","description":"Business this invoice belongs to","examples":["biz_thefred"]},"vendor_name":{"type":"string","title":"Vendor Name","description":"Vendor or supplier who issued the invoice","examples":["VI Restaurant Supply"]},"invoice_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Number","description":"Vendor's invoice number for reference","examples":["VRS-2026-00847"]},"invoice_date":{"type":"string","title":"Invoice Date","description":"Date on the invoice (YYYY-MM-DD)","examples":["2026-02-01"]},"due_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Date","description":"Payment due date (YYYY-MM-DD)","examples":["2026-03-01"]},"amount":{"type":"number","title":"Amount","description":"Invoice total amount","examples":[2340.75]},"status":{"type":"string","title":"Status","description":"Approval status: pending, approved, or rejected","examples":["pending"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-text notes about the invoice","examples":["Monthly kitchen equipment maintenance parts"]},"approved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved By","description":"User ID who approved the invoice","examples":["user_admin_01"]},"approved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved At","description":"ISO-8601 timestamp when the invoice was approved","examples":["2026-02-05T11:00:00+00:00"]},"rejected_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejected By","description":"User ID who rejected the invoice"},"rejected_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejected At","description":"ISO-8601 timestamp when the invoice was rejected"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason","description":"Reason provided when the invoice was rejected","examples":["Amount does not match purchase order"]},"source_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Email","description":"Email address the invoice was received from, if ingested via IMAP","examples":["invoices@virestaurantsupply.com"]},"source_file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source File Path","description":"Path to the original invoice file in CBFiles storage","examples":["/invoices/2026/02/VRS-2026-00847.pdf"]},"ocr_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ocr Confidence","description":"CBAI OCR confidence score (0.0-1.0) for auto-parsed invoices","examples":[0.94]},"order_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order Id","description":"Linked purchase order ID, if this invoice matches an order","examples":["ord_a1b2c3d4"]},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the record was created","examples":["2026-02-03T10:15:00+00:00"]},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last update","examples":["2026-02-03T10:15:00+00:00"]}},"type":"object","required":["id","business_id","vendor_name","invoice_date","amount","status","created_at","updated_at"],"title":"InvoiceResponse","description":"Full invoice record with approval/rejection state and OCR metadata.","example":{"amount":2340.75,"business_id":"biz_thefred","created_at":"2026-02-03T10:15:00+00:00","due_date":"2026-03-01","id":"inv_x7y8z9","invoice_date":"2026-02-01","invoice_number":"VRS-2026-00847","notes":"Monthly kitchen equipment maintenance parts","ocr_confidence":0.94,"order_id":"ord_a1b2c3d4","source_email":"invoices@virestaurantsupply.com","source_file_path":"/invoices/2026/02/VRS-2026-00847.pdf","status":"pending","updated_at":"2026-02-03T10:15:00+00:00","vendor_name":"VI Restaurant Supply"}},"cbpulse__InvoiceStats":{"properties":{"total":{"type":"integer","title":"Total","default":0},"by_status":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Status"},"by_business":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"By Business"},"total_pending_amount":{"type":"number","title":"Total Pending Amount","default":0},"total_approved_amount":{"type":"number","title":"Total Approved Amount","default":0}},"type":"object","title":"InvoiceStats","description":"Counts per status + totals for dashboard overview."},"cbpulse__ItemPriorPeriod":{"properties":{"units_sold":{"type":"integer","title":"Units Sold","description":"Units sold in the prior period"},"revenue":{"type":"number","title":"Revenue","description":"Revenue in the prior period"},"units_change_pct":{"type":"number","title":"Units Change Pct","description":"Units change vs prior period (%)"},"revenue_change_pct":{"type":"number","title":"Revenue Change Pct","description":"Revenue change vs prior period (%)"}},"type":"object","required":["units_sold","revenue","units_change_pct","revenue_change_pct"],"title":"ItemPriorPeriod"},"cbpulse__ItemSalesDetail":{"properties":{"name":{"type":"string","title":"Name","description":"Item name"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Item category"},"units_sold":{"type":"integer","title":"Units Sold","description":"Total units sold in period"},"revenue":{"type":"number","title":"Revenue","description":"Total revenue for this item"},"avg_price":{"type":"number","title":"Avg Price","description":"Average unit price"},"pct_of_total":{"type":"number","title":"Pct Of Total","description":"Percentage of total revenue"},"prior_period":{"anyOf":[{"$ref":"#/components/schemas/cbpulse__ItemPriorPeriod"},{"type":"null"}],"description":"Prior period comparison data"}},"type":"object","required":["name","units_sold","revenue","avg_price","pct_of_total"],"title":"ItemSalesDetail"},"cbpulse__ItemSalesResponse":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Business identifier"},"business_name":{"type":"string","title":"Business Name","description":"Business display name"},"period":{"$ref":"#/components/schemas/cbpulse__PeriodRange","description":"Queried date range"},"total_transactions":{"type":"integer","title":"Total Transactions","description":"Distinct order count in period"},"total_revenue":{"type":"number","title":"Total Revenue","description":"Sum of item revenue in period"},"avg_check":{"type":"number","title":"Avg Check","description":"Average revenue per transaction"},"items":{"items":{"$ref":"#/components/schemas/cbpulse__ItemSalesDetail"},"type":"array","title":"Items","description":"Per-item sales breakdown"},"categories":{"items":{"$ref":"#/components/schemas/cbpulse__CategoryBreakdown"},"type":"array","title":"Categories","description":"Per-category revenue mix"}},"type":"object","required":["business_id","business_name","period","total_transactions","total_revenue","avg_check","items","categories"],"title":"ItemSalesResponse","description":"Aggregated item-level sales data for a business and date range.","example":{"avg_check":40.38,"business_id":"biz_thefred","business_name":"The Fred","categories":[{"name":"Beverage","pct_of_total":53.2,"revenue":18200.0}],"items":[{"avg_price":20.0,"category":"Beverage","name":"Rum Punch","pct_of_total":13.7,"revenue":4680.0,"units_sold":234}],"period":{"end":"2026-03-23","start":"2026-03-17"},"total_revenue":34200.0,"total_transactions":847}},"cbpulse__JobResult":{"properties":{"job":{"type":"string","title":"Job"},"status":{"type":"string","title":"Status"},"records":{"type":"integer","title":"Records","default":0},"duration":{"type":"number","title":"Duration","default":0.0},"details":{"type":"string","title":"Details","default":""}},"type":"object","required":["job","status"],"title":"JobResult"},"cbpulse__JobRunResponse":{"properties":{"job_name":{"type":"string","title":"Job Name","description":"Name of the job that was executed","examples":["sync_clover","check_alerts","generate_daily_summary"]},"status":{"type":"string","title":"Status","description":"Execution result: completed or failed","examples":["completed","failed"]},"records_processed":{"type":"integer","title":"Records Processed","description":"Number of records processed during execution","examples":[42]},"duration_seconds":{"type":"number","title":"Duration Seconds","description":"Wall-clock execution time in seconds","examples":[1.234]},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Error details if the job failed","examples":[null]}},"type":"object","required":["job_name","status","records_processed","duration_seconds"],"title":"JobRunResponse","description":"Result of manually running a single background job.","example":{"duration_seconds":1.234,"job_name":"sync_clover","records_processed":42,"status":"completed"}},"cbpulse__JobScheduleResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique job schedule identifier","examples":["job_01HXYZ"]},"job_name":{"type":"string","title":"Job Name","description":"Job identifier, e.g. 'sync_clover', 'check_alerts', 'aggregate_financials'","examples":["sync_clover","check_alerts","aggregate_financials"]},"job_type":{"type":"string","title":"Job Type","description":"Job category: sync, alert, financial, or summary","examples":["sync","alert","financial","summary"]},"interval_minutes":{"type":"integer","title":"Interval Minutes","description":"Minutes between scheduled runs","examples":[5,15,60,1440]},"last_run_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Run At","description":"ISO-8601 timestamp of the last execution","examples":["2026-02-15T08:25:00Z"]},"next_run_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Run At","description":"ISO-8601 timestamp of the next scheduled execution","examples":["2026-02-15T08:30:00Z"]},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether this job is active and will run on schedule","examples":[true]},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config","description":"Job-specific configuration parameters","examples":[null]}},"type":"object","required":["id","job_name","job_type","interval_minutes","is_enabled"],"title":"JobScheduleResponse","description":"Configuration and state of a scheduled background job.","example":{"id":"job_01HXYZ","interval_minutes":5,"is_enabled":true,"job_name":"sync_clover","job_type":"sync","last_run_at":"2026-02-15T08:25:00Z","next_run_at":"2026-02-15T08:30:00Z"}},"cbpulse__LaborCostResponse":{"properties":{"business_id":{"type":"string","title":"Business Id"},"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"},"accumulated_cost":{"type":"number","title":"Accumulated Cost","description":"Total labor cost so far in USD"},"accumulated_hours":{"type":"number","title":"Accumulated Hours"},"days_elapsed":{"type":"integer","title":"Days Elapsed"},"days_remaining":{"type":"integer","title":"Days Remaining"},"projected_total":{"type":"number","title":"Projected Total","description":"Projected total cost at period end based on daily run rate"}},"type":"object","required":["business_id","period_start","period_end","accumulated_cost","accumulated_hours","days_elapsed","days_remaining","projected_total"],"title":"LaborCostResponse","description":"Accumulated labor cost for current pay period with projection."},"cbpulse__LastClockInResponse":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"last_clock_in_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Clock In Date","description":"Max ``shifts.shift_date`` (ISO ``YYYY-MM-DD``) for this employee, scoped to their ``business_id``. NULL when no shifts are on file so the wizard can fall back to manual entry."},"clock_in_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Clock In At","description":"Clock-in timestamp of that shift (actual_start, else scheduled_start)."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Labor source of the latest shift (e.g. ``homebase``)."}},"type":"object","required":["employee_id"],"title":"LastClockInResponse"},"cbpulse__LineItem":{"properties":{"description":{"type":"string","title":"Description"},"qty":{"type":"number","title":"Qty"},"price":{"type":"number","title":"Price"}},"type":"object","required":["description","qty","price"],"title":"LineItem"},"cbpulse__LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"User's email address used for authentication","examples":["gm@thefred.vi","admin@thesaint.vi","manager@lovango.vi"]},"password":{"type":"string","title":"Password","description":"User's password (plaintext, validated server-side)","examples":["s3cur3Pa$$w0rd","MyStr0ngP@ss!"]}},"type":"object","required":["email","password"],"title":"LoginRequest","description":"Credentials for email/password authentication."},"cbpulse__ManagerLogResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique manager log entry identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this log entry belongs to","examples":["biz_thefred","biz_thesaint","biz_lovango"]},"entry_type":{"type":"string","title":"Entry Type","description":"Log type: shift_note, incident, observation, or handoff","examples":["shift_note","incident","observation","handoff"]},"title":{"type":"string","title":"Title","description":"Short summary of the log entry","examples":["Friday dinner rush — short-staffed","Guest complaint: room 204 AC failure","Handoff to evening manager"]},"body":{"type":"string","title":"Body","description":"Full narrative content of the log entry"},"tags":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Tags","description":"Categorization tags, e.g. ['staffing', 'guest-complaint']"},"author_id":{"type":"string","title":"Author Id","description":"User ID of the manager who wrote this entry"},"author_email":{"type":"string","title":"Author Email","description":"Email of the manager who wrote this entry","examples":["maria@thefred.vi","james@thesaint.vi"]},"shift_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shift Date","description":"Date of the shift this entry covers (YYYY-MM-DD)","examples":["2026-02-14","2026-02-15"]},"is_flagged":{"type":"boolean","title":"Is Flagged","description":"If true, entry is flagged for follow-up or escalation"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the entry was created"},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last update"}},"type":"object","required":["id","business_id","entry_type","title","body","author_id","author_email","is_flagged","created_at","updated_at"],"title":"ManagerLogResponse","description":"Manager shift log entry with type, tags, and flag status.","example":{"author_email":"maria@thefred.vi","author_id":"usr_maria_gomez","body":"Only 3 servers for 45 covers. Called in backup from lunch team. Service recovered by 8pm but early seating had 20-min ticket times.","business_id":"biz_thefred","created_at":"2026-02-14T23:15:00+00:00","entry_type":"shift_note","id":"mlog_7d4e1f2a","is_flagged":true,"shift_date":"2026-02-14","tags":["staffing","service-delay"],"title":"Friday dinner rush — short-staffed","updated_at":"2026-02-14T23:15:00+00:00"}},"cbpulse__ManualEmployeeRequest":{"properties":{"business_id":{"type":"string","maxLength":36,"minLength":36,"title":"Business Id"},"first_name":{"type":"string","maxLength":100,"minLength":1,"title":"First Name"},"last_name":{"type":"string","maxLength":100,"minLength":1,"title":"Last Name"},"email":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string","maxLength":30},{"type":"null"}],"title":"Phone"},"position":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Position"},"department":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Department"},"hourly_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Hourly Rate"},"employment_type":{"type":"string","pattern":"^(hourly|salary|contractor)$","title":"Employment Type","default":"hourly"},"hire_date":{"anyOf":[{"type":"string","maxLength":10,"minLength":10},{"type":"null"}],"title":"Hire Date"}},"type":"object","required":["business_id","first_name","last_name"],"title":"ManualEmployeeRequest"},"cbpulse__ManualEmployeeResponse":{"properties":{"id":{"type":"string","title":"Id"},"source":{"type":"string","title":"Source"},"external_id":{"type":"string","title":"External Id"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"business_id":{"type":"string","title":"Business Id"},"hire_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hire Date"},"status":{"type":"string","title":"Status"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","source","external_id","first_name","last_name","business_id","hire_date","status","is_active","created_at"],"title":"ManualEmployeeResponse"},"cbpulse__MarkManualResponse":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"previous_source":{"type":"string","title":"Previous Source"},"new_source":{"type":"string","title":"New Source"},"external_id":{"type":"string","title":"External Id"}},"type":"object","required":["employee_id","previous_source","new_source","external_id"],"title":"MarkManualResponse"},"cbpulse__MarketingOverviewResponse":{"properties":{"generated_at":{"type":"string","title":"Generated At","description":"ISO-8601 timestamp when this response was generated"},"business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Business Id","description":"Business ID if scoped to a single business, null for portfolio-wide"},"signals":{"$ref":"#/components/schemas/cbpulse__SignalIntelligence","description":"Marketing signal intelligence summary"},"campaigns":{"$ref":"#/components/schemas/cbpulse__CampaignPipeline","description":"Campaign pipeline and delivery metrics"},"guests":{"$ref":"#/components/schemas/cbpulse__GuestCRM","description":"Guest CRM and segmentation summary"},"reviews":{"$ref":"#/components/schemas/cbpulse__ReviewReputation","description":"Review reputation summary"},"inbox":{"$ref":"#/components/schemas/cbpulse__InboxActivity","description":"Unified inbox activity summary"},"brand_status":{"items":{"$ref":"#/components/schemas/cbpulse__BusinessBrandStatus"},"type":"array","title":"Brand Status","description":"Per-business brand profile readiness"}},"type":"object","required":["generated_at","signals","campaigns","guests","reviews","inbox","brand_status"],"title":"MarketingOverviewResponse","description":"Composite marketing dashboard payload aggregating all 6 domains.","example":{"brand_status":[],"campaigns":{},"generated_at":"2026-02-15T14:30:00+00:00","guests":{},"inbox":{},"reviews":{},"signals":{}}},"cbpulse__MeResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique user identifier"},"email":{"type":"string","title":"Email","description":"User's email address"},"first_name":{"type":"string","title":"First Name","description":"User's first name"},"last_name":{"type":"string","title":"Last Name","description":"User's last name"},"role":{"type":"string","title":"Role","description":"RBAC role: sysop, admin, manager, or employee"},"business_ids":{"items":{"type":"string"},"type":"array","title":"Business Ids","description":"List of business IDs the user has access to"},"functional_roles":{"items":{"type":"string"},"type":"array","title":"Functional Roles","description":"Functional roles controlling module visibility"},"landing_page":{"type":"string","title":"Landing Page","description":"Role-based landing page path for the frontend","default":"/"}},"type":"object","required":["id","email","first_name","last_name","role","business_ids"],"title":"MeResponse","description":"Current user profile with role and business assignments.","example":{"business_ids":["biz_thefred","biz_lovango"],"email":"gm@thefred.vi","first_name":"Maria","functional_roles":["finance","operations"],"id":"usr_abc123","landing_page":"/invoices","last_name":"Santos","role":"manager"}},"cbpulse__MergeCandidatesResponse":{"properties":{"total_groups":{"type":"integer","title":"Total Groups"},"total_rows":{"type":"integer","title":"Total Rows"},"groups":{"items":{"$ref":"#/components/schemas/cbpulse__CollisionGroupResponse"},"type":"array","title":"Groups"}},"type":"object","required":["total_groups","total_rows","groups"],"title":"MergeCandidatesResponse"},"cbpulse__MergeRequest":{"properties":{"canonical_id":{"type":"string","title":"Canonical Id","description":"Row to keep"},"duplicate_id":{"type":"string","title":"Duplicate Id","description":"Row to merge into the canonical"}},"type":"object","required":["canonical_id","duplicate_id"],"title":"MergeRequest"},"cbpulse__MergeResponse":{"properties":{"canonical_id":{"type":"string","title":"Canonical Id"},"duplicate_id":{"type":"string","title":"Duplicate Id"},"repointed":{"additionalProperties":{"type":"integer"},"type":"object","title":"Repointed","description":"Per-table count of FK rows repointed from duplicate → canonical"},"action_log_target_updates":{"type":"integer","title":"Action Log Target Updates"},"hr_audit_log_entity_updates":{"type":"integer","title":"Hr Audit Log Entity Updates"}},"type":"object","required":["canonical_id","duplicate_id","repointed","action_log_target_updates","hr_audit_log_entity_updates"],"title":"MergeResponse"},"cbpulse__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbpulse__MessageResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique message identifier"},"conversation_id":{"type":"string","title":"Conversation Id","description":"Parent conversation ID"},"business_id":{"type":"string","title":"Business Id","description":"Business this message belongs to"},"direction":{"type":"string","title":"Direction","description":"Message direction: inbound or outbound"},"sender_type":{"type":"string","title":"Sender Type","description":"Sender type: guest, staff, system"},"sender_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Id","description":"Sender user/external ID"},"sender_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sender Name","description":"Sender display name"},"body":{"type":"string","title":"Body","description":"Message body text"},"content_type":{"type":"string","title":"Content Type","description":"Content type: text, html, image"},"attachments":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Attachments","description":"List of attachment objects"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"External platform message ID"},"ai_suggestion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Suggestion","description":"AI-generated reply suggestion"},"is_read":{"type":"boolean","title":"Is Read","description":"Whether the message has been read"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the message was created"},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last update"}},"type":"object","required":["id","conversation_id","business_id","direction","sender_type","body","content_type","is_read","created_at","updated_at"],"title":"MessageResponse","description":"Single message within an inbox conversation."},"cbpulse__ModeRequest":{"properties":{"topic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Topic","description":"Topic / target (mode-specific)"},"article_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Article Type","description":"Optional explicit article type (e.g. 'Beach feature', 'Restaurant review', 'Guide'). When set it's a directive to the writer; when omitted the writer chooses based on the research brief. (cbpulse#350)"},"article_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Article Kind","description":"Which QA prompt profile judges the result: 'news' or 'feature' (cbpulse#577). Gates 6, 7 and 10 penalise inline citation, which is correct for a beach feature and wrong for a sourced news piece — so a correctly-sourced news article could never clear QA under the feature profile. Set 'news' when you chose a news topic. Omitted (the default) keeps whatever the pipeline stamped on the draft, which is 'feature' unless the planner decided otherwise."},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","description":"LLM provider override: ollama (default) | anthropic"}},"type":"object","title":"ModeRequest","description":"Common body for a mode trigger. Provider is overridable per-request."},"cbpulse__MonitoringJobCreate":{"properties":{"name":{"type":"string","minLength":1,"title":"Name","description":"Human-readable job name"},"sources":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Sources","description":"Source URLs to sweep via cbintel crawl"},"schedule_cron":{"type":"string","minLength":1,"title":"Schedule Cron","description":"5-field cron expression (USVI time on activation)"},"run_graph_template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Graph Template","description":"Optional cbintel run_graph template (runs a graph instead of crawl)"}},"type":"object","required":["name","sources","schedule_cron"],"title":"MonitoringJobCreate","description":"Body to configure a monitoring job (created as pending_approval)."},"cbpulse__MonitoringJobResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Monitoring job id"},"name":{"type":"string","title":"Name","description":"Job name"},"sources":{"items":{"type":"string"},"type":"array","title":"Sources","description":"Source URLs swept"},"schedule_cron":{"type":"string","title":"Schedule Cron","description":"5-field cron expression"},"run_graph_template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Graph Template","description":"run_graph template"},"status":{"type":"string","title":"Status","description":"pending_approval | active | inactive"},"cbcron_job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cbcron Job Id","description":"cbcron job id (set when active)"},"last_run_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Run At","description":"ISO last sweep timestamp"},"last_run_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Run Status","description":"Outcome of the last sweep (ok | partial | failed)"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"ISO created timestamp"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At","description":"ISO updated timestamp"}},"type":"object","required":["id","name","sources","schedule_cron","status"],"title":"MonitoringJobResponse","description":"A configured monitoring job + its status."},"cbpulse__MonitoringSweepResult":{"properties":{"id":{"type":"string","title":"Id","description":"Monitoring job id"},"status":{"type":"string","title":"Status","description":"ok | partial | failed | skipped"},"job_ids":{"items":{"type":"string"},"type":"array","title":"Job Ids","description":"cbintel job ids submitted by this sweep"},"detail":{"type":"string","title":"Detail","description":"Human-readable sweep outcome","default":""}},"type":"object","required":["id","status"],"title":"MonitoringSweepResult","description":"Result of running one sweep (cron-triggered)."},"cbpulse__NOSJArticleDetail":{"properties":{"id":{"type":"string","title":"Id","description":"Draft id"},"topic":{"type":"string","title":"Topic","description":"Article topic"},"mode":{"type":"string","title":"Mode","description":"Mode that produced it (single, refresh, ...)"},"status":{"type":"string","title":"Status","description":"draft | needs_revision | rejected | approved"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Article title"},"llm_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Provider","description":"LLM provider used (ollama | anthropic)"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"ISO created timestamp"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At","description":"ISO updated timestamp"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Markdown article body"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Writer metadata block"},"research_brief":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Research Brief","description":"Structured research brief"},"qa_report":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Qa Report","description":"13-gate QA report"},"qa_report_stale":{"type":"boolean","title":"Qa Report Stale","description":"True when `qa_report` was computed against different prose than the `body` returned here (cbpulse#584). The stored report is not re-run on edit, so gate verdicts can quote deleted text -- the published article carried gate detail for a body with 6 inline links against one with 3. Reports written before #584 carry no provenance and are reported stale rather than current.","default":false}},"type":"object","required":["id","topic","mode","status"],"title":"NOSJArticleDetail","description":"A persisted NOSJ draft, full view (body + structured brief/QA)."},"cbpulse__NOSJArticleSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Draft id"},"topic":{"type":"string","title":"Topic","description":"Article topic"},"mode":{"type":"string","title":"Mode","description":"Mode that produced it (single, refresh, ...)"},"status":{"type":"string","title":"Status","description":"draft | needs_revision | rejected | approved"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Article title"},"llm_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Provider","description":"LLM provider used (ollama | anthropic)"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"ISO created timestamp"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At","description":"ISO updated timestamp"}},"type":"object","required":["id","topic","mode","status"],"title":"NOSJArticleSummary","description":"A persisted NOSJ draft, list view (no full body)."},"cbpulse__NOSJGateResult":{"properties":{"gate_id":{"type":"integer","title":"Gate Id","description":"Gate number 1-13"},"name":{"type":"string","title":"Name","description":"Human-readable gate name"},"verdict":{"type":"string","title":"Verdict","description":"pass | warn | fail"},"detail":{"type":"string","title":"Detail","description":"What was found / why it warned/failed","default":""}},"type":"object","required":["gate_id","name","verdict"],"title":"NOSJGateResult","description":"One QA gate result, surfaced in the single-article response."},"cbpulse__NOSJJobChild":{"properties":{"id":{"type":"string","title":"Id","description":"Child job id"},"status":{"type":"string","title":"Status","description":"pending | running | done | failed"},"topic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Topic","description":"Article topic"},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode","description":"single | refresh"},"opening_pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Opening Pattern","description":"Assigned NOSJ opening pattern"},"article_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Article Id","description":"Produced draft id (set when done)"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Failure message (when failed)"}},"type":"object","required":["id","status"],"title":"NOSJJobChild","description":"A child article job in a batch status view."},"cbpulse__NOSJJobStatus":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Parent batch job id"},"status":{"type":"string","title":"Status","description":"pending | running | done | failed"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"ISO created timestamp"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At","description":"ISO updated timestamp"},"children":{"items":{"$ref":"#/components/schemas/cbpulse__NOSJJobChild"},"type":"array","title":"Children","description":"Per-article child jobs + their article ids"}},"type":"object","required":["job_id","status"],"title":"NOSJJobStatus","description":"Batch job status: the parent plus every child (pollable result)."},"cbpulse__NOSJRefreshResult":{"properties":{"id":{"type":"string","title":"Id","description":"Persisted draft id"},"topic":{"type":"string","title":"Topic","description":"Article topic"},"mode":{"type":"string","title":"Mode","description":"single"},"status":{"type":"string","title":"Status","description":"draft | needs_revision | rejected"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Article title"},"llm_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Provider","description":"LLM provider used (ollama | anthropic)"},"verdict":{"type":"string","title":"Verdict","description":"QA verdict: APPROVED | NEEDS_REVISION | REJECTED"},"gates":{"items":{"$ref":"#/components/schemas/cbpulse__NOSJGateResult"},"type":"array","title":"Gates","description":"Per-gate QA results (all 13 gates)"},"source_post_id":{"type":"integer","title":"Source Post Id","description":"WP post id the refresh was based on"},"diff_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Diff Summary","description":"Markdown diff summary (sources + factual changes)"}},"type":"object","required":["id","topic","mode","status","verdict","source_post_id"],"title":"NOSJRefreshResult","description":"Result of refreshing an article (Mode 2).\n\nExtends the single-article result with the refresh source post id and the\ndiff summary (markdown: source-set diff + a short prose change summary). The\narticle is ALWAYS a DRAFT for CJ — never published."},"cbpulse__NOSJSingleResult":{"properties":{"id":{"type":"string","title":"Id","description":"Persisted draft id"},"topic":{"type":"string","title":"Topic","description":"Article topic"},"mode":{"type":"string","title":"Mode","description":"single"},"status":{"type":"string","title":"Status","description":"draft | needs_revision | rejected"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Article title"},"llm_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Provider","description":"LLM provider used (ollama | anthropic)"},"verdict":{"type":"string","title":"Verdict","description":"QA verdict: APPROVED | NEEDS_REVISION | REJECTED"},"gates":{"items":{"$ref":"#/components/schemas/cbpulse__NOSJGateResult"},"type":"array","title":"Gates","description":"Per-gate QA results (all 13 gates)"}},"type":"object","required":["id","topic","mode","status","verdict"],"title":"NOSJSingleResult","description":"Result of producing a single article (Mode 1).\n\nThe article is ALWAYS a draft for CJ — ``status`` is one of draft /\nneeds_revision / rejected (never published/approved). ``verdict`` is the raw\nQA outcome; ``gates`` is the per-gate breakdown so CJ sees exactly what\npassed, warned, or failed."},"cbpulse__NotificationListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbpulse__NotificationResponse"},"type":"array","title":"Items","description":"Notification items"},"unread_count":{"type":"integer","title":"Unread Count","description":"Total unread notifications for this user"}},"type":"object","required":["items","unread_count"],"title":"NotificationListResponse","description":"Paginated notification list with unread count.","example":{"items":[{"body":"Lunch shift labor cost reached 38.2% of revenue.","category":"alert","created_at":"2026-02-15T14:30:00+00:00","id":"ntf_a1b2c3d4","is_read":false,"link":"/alerts/alt_8f3a2b1c","severity":"warning","source_id":"alt_8f3a2b1c","source_type":"alert","title":"Labor spike at The Fred"}],"unread_count":3}},"cbpulse__NotificationResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique notification identifier"},"category":{"type":"string","title":"Category","description":"Notification category: alert, finance, maintenance, system, digest","examples":["alert","finance","maintenance"]},"severity":{"type":"string","title":"Severity","description":"Severity level: critical, warning, info","examples":["critical","warning","info"]},"title":{"type":"string","title":"Title","description":"Short notification title","examples":["Labor spike at The Fred","Low occupancy at The Saint","Overdue invoice from Sysco"]},"body":{"type":"string","title":"Body","description":"Notification body text","examples":["Lunch shift labor cost reached 38.2% of revenue, exceeding the 35% threshold.","The Saint occupancy dropped below 40% for 3 consecutive days."]},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link","description":"Deep-link to the relevant page","examples":["/alerts/alt_8f3a2b1c","/invoices/inv_sysco_0215"]},"source_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Type","description":"Source type: alert, invoice, deadline","examples":["alert","invoice","deadline"]},"source_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Id","description":"Source record ID","examples":["alt_8f3a2b1c","inv_sysco_0215"]},"is_read":{"type":"boolean","title":"Is Read","description":"Whether the notification has been read"},"read_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Read At","description":"ISO-8601 timestamp when read"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 creation timestamp"}},"type":"object","required":["id","category","severity","title","body","is_read","created_at"],"title":"NotificationResponse","description":"Single notification item.","example":{"body":"Lunch shift labor cost reached 38.2% of revenue, exceeding the 35% threshold.","category":"alert","created_at":"2026-02-15T14:30:00+00:00","id":"ntf_a1b2c3d4","is_read":false,"link":"/alerts/alt_8f3a2b1c","severity":"warning","source_id":"alt_8f3a2b1c","source_type":"alert","title":"Labor spike at The Fred"}},"cbpulse__ObligationDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"obligation_type":{"type":"string","title":"Obligation Type"},"category":{"type":"string","title":"Category"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"condition_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Condition Number"},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount"},"recipient":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient"},"filed_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed With"},"also_filed_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Also Filed With"},"proof_types_required":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Proof Types Required"},"recurrence_rule":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Recurrence Rule"},"is_auto_generatable":{"type":"boolean","title":"Is Auto Generatable"},"legal_source_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legal Source Date"},"filings":{"items":{"$ref":"#/components/schemas/cbpulse__FilingResponse"},"type":"array","title":"Filings"}},"type":"object","required":["id","obligation_type","category","title","description","condition_number","amount","recipient","filed_with","also_filed_with","proof_types_required","recurrence_rule","is_auto_generatable","legal_source_date","filings"],"title":"ObligationDetailResponse"},"cbpulse__ObligationResponse":{"properties":{"id":{"type":"string","title":"Id"},"obligation_type":{"type":"string","title":"Obligation Type"},"category":{"type":"string","title":"Category"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"condition_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Condition Number"},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount"},"recipient":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient"},"filed_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filed With"},"also_filed_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Also Filed With"},"proof_types_required":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Proof Types Required"},"recurrence_rule":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Recurrence Rule"},"is_auto_generatable":{"type":"boolean","title":"Is Auto Generatable"},"legal_source_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legal Source Date"}},"type":"object","required":["id","obligation_type","category","title","description","condition_number","amount","recipient","filed_with","also_filed_with","proof_types_required","recurrence_rule","is_auto_generatable","legal_source_date"],"title":"ObligationResponse"},"cbpulse__OccupancyResponse":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Business this metric belongs to","examples":["biz_thesaint","biz_thefred"]},"report_date":{"type":"string","title":"Report Date","description":"Date of this snapshot (YYYY-MM-DD)","examples":["2026-02-15"]},"source":{"type":"string","title":"Source","description":"Data source (cloudbeds, webrezpro)","examples":["cloudbeds","webrezpro"]},"occupancy_rate":{"type":"number","title":"Occupancy Rate","description":"Occupancy rate (0.0 to 1.0)","examples":[0.875]},"occupancy_percent":{"type":"number","title":"Occupancy Percent","description":"Occupancy as percentage (0-100)","examples":[87.5]},"adr":{"type":"number","title":"Adr","description":"Average Daily Rate in USD","examples":[395.0]},"revpar":{"type":"number","title":"Revpar","description":"Revenue Per Available Room in USD","examples":[345.63]},"rooms_sold":{"type":"integer","title":"Rooms Sold","description":"Number of rooms occupied","examples":[7]},"rooms_available":{"type":"integer","title":"Rooms Available","description":"Total room inventory","examples":[8,25]},"bookings_30_day":{"type":"integer","title":"Bookings 30 Day","description":"Confirmed arrivals in next 30 days","examples":[22]},"bookings_90_day":{"type":"integer","title":"Bookings 90 Day","description":"Confirmed arrivals in next 90 days","examples":[58]},"updated_at":{"type":"string","title":"Updated At","description":"Last updated timestamp (ISO-8601)","examples":["2026-02-15T08:30:00Z"]}},"type":"object","required":["business_id","report_date","source","occupancy_rate","occupancy_percent","adr","revpar","rooms_sold","rooms_available","bookings_30_day","bookings_90_day","updated_at"],"title":"OccupancyResponse","description":"Hotel performance metrics for a business on a given date.","example":{"adr":395.0,"bookings_30_day":22,"bookings_90_day":58,"business_id":"biz_thesaint","occupancy_percent":87.5,"occupancy_rate":0.875,"report_date":"2026-02-15","revpar":345.63,"rooms_available":8,"rooms_sold":7,"source":"cloudbeds","updated_at":"2026-02-15T08:30:00Z"}},"cbpulse__OnboardingCreate":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"business_id":{"type":"string","title":"Business Id"},"template_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Id"},"initiated_by":{"type":"string","title":"Initiated By"},"send_now":{"type":"boolean","title":"Send Now","default":false}},"type":"object","required":["employee_id","business_id","initiated_by"],"title":"OnboardingCreate"},"cbpulse__OnboardingDocumentURLResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"source":{"type":"string","title":"Source"}},"type":"object","required":["url","source"],"title":"OnboardingDocumentURLResponse","description":"Fresh time-limited PDF URL for an onboarding packet. cbpulse#488.\n\nMirrors WriteUpDocumentURLResponse (cbpulse#428) with one extra source:\n``legacy`` for airtable-backfill rows that have a stored ``document_url``\nbut no BoldSign envelope (cbpulse#382)."},"cbpulse__OnboardingQuickSendRequest":{"properties":{"employee_id":{"type":"string","title":"Employee Id","description":"Employee whose default-property onboarding packet to dispatch."}},"type":"object","required":["employee_id"],"title":"OnboardingQuickSendRequest"},"cbpulse__OnboardingRemindRequest":{"properties":{"message":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Message","description":"Optional override for the BoldSign reminder email body."}},"type":"object","title":"OnboardingRemindRequest"},"cbpulse__OnboardingRemindResponse":{"properties":{"reminded":{"type":"boolean","title":"Reminded"},"boldsign_doc_id":{"type":"string","title":"Boldsign Doc Id"},"already_complete":{"type":"boolean","title":"Already Complete","default":false},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["reminded","boldsign_doc_id"],"title":"OnboardingRemindResponse"},"cbpulse__OnboardingResponse":{"properties":{"id":{"type":"string","title":"Id"},"employee_id":{"type":"string","title":"Employee Id"},"business_id":{"type":"string","title":"Business Id"},"template_id":{"type":"string","title":"Template Id"},"boldsign_doc_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Boldsign Doc Id"},"status":{"type":"string","title":"Status"},"initiated_by":{"type":"string","title":"Initiated By"},"initiated_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Initiated By User Id"},"created_at":{"type":"string","title":"Created At"},"sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent At"},"signed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed At"}},"type":"object","required":["id","employee_id","business_id","template_id","boldsign_doc_id","status","initiated_by","initiated_by_user_id","created_at","sent_at","signed_at"],"title":"OnboardingResponse"},"cbpulse__OrderCreateResponse":{"properties":{"id":{"type":"string","title":"Id"},"total":{"type":"number","title":"Total"},"month":{"type":"string","title":"Month"}},"type":"object","required":["id","total","month"],"title":"OrderCreateResponse"},"cbpulse__OrderDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"businessId":{"type":"string","title":"Businessid"},"date":{"type":"string","title":"Date"},"vendor":{"type":"string","title":"Vendor"},"category":{"type":"string","title":"Category"},"invoice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice"},"staff":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Staff"},"total":{"type":"number","title":"Total"},"month":{"type":"string","title":"Month"},"status":{"type":"string","title":"Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"createdAt":{"type":"string","title":"Createdat"},"updatedAt":{"type":"string","title":"Updatedat"},"lines":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Lines"}},"type":"object","required":["id","businessId","date","vendor","category","total","month","status","createdAt","updatedAt","lines"],"title":"OrderDetailResponse"},"cbpulse__OrderResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique purchase order identifier","examples":["ord_a1b2c3d4"]},"business_id":{"type":"string","title":"Business Id","description":"Business this order belongs to","examples":["biz_thefred"]},"vendor_name":{"type":"string","title":"Vendor Name","description":"Vendor or supplier name","examples":["Caribbean Produce Co."]},"vendor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Id","description":"Optional vendor identifier from external system","examples":["vendor_cp_001"]},"line_items":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Line Items","description":"List of order line items with product, quantity, and price","examples":[[{"product":"Local Snapper 10lb","quantity":5,"unit_price":89.0}]]},"subtotal":{"type":"number","title":"Subtotal","description":"Sum of line item costs before tax and shipping","examples":[739.0]},"tax":{"type":"number","title":"Tax","description":"Tax amount applied to the order","examples":[36.95]},"shipping":{"type":"number","title":"Shipping","description":"Shipping/delivery cost","examples":[45.0]},"total":{"type":"number","title":"Total","description":"Grand total: subtotal + tax + shipping","examples":[820.95]},"status":{"type":"string","title":"Status","description":"Order status: pending_approval, approved, placed, in_transit, delivered, or cancelled","examples":["pending_approval"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-text notes about the order","examples":["Weekly seafood and bar restock for The Fred kitchen"]},"requested_by":{"type":"string","title":"Requested By","description":"User ID who submitted the purchase request","examples":["user_mgr_01"]},"requested_at":{"type":"string","title":"Requested At","description":"ISO-8601 timestamp when the order was requested","examples":["2026-02-10T09:30:00+00:00"]},"approved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved By","description":"User ID who approved the order","examples":["user_admin_01"]},"approved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved At","description":"ISO-8601 timestamp when the order was approved","examples":["2026-02-10T14:15:00+00:00"]},"rejected_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejected By","description":"User ID who rejected the order"},"rejected_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejected At","description":"ISO-8601 timestamp when the order was rejected"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason","description":"Reason provided when the order was rejected","examples":["Budget exceeded for this quarter"]},"order_placed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order Placed At","description":"ISO-8601 timestamp when the order was placed with the vendor","examples":["2026-02-10T14:20:00+00:00"]},"tracking_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tracking Number","description":"Shipping tracking number","examples":["USPS9400111899223456789012"]},"expected_delivery":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expected Delivery","description":"Expected delivery date (YYYY-MM-DD)","examples":["2026-02-14"]},"delivered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivered At","description":"ISO-8601 timestamp when the order was delivered"},"requested_delivery_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested Delivery Date","description":"Preferred delivery date specified at order creation","examples":["2026-02-13"]},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the record was created","examples":["2026-02-10T09:30:00+00:00"]},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last update","examples":["2026-02-10T14:20:00+00:00"]}},"type":"object","required":["id","business_id","vendor_name","subtotal","tax","shipping","total","status","requested_by","requested_at","created_at","updated_at"],"title":"OrderResponse","description":"Full purchase order with approval state, fulfillment tracking, and line items.","example":{"approved_at":"2026-02-10T14:15:00+00:00","approved_by":"user_admin_01","business_id":"biz_thefred","created_at":"2026-02-10T09:30:00+00:00","expected_delivery":"2026-02-14","id":"ord_a1b2c3d4","line_items":[{"product":"Local Snapper 10lb","quantity":5,"unit_price":89.0},{"product":"Cruzan Rum 750ml","quantity":12,"unit_price":24.5}],"notes":"Weekly seafood and bar restock for The Fred kitchen","order_placed_at":"2026-02-10T14:20:00+00:00","requested_at":"2026-02-10T09:30:00+00:00","requested_by":"user_mgr_01","requested_delivery_date":"2026-02-13","shipping":45.0,"status":"approved","subtotal":739.0,"tax":36.95,"total":820.95,"tracking_number":"USPS9400111899223456789012","updated_at":"2026-02-10T14:20:00+00:00","vendor_id":"vendor_cp_001","vendor_name":"Caribbean Produce Co."}},"cbpulse__OrderSummaryResponse":{"properties":{"id":{"type":"string","title":"Id"},"businessId":{"type":"string","title":"Businessid"},"date":{"type":"string","title":"Date"},"vendor":{"type":"string","title":"Vendor"},"category":{"type":"string","title":"Category"},"invoice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice"},"staff":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Staff"},"total":{"type":"number","title":"Total"},"month":{"type":"string","title":"Month"},"status":{"type":"string","title":"Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"createdAt":{"type":"string","title":"Createdat"},"updatedAt":{"type":"string","title":"Updatedat"}},"type":"object","required":["id","businessId","date","vendor","category","total","month","status","createdAt","updatedAt"],"title":"OrderSummaryResponse"},"cbpulse__OtTrendEmployee":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"employee_name":{"type":"string","title":"Employee Name"},"business_id":{"type":"string","title":"Business Id"},"weeks":{"items":{"$ref":"#/components/schemas/cbpulse__OtTrendWeek"},"type":"array","title":"Weeks"}},"type":"object","required":["employee_id","employee_name","business_id","weeks"],"title":"OtTrendEmployee"},"cbpulse__OtTrendWeek":{"properties":{"week_start":{"type":"string","title":"Week Start"},"regular_hours":{"type":"number","title":"Regular Hours"},"overtime_hours":{"type":"number","title":"Overtime Hours"},"double_ot_hours":{"type":"number","title":"Double Ot Hours"}},"type":"object","required":["week_start","regular_hours","overtime_hours","double_ot_hours"],"title":"OtTrendWeek"},"cbpulse__OvertimeAlertResponse":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"employee_name":{"type":"string","title":"Employee Name"},"business_id":{"type":"string","title":"Business Id"},"total_hours":{"type":"number","title":"Total Hours","description":"Total hours worked this week"},"overtime_hours":{"type":"number","title":"Overtime Hours","description":"Hours exceeding threshold"},"alert_type":{"type":"string","title":"Alert Type","description":"overtime_threshold, multi_location, or understaffed"}},"type":"object","required":["employee_id","employee_name","business_id","total_hours","overtime_hours","alert_type"],"title":"OvertimeAlertResponse","description":"Weekly overtime or staffing alert for an employee."},"cbpulse__OvertimeCreate":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"business_id":{"type":"string","title":"Business Id"},"overtime_date":{"type":"string","title":"Overtime Date"},"hours_requested":{"type":"number","maximum":80.0,"exclusiveMinimum":0.0,"title":"Hours Requested"},"reason":{"type":"string","maxLength":2000,"minLength":1,"title":"Reason"},"requested_by":{"type":"string","title":"Requested By"}},"type":"object","required":["employee_id","business_id","overtime_date","hours_requested","reason","requested_by"],"title":"OvertimeCreate"},"cbpulse__OvertimeDeny":{"properties":{"reason":{"type":"string","maxLength":500,"minLength":1,"title":"Reason"}},"type":"object","required":["reason"],"title":"OvertimeDeny"},"cbpulse__OvertimeEmployeeResponse":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"name":{"type":"string","title":"Name"},"business_id":{"type":"string","title":"Business Id"},"business_name":{"type":"string","title":"Business Name"},"hours_worked":{"type":"number","title":"Hours Worked"},"projected_total_hours":{"type":"number","title":"Projected Total Hours"},"projected_ot_hours":{"type":"number","title":"Projected Ot Hours"},"ot_cost_estimate":{"type":"number","title":"Ot Cost Estimate","description":"Projected OT cost at 1.5× rate"},"avg_hourly_rate":{"type":"number","title":"Avg Hourly Rate"}},"type":"object","required":["employee_id","name","business_id","business_name","hours_worked","projected_total_hours","projected_ot_hours","ot_cost_estimate","avg_hourly_rate"],"title":"OvertimeEmployeeResponse","description":"Employee projected to exceed 40 hours in current pay period."},"cbpulse__OvertimeHistoryEmployee":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"name":{"type":"string","title":"Name"},"business_id":{"type":"string","title":"Business Id"},"business_name":{"type":"string","title":"Business Name"},"avg_hourly_rate":{"type":"number","title":"Avg Hourly Rate"},"periods":{"items":{"$ref":"#/components/schemas/cbpulse__OvertimePeriod"},"type":"array","title":"Periods"},"totals":{"$ref":"#/components/schemas/cbpulse__OvertimeTotals"}},"type":"object","required":["employee_id","name","business_id","business_name","avg_hourly_rate","totals"],"title":"OvertimeHistoryEmployee","description":"Per-employee OT history with period breakdown."},"cbpulse__OvertimeHistoryResponse":{"properties":{"employees":{"items":{"$ref":"#/components/schemas/cbpulse__OvertimeHistoryEmployee"},"type":"array","title":"Employees"}},"type":"object","title":"OvertimeHistoryResponse","description":"Rolling OT history response."},"cbpulse__OvertimePeriod":{"properties":{"start":{"type":"string","title":"Start"},"end":{"type":"string","title":"End"},"total_hours":{"type":"number","title":"Total Hours"},"ot_hours":{"type":"number","title":"Ot Hours"},"ot_cost":{"type":"number","title":"Ot Cost"}},"type":"object","required":["start","end","total_hours","ot_hours","ot_cost"],"title":"OvertimePeriod","description":"Single pay-period OT breakdown."},"cbpulse__OvertimeRemindRequest":{"properties":{"message":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Message","description":"Optional override for the BoldSign reminder email body."}},"type":"object","title":"OvertimeRemindRequest"},"cbpulse__OvertimeRemindResponse":{"properties":{"reminded":{"type":"boolean","title":"Reminded"},"boldsign_doc_id":{"type":"string","title":"Boldsign Doc Id"},"already_complete":{"type":"boolean","title":"Already Complete","default":false},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["reminded","boldsign_doc_id"],"title":"OvertimeRemindResponse"},"cbpulse__OvertimeResponse":{"properties":{"id":{"type":"string","title":"Id"},"employee_id":{"type":"string","title":"Employee Id"},"business_id":{"type":"string","title":"Business Id"},"overtime_date":{"type":"string","title":"Overtime Date"},"hours_requested":{"type":"number","title":"Hours Requested"},"reason":{"type":"string","title":"Reason"},"requested_by":{"type":"string","title":"Requested By"},"requested_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested By User Id"},"boldsign_doc_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Boldsign Doc Id"},"status":{"type":"string","title":"Status"},"denied_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Denied Reason"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","employee_id","business_id","overtime_date","hours_requested","reason","requested_by","requested_by_user_id","boldsign_doc_id","status","denied_reason","created_at","updated_at"],"title":"OvertimeResponse"},"cbpulse__OvertimeTotals":{"properties":{"total_ot_hours":{"type":"number","title":"Total Ot Hours"},"total_ot_cost":{"type":"number","title":"Total Ot Cost"},"periods_with_ot":{"type":"integer","title":"Periods With Ot"},"total_periods":{"type":"integer","title":"Total Periods"}},"type":"object","required":["total_ot_hours","total_ot_cost","periods_with_ot","total_periods"],"title":"OvertimeTotals","description":"Aggregate OT totals across all periods."},"cbpulse__OvertimeTrackingResponse":{"properties":{"pay_period":{"$ref":"#/components/schemas/cbpulse__PayPeriodInfo"},"employees":{"items":{"$ref":"#/components/schemas/cbpulse__OvertimeEmployeeResponse"},"type":"array","title":"Employees"}},"type":"object","required":["pay_period"],"title":"OvertimeTrackingResponse","description":"Current pay-period OT tracking response."},"cbpulse__PaginatedInvoices":{"properties":{"invoices":{"items":{"$ref":"#/components/schemas/cbpulse__InvoiceAuditDetail"},"type":"array","title":"Invoices"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["invoices","total","page","page_size","has_more"],"title":"PaginatedInvoices","description":"Paginated invoice list with total count."},"cbpulse__PayPeriodDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"period_start":{"type":"string","title":"Period Start","description":"YYYY-MM-DD start of period"},"period_end":{"type":"string","title":"Period End","description":"YYYY-MM-DD end of period"},"pay_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pay Date","description":"Scheduled pay date"},"period_type":{"type":"string","title":"Period Type","description":"regular or contractor"},"status":{"type":"string","title":"Status","description":"assembling, review, approved, submitted, rejected"},"total_gross":{"type":"number","title":"Total Gross","description":"Total gross payroll in USD"},"total_hours":{"type":"number","title":"Total Hours"},"total_overtime_hours":{"type":"number","title":"Total Overtime Hours"},"employee_count":{"type":"integer","title":"Employee Count"},"exception_count":{"type":"integer","title":"Exception Count","description":"Number of timesheet exceptions"},"approved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved By"},"approved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved At"},"rejection_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Notes"},"submitted_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Submitted At"},"variance_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Variance Summary","description":"Free-text summary of period-over-period variance"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"packages":{"items":{"$ref":"#/components/schemas/cbpulse__PayrollPackageResponse"},"type":"array","title":"Packages"},"exceptions":{"items":{"$ref":"#/components/schemas/cbpulse__TimesheetExceptionResponse"},"type":"array","title":"Exceptions"}},"type":"object","required":["id","business_id","period_start","period_end","period_type","status","total_gross","total_hours","total_overtime_hours","employee_count","exception_count","created_at","updated_at"],"title":"PayPeriodDetailResponse"},"cbpulse__PayPeriodInfo":{"properties":{"start":{"type":"string","title":"Start","description":"YYYY-MM-DD period start"},"end":{"type":"string","title":"End","description":"YYYY-MM-DD period end"},"days_remaining":{"type":"integer","title":"Days Remaining"},"days_elapsed":{"type":"integer","title":"Days Elapsed"}},"type":"object","required":["start","end","days_remaining","days_elapsed"],"title":"PayPeriodInfo","description":"Current pay-period window metadata."},"cbpulse__PayPeriodResponse":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"period_start":{"type":"string","title":"Period Start","description":"YYYY-MM-DD start of period"},"period_end":{"type":"string","title":"Period End","description":"YYYY-MM-DD end of period"},"pay_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pay Date","description":"Scheduled pay date"},"period_type":{"type":"string","title":"Period Type","description":"regular or contractor"},"status":{"type":"string","title":"Status","description":"assembling, review, approved, submitted, rejected"},"total_gross":{"type":"number","title":"Total Gross","description":"Total gross payroll in USD"},"total_hours":{"type":"number","title":"Total Hours"},"total_overtime_hours":{"type":"number","title":"Total Overtime Hours"},"employee_count":{"type":"integer","title":"Employee Count"},"exception_count":{"type":"integer","title":"Exception Count","description":"Number of timesheet exceptions"},"approved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved By"},"approved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved At"},"rejection_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Notes"},"submitted_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Submitted At"},"variance_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Variance Summary","description":"Free-text summary of period-over-period variance"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","business_id","period_start","period_end","period_type","status","total_gross","total_hours","total_overtime_hours","employee_count","exception_count","created_at","updated_at"],"title":"PayPeriodResponse","description":"Semi-monthly pay period summary."},"cbpulse__PayableItem":{"properties":{"id":{"type":"string","title":"Id","description":"Unique payable record identifier"},"vendor_name":{"type":"string","title":"Vendor Name","description":"Name of the vendor or supplier"},"invoice_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Number","description":"Vendor invoice number, if available"},"business_name":{"type":"string","title":"Business Name","description":"Business this payable belongs to"},"amount":{"type":"number","title":"Amount","description":"Invoice amount in USD"},"due_date":{"type":"string","title":"Due Date","description":"Payment due date (YYYY-MM-DD)"},"status":{"type":"string","title":"Status","description":"Payment status, e.g. pending or approved"},"days_until_due":{"type":"integer","title":"Days Until Due","description":"Days remaining until payment is due"}},"type":"object","required":["id","vendor_name","invoice_number","business_name","amount","due_date","status","days_until_due"],"title":"PayableItem","example":{"amount":3450.0,"business_name":"The Fred","days_until_due":7,"due_date":"2026-02-22","id":"pay-20260215-001","invoice_number":"CP-2026-0842","status":"pending","vendor_name":"Caribbean Produce Co."}},"cbpulse__PayablesResponse":{"properties":{"overdue":{"items":{"$ref":"#/components/schemas/cbpulse__PayableItem"},"type":"array","title":"Overdue","description":"Invoices past their due date"},"due_soon":{"items":{"$ref":"#/components/schemas/cbpulse__PayableItem"},"type":"array","title":"Due Soon","description":"Invoices due within 7 days"},"future":{"items":{"$ref":"#/components/schemas/cbpulse__PayableItem"},"type":"array","title":"Future","description":"Invoices due beyond 7 days"},"total_amount":{"type":"number","title":"Total Amount","description":"Sum of all payable amounts in USD"},"overdue_amount":{"type":"number","title":"Overdue Amount","description":"Sum of overdue payable amounts in USD"},"overdue_count":{"type":"integer","title":"Overdue Count","description":"Number of overdue invoices"},"due_soon_count":{"type":"integer","title":"Due Soon Count","description":"Number of invoices due within 7 days"},"total_count":{"type":"integer","title":"Total Count","description":"Total number of payable invoices"}},"type":"object","required":["overdue","due_soon","future","total_amount","overdue_amount","overdue_count","due_soon_count","total_count"],"title":"PayablesResponse","example":{"due_soon":[],"due_soon_count":3,"future":[],"overdue":[],"overdue_amount":2400.0,"overdue_count":1,"total_amount":18750.0,"total_count":8}},"cbpulse__PayrollPackageResponse":{"properties":{"id":{"type":"string","title":"Id"},"pay_period_id":{"type":"string","title":"Pay Period Id"},"business_id":{"type":"string","title":"Business Id"},"employee_id":{"type":"string","title":"Employee Id"},"employee_name":{"type":"string","title":"Employee Name"},"regular_hours":{"type":"number","title":"Regular Hours"},"overtime_hours":{"type":"number","title":"Overtime Hours"},"hourly_rate":{"type":"number","title":"Hourly Rate","description":"Base hourly rate in USD"},"overtime_rate":{"type":"number","title":"Overtime Rate","description":"Overtime rate (typically 1.5x)"},"gross_pay":{"type":"number","title":"Gross Pay"},"deductions":{"type":"number","title":"Deductions"},"net_pay":{"type":"number","title":"Net Pay"},"status":{"type":"string","title":"Status","description":"active or hold"},"hold_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hold Reason","description":"Reason for hold (missing timecard, etc.)"}},"type":"object","required":["id","pay_period_id","business_id","employee_id","employee_name","regular_hours","overtime_hours","hourly_rate","overtime_rate","gross_pay","deductions","net_pay","status"],"title":"PayrollPackageResponse","description":"Per-employee payroll line item within a pay period."},"cbpulse__PendingListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbpulse__PendingResolutionResponse"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["items","total","limit","offset"],"title":"PendingListResponse"},"cbpulse__PendingResolutionResponse":{"properties":{"id":{"type":"integer","title":"Id"},"source":{"type":"string","title":"Source"},"source_ref":{"type":"string","title":"Source Ref"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"},"candidates":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Candidates"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","source","source_ref","payload","candidates","status","created_at"],"title":"PendingResolutionResponse"},"cbpulse__PeriodRange":{"properties":{"start":{"type":"string","title":"Start","description":"Period start date (YYYY-MM-DD)"},"end":{"type":"string","title":"End","description":"Period end date (YYYY-MM-DD)"}},"type":"object","required":["start","end"],"title":"PeriodRange"},"cbpulse__PhotoAdd":{"properties":{"url":{"type":"string","maxLength":1000,"minLength":1,"title":"Url"}},"type":"object","required":["url"],"title":"PhotoAdd","description":"One photo reference to append. The app stores a cbfiles object\nreference (``cbfiles://<bucket>/<key>``) and mints a signed URL to show\nit; a plain https URL is kept as given."},"cbpulse__PinLoginRequest":{"properties":{"pin":{"type":"string","title":"Pin"}},"type":"object","required":["pin"],"title":"PinLoginRequest"},"cbpulse__PnLComparisonResponse":{"properties":{"business_id":{"type":"string","title":"Business Id","examples":["biz_thefred"]},"start_date":{"type":"string","title":"Start Date","examples":["2026-02-01"]},"end_date":{"type":"string","title":"End Date","examples":["2026-02-15"]},"line_items":{"items":{"$ref":"#/components/schemas/cbpulse__PnLLineItem"},"type":"array","title":"Line Items"},"has_qb_data":{"type":"boolean","title":"Has Qb Data","description":"Whether QB snapshot exists for this period","examples":[true]}},"type":"object","required":["business_id","start_date","end_date","line_items","has_qb_data"],"title":"PnLComparisonResponse","example":{"business_id":"biz_thefred","end_date":"2026-02-15","has_qb_data":true,"line_items":[{"label":"Total Revenue","qb_amount":12300.0,"variance":150.0,"variance_pct":1.2,"vip_amount":12450.0}],"start_date":"2026-02-01"}},"cbpulse__PnLLineItem":{"properties":{"label":{"type":"string","title":"Label","description":"Line item name (e.g. Total Revenue, COGS)","examples":["Total Revenue","COGS","Gross Profit"]},"vip_amount":{"type":"number","title":"Vip Amount","description":"Amount computed from VIP data","examples":[12450.0]},"qb_amount":{"type":"number","title":"Qb Amount","description":"Amount from QuickBooks snapshot","examples":[12300.0]},"variance":{"type":"number","title":"Variance","description":"Difference: VIP amount minus QB amount","examples":[150.0]},"variance_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Variance Pct","description":"Variance as percentage of QB amount","examples":[1.2]}},"type":"object","required":["label","vip_amount","qb_amount","variance"],"title":"PnLLineItem","example":{"label":"Total Revenue","qb_amount":12300.0,"variance":150.0,"variance_pct":1.2,"vip_amount":12450.0}},"cbpulse__PortfolioResponse":{"properties":{"total_revenue":{"type":"number","title":"Total Revenue","description":"Sum of latest revenue across all active businesses"},"total_labor_cost":{"type":"number","title":"Total Labor Cost","description":"Sum of latest labor cost across all active businesses"},"total_cogs":{"type":"number","title":"Total Cogs","description":"Sum of latest COGS across all active businesses"},"avg_health_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Health Score","description":"Average health score across businesses with computed scores"},"business_count":{"type":"integer","title":"Business Count","description":"Number of active businesses with financial data"},"businesses":{"items":{"$ref":"#/components/schemas/cbpulse__FinancialsResponse"},"type":"array","title":"Businesses","description":"Per-business latest financial snapshots"}},"type":"object","required":["total_revenue","total_labor_cost","total_cogs","avg_health_score","business_count","businesses"],"title":"PortfolioResponse","description":"Aggregated portfolio-wide financial overview with per-business breakdowns.","example":{"avg_health_score":78.5,"business_count":3,"businesses":[],"total_cogs":10500.0,"total_labor_cost":13860.0,"total_revenue":42000.0}},"cbpulse__PortfolioTotals":{"properties":{"total_revenue":{"type":"number","title":"Total Revenue","description":"Portfolio-wide combined revenue","default":0.0},"labor_cost":{"type":"number","title":"Labor Cost","description":"Portfolio-wide total labor cost","default":0.0},"cogs":{"type":"number","title":"Cogs","description":"Portfolio-wide cost of goods sold","default":0.0},"transaction_count":{"type":"integer","title":"Transaction Count","description":"Portfolio-wide POS transaction count","default":0},"labor_hours":{"type":"number","title":"Labor Hours","description":"Portfolio-wide total staff hours worked","default":0.0},"fb_revenue":{"type":"number","title":"Fb Revenue","description":"Portfolio-wide food and beverage revenue","default":0.0},"gross_profit":{"type":"number","title":"Gross Profit","description":"Portfolio-wide gross profit","default":0.0},"labor_percent":{"type":"number","title":"Labor Percent","description":"Portfolio-wide labor cost as percentage of revenue","default":0.0},"cogs_percent":{"type":"number","title":"Cogs Percent","description":"Portfolio-wide COGS as percentage of revenue","default":0.0},"prime_cost_pct":{"type":"number","title":"Prime Cost Pct","description":"Portfolio-wide prime cost percentage","default":0.0},"food_cost_pct":{"type":"number","title":"Food Cost Pct","description":"Portfolio-wide food cost percentage","default":0.0},"avg_check":{"type":"number","title":"Avg Check","description":"Portfolio-wide average transaction amount","default":0.0},"rev_per_labor_hour":{"type":"number","title":"Rev Per Labor Hour","description":"Portfolio-wide revenue per labor hour","default":0.0},"health_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Health Score","description":"Portfolio-wide average health score"},"wow":{"$ref":"#/components/schemas/cbpulse__WoWChange","description":"Portfolio-wide week-over-week changes"}},"type":"object","title":"PortfolioTotals","description":"Portfolio-wide aggregated KPIs.","example":{"avg_check":86.6,"cogs":10500.0,"cogs_percent":25.0,"fb_revenue":19200.0,"food_cost_pct":28.5,"gross_profit":17640.0,"health_score":78,"labor_cost":13860.0,"labor_hours":312.0,"labor_percent":33.0,"prime_cost_pct":58.0,"rev_per_labor_hour":134.62,"total_revenue":42000.0,"transaction_count":485,"wow":{"avg_check":2.7,"cogs":3.0,"health_score":1.0,"labor_cost":-1.5,"labor_percent":-2.1,"prime_cost_pct":-0.4,"rev_per_labor_hour":6.1,"total_revenue":8.3}}},"cbpulse__PostCreate":{"properties":{"brand":{"type":"string","title":"Brand","description":"Brand slug","examples":["the_saint","the_fred"]},"platform":{"type":"string","title":"Platform","description":"Target platform","examples":["instagram","fb"]},"copy":{"type":"string","title":"Copy","description":"The copy CB pushed (the original)"},"photo_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Photo Path","description":"Optional asset path"},"vista_post_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vista Post Id","description":"Vista's post id, returned on push"},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type","description":"atmosphere/event/food/booking"},"pushed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pushed By","description":"Skill/user identity that pushed the post"}},"type":"object","required":["brand","platform","copy"],"title":"PostCreate","description":"Record of a post CB pushed to Vista (the \"original\").","example":{"brand":"the_saint","content_type":"atmosphere","copy":"Sunset on the dock. #stjohn #usvi #cocktails","platform":"instagram","pushed_by":"generation-skill","vista_post_id":"vs_1234567"}},"cbpulse__PostUpdate":{"properties":{"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"publish_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Publish At"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"},"publish_as":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Publish As"},"labels":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Labels"}},"type":"object","title":"PostUpdate"},"cbpulse__PreferenceResponse":{"properties":{"in_app_enabled":{"type":"boolean","title":"In App Enabled","description":"Whether in-app notifications are enabled"},"email_enabled":{"type":"boolean","title":"Email Enabled","description":"Whether email notifications are enabled"},"email_critical":{"type":"boolean","title":"Email Critical","description":"Email immediately on critical alerts"},"email_warning":{"type":"boolean","title":"Email Warning","description":"Include warning alerts in batch emails"},"email_info":{"type":"boolean","title":"Email Info","description":"Include info items in daily digest"},"quiet_hours_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quiet Hours Start","description":"Quiet hours start (HH:MM)","examples":["22:00","21:00"]},"quiet_hours_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quiet Hours End","description":"Quiet hours end (HH:MM)","examples":["07:00","06:30"]}},"type":"object","required":["in_app_enabled","email_enabled","email_critical","email_warning","email_info"],"title":"PreferenceResponse","description":"User notification preferences.","example":{"email_critical":true,"email_enabled":true,"email_info":false,"email_warning":true,"in_app_enabled":true,"quiet_hours_end":"07:00","quiet_hours_start":"22:00"}},"cbpulse__PreferenceUpdate":{"properties":{"in_app_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"In App Enabled"},"email_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Enabled"},"email_critical":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Critical"},"email_warning":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Warning"},"email_info":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Info"},"quiet_hours_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quiet Hours Start","examples":["22:00","21:00"]},"quiet_hours_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quiet Hours End","examples":["07:00","06:30"]}},"type":"object","title":"PreferenceUpdate","description":"Fields to update on notification preferences.","example":{"email_critical":true,"email_enabled":true,"quiet_hours_end":"07:00","quiet_hours_start":"22:00"}},"cbpulse__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbpulse__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbpulse__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbpulse__ProfileCreate":{"properties":{"brand":{"type":"string","title":"Brand","description":"Brand slug"},"platform":{"type":"string","title":"Platform","description":"Platform: instagram, facebook, google_business"},"vista_profile_id":{"type":"integer","title":"Vista Profile Id","description":"Vista Social numeric profile ID"},"handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Handle","description":"Social media handle"}},"type":"object","required":["brand","platform","vista_profile_id"],"title":"ProfileCreate"},"cbpulse__ProfileResponse":{"properties":{"id":{"type":"string","title":"Id"},"brand":{"type":"string","title":"Brand"},"platform":{"type":"string","title":"Platform"},"vista_profile_id":{"type":"integer","title":"Vista Profile Id"},"handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Handle"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","brand","platform","vista_profile_id","handle","is_active"],"title":"ProfileResponse"},"cbpulse__ProfileUpdate":{"properties":{"brand":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"},"vista_profile_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Vista Profile Id"},"handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Handle"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"ProfileUpdate"},"cbpulse__PurgeRequest":{"properties":{"statuses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Statuses"},"brand_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand Slug"},"dry_run":{"type":"boolean","title":"Dry Run","default":true},"allow_pushed":{"type":"boolean","title":"Allow Pushed","default":false}},"type":"object","title":"PurgeRequest","description":"Hard-delete request. `dry_run` defaults True on purpose."},"cbpulse__QBAccountListResponse":{"properties":{"accounts":{"items":{"$ref":"#/components/schemas/cbpulse__QBAccountResponse"},"type":"array","title":"Accounts","description":"Synced QB chart of accounts"},"total":{"type":"integer","title":"Total","description":"Total number of matching accounts","examples":[24]}},"type":"object","required":["accounts","total"],"title":"QBAccountListResponse"},"cbpulse__QBAccountResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Internal account record identifier","examples":["acc_01HXYZ"]},"qb_account_id":{"type":"string","title":"Qb Account Id","description":"QuickBooks account identifier","examples":["101"]},"name":{"type":"string","title":"Name","description":"Account display name","examples":["Sales Revenue"]},"fully_qualified_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fully Qualified Name","description":"Full hierarchical account name","examples":["Income:Sales Revenue"]},"account_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Type","description":"Top-level account type","examples":["Income"]},"account_sub_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Sub Type","description":"Account sub-type classification","examples":["SalesOfProductIncome"]},"classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification","description":"Asset, liability, equity, revenue, or expense","examples":["Revenue"]},"current_balance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Current Balance","description":"Current account balance in dollars","examples":[48250.75]},"currency_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency Ref","description":"ISO currency code for the account","examples":["USD"]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the account is active in QB","default":true,"examples":[true]}},"type":"object","required":["id","qb_account_id","name"],"title":"QBAccountResponse","example":{"account_sub_type":"SalesOfProductIncome","account_type":"Income","classification":"Revenue","currency_ref":"USD","current_balance":48250.75,"fully_qualified_name":"Income:Sales Revenue","id":"acc_01HXYZ","is_active":true,"name":"Sales Revenue","qb_account_id":"101"}},"cbpulse__RecategorizeReport":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run"},"scanned":{"type":"integer","title":"Scanned"},"recategorized":{"type":"integer","title":"Recategorized"},"by_category":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Category"},"sample":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Sample"}},"type":"object","required":["dry_run","scanned","recategorized","by_category","sample"],"title":"RecategorizeReport"},"cbpulse__RecentSignal":{"properties":{"id":{"type":"string","title":"Id","description":"Unique signal identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this signal belongs to"},"signal_type":{"type":"string","title":"Signal Type","description":"Signal category that triggered detection","examples":["low_occupancy","revenue_dip","booking_surge"]},"severity":{"type":"string","title":"Severity","description":"Signal severity: critical, warning, or info","examples":["critical","warning","info"]},"status":{"type":"string","title":"Status","description":"Lifecycle status: new, acknowledged, actioned, dismissed, expired"},"title":{"type":"string","title":"Title","description":"Human-readable signal description"},"signal_date":{"type":"string","title":"Signal Date","description":"Date the signal was detected (YYYY-MM-DD)"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the signal was created"}},"type":"object","required":["id","business_id","signal_type","severity","status","title","signal_date","created_at"],"title":"RecentSignal","description":"Abbreviated signal preview for the dashboard.","example":{"business_id":"biz_thefred","created_at":"2026-02-15T14:30:00+00:00","id":"sig_9a2b3c4d","severity":"warning","signal_date":"2026-02-15","signal_type":"low_occupancy","status":"new","title":"Low occupancy: 32% (threshold 40%)"}},"cbpulse__RecommendedActionResponse":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"recommended_level":{"type":"string","title":"Recommended Level","description":"Next step on progressive discipline ladder"},"current_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Level","description":"Current highest active discipline level, or null if none"},"reasoning":{"type":"string","title":"Reasoning","description":"Explanation of recommendation"},"wda_ground":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wda Ground"},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning"}},"type":"object","required":["employee_id","recommended_level","current_level","reasoning"],"title":"RecommendedActionResponse","description":"AI-recommended next disciplinary action."},"cbpulse__RehireRequest":{"properties":{"re_hire_date":{"type":"string","maxLength":10,"minLength":10,"title":"Re Hire Date","description":"ISO date (YYYY-MM-DD) the employee returned to work."},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes","description":"Optional context recorded on the audit row."}},"type":"object","required":["re_hire_date"],"title":"RehireRequest"},"cbpulse__RehireResponse":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"rehire_id":{"type":"string","title":"Rehire Id"},"previous_status":{"type":"string","title":"Previous Status"},"new_status":{"type":"string","title":"New Status"},"re_hire_date":{"type":"string","title":"Re Hire Date"},"prior_termination_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prior Termination Id"},"decided_by_user_id":{"type":"string","title":"Decided By User Id"},"decided_by_name":{"type":"string","title":"Decided By Name"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["employee_id","rehire_id","previous_status","new_status","re_hire_date","prior_termination_id","decided_by_user_id","decided_by_name","created_at"],"title":"RehireResponse"},"cbpulse__RejectCampaignRequest":{"properties":{"reason":{"type":"string","title":"Reason","description":"Reason for rejecting the campaign","examples":["Content doesn't match brand voice — needs warmer tone"]}},"type":"object","required":["reason"],"title":"RejectCampaignRequest"},"cbpulse__ReplyRequest":{"properties":{"body":{"type":"string","title":"Body","description":"Reply message body"},"content_type":{"type":"string","title":"Content Type","description":"Content type: text, html","default":"text"},"attachments":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Attachments","description":"Optional attachments"}},"type":"object","required":["body"],"title":"ReplyRequest","description":"Payload for sending a reply in a conversation."},"cbpulse__ReportOptions":{"properties":{"skip_if_no_data":{"type":"boolean","title":"Skip If No Data","description":"Skip sending if every data source returned empty","default":false},"date_offset":{"type":"integer","title":"Date Offset","description":"Shift the report date by N days (e.g. -1 for yesterday)","default":0},"period":{"type":"string","title":"Period","description":"Report period: day, wtd, ptd, mtd, week, month, custom","default":"day"},"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date","description":"Explicit start date for custom period (YYYY-MM-DD)"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date","description":"Explicit end date for custom period (YYYY-MM-DD)"}},"type":"object","title":"ReportOptions","description":"Optional flags that modify report generation behaviour."},"cbpulse__ReportRequest":{"properties":{"report_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Report Type","description":"Category key (e.g. 'daily_snapshot')"},"report_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Report Name","description":"Human-readable name shown in logs and emails"},"data_sources":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbpulse__DataSourceSpec"},"type":"array"},{"type":"null"}],"title":"Data Sources","description":"Ordered list of data sources to fetch"},"template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template","description":"Jinja2 template filename (relative to templates dir)"},"html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Html","description":"Pre-rendered HTML body — skips generation when provided"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject","description":"Email subject line (used in passthrough mode)"},"email":{"$ref":"#/components/schemas/cbpulse__EmailSpec"},"delivery":{"anyOf":[{"type":"string","enum":["smtp","front"]},{"type":"null"}],"title":"Delivery","description":"Delivery backend override (None = use config default)"},"options":{"$ref":"#/components/schemas/cbpulse__ReportOptions"}},"type":"object","required":["email"],"title":"ReportRequest","description":"Full specification for generating and delivering one report.\n\nTwo modes:\n- **Generate mode** (default): provide report_type, data_sources, template\n  to fetch data, render, and deliver.\n- **Passthrough mode**: provide ``html`` and ``subject`` to deliver\n  pre-rendered HTML without generation."},"cbpulse__ReportResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Outcome status (e.g. 'sent', 'skipped')"},"report_name":{"type":"string","title":"Report Name"},"data_sources_fetched":{"type":"integer","title":"Data Sources Fetched","description":"Number of data sources that returned data"},"data_sources_empty":{"type":"integer","title":"Data Sources Empty","description":"Number of data sources that returned no data"},"delivery_method":{"type":"string","title":"Delivery Method","description":"Backend used ('smtp' or 'front')"},"delivery_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivery Id","description":"External message/conversation ID from the delivery backend"},"rendered_at":{"type":"string","format":"date-time","title":"Rendered At","description":"Timestamp when the report HTML was rendered"}},"type":"object","required":["status","report_name","data_sources_fetched","data_sources_empty","delivery_method","rendered_at"],"title":"ReportResponse","description":"Result returned after a report has been generated and dispatched."},"cbpulse__ReservationResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Internal VIP reservation ID","examples":["res_01HXYZ"]},"business_id":{"type":"string","title":"Business Id","description":"Business this reservation belongs to","examples":["biz_thesaint","biz_thefred"]},"source":{"type":"string","title":"Source","description":"Integration source (cloudbeds, webrezpro)","examples":["cloudbeds","webrezpro"]},"external_id":{"type":"string","title":"External Id","description":"ID in the source PMS system","examples":["CB-78901"]},"reservation_type":{"type":"string","title":"Reservation Type","description":"Type: room, restaurant, event, spa","examples":["room"]},"status":{"type":"string","title":"Status","description":"Reservation status: confirmed, checked_in, checked_out, cancelled, no_show","examples":["confirmed","checked_in","checked_out"]},"guest_name":{"type":"string","title":"Guest Name","description":"Primary guest name","examples":["John Smith","Sarah Johnson"]},"guest_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Email","description":"Guest email address","examples":["john.smith@example.com"]},"guest_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guest Phone","description":"Guest phone number","examples":["+1-340-555-0123"]},"arrival_date":{"type":"string","title":"Arrival Date","description":"Check-in date (YYYY-MM-DD)","examples":["2026-02-15"]},"departure_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Departure Date","description":"Check-out date (YYYY-MM-DD)","examples":["2026-02-19"]},"total_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Amount","description":"Total reservation amount","examples":[1580.0]},"room_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Type","description":"Room type name","examples":["Ocean View King","Garden Suite"]},"room_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Number","description":"Assigned room identifier","examples":["204"]},"nights":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Nights","description":"Number of nights","examples":[4]},"synced_at":{"type":"string","title":"Synced At","description":"Last sync timestamp (ISO-8601)","examples":["2026-02-15T08:30:00Z"]}},"type":"object","required":["id","business_id","source","external_id","reservation_type","status","guest_name","arrival_date","synced_at"],"title":"ReservationResponse","description":"A synced reservation from Cloudbeds or WebRezPro.","example":{"arrival_date":"2026-02-15","business_id":"biz_thesaint","departure_date":"2026-02-19","external_id":"CB-78901","guest_email":"john.smith@example.com","guest_name":"John Smith","guest_phone":"+1-340-555-0123","id":"res_01HXYZ","nights":4,"reservation_type":"room","room_number":"204","room_type":"Ocean View King","source":"cloudbeds","status":"confirmed","synced_at":"2026-02-15T08:30:00Z","total_amount":1580.0}},"cbpulse__ResetPasswordRequest":{"properties":{"token":{"type":"string","title":"Token","description":"Password reset token","examples":["aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789..."]},"new_password":{"type":"string","minLength":8,"title":"New Password","description":"New password","examples":["N3wS3cur3P@ss!","MyUpd@tedP4ssw0rd"]}},"type":"object","required":["token","new_password"],"title":"ResetPasswordRequest","description":"Complete a password reset using a token."},"cbpulse__ResolveExceptionRequest":{"properties":{"resolution_notes":{"type":"string","title":"Resolution Notes","description":"How the exception was resolved"}},"type":"object","required":["resolution_notes"],"title":"ResolveExceptionRequest"},"cbpulse__ResolveResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"pending_id":{"type":"integer","title":"Pending Id"},"action":{"type":"string","title":"Action"},"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"new_alias_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"New Alias Id"},"swept_count":{"type":"integer","title":"Swept Count","default":0}},"type":"object","required":["success","pending_id","action"],"title":"ResolveResponse"},"cbpulse__ReviewReputation":{"properties":{"total_reviews":{"type":"integer","title":"Total Reviews","description":"Total number of reviews across all platforms"},"avg_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Rating","description":"Average star rating across all reviews"},"by_sentiment":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Sentiment","description":"Review counts grouped by sentiment label (positive, neutral, negative)"},"unresponded":{"type":"integer","title":"Unresponded","description":"Number of reviews without a posted response"},"by_platform":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Platform","description":"Review counts grouped by platform (google, tripadvisor, etc.)"}},"type":"object","required":["total_reviews","by_sentiment","unresponded","by_platform"],"title":"ReviewReputation","description":"Review aggregation and reputation summary.","example":{"avg_rating":4.3,"by_platform":{"booking_com":34,"google":120,"tripadvisor":80},"by_sentiment":{"negative":24,"neutral":30,"positive":180},"total_reviews":234,"unresponded":12}},"cbpulse__RoleUpdateRequest":{"properties":{"functional_roles":{"items":{"$ref":"#/components/schemas/cbpulse__FunctionalRole"},"type":"array","title":"Functional Roles","description":"Complete list of functional roles to assign (replaces existing)","examples":[["finance","operations"],["owner","finance","hr"],["payroll"]]}},"type":"object","required":["functional_roles"],"title":"RoleUpdateRequest","description":"Replace a user's functional roles."},"cbpulse__RoomConditionPut":{"properties":{"room_condition":{"type":"string","title":"Room Condition"},"do_not_disturb":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Do Not Disturb"}},"type":"object","required":["room_condition"],"title":"RoomConditionPut"},"cbpulse__RunItemOut":{"properties":{"id":{"type":"string","title":"Id"},"item_id":{"type":"string","title":"Item Id"},"label":{"type":"string","title":"Label"},"position":{"type":"integer","title":"Position"},"checked":{"type":"boolean","title":"Checked"},"checked_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checked By Name"},"checked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Checked At"}},"type":"object","required":["id","item_id","label","position","checked","checked_by_name","checked_at"],"title":"RunItemOut"},"cbpulse__RunItemPatch":{"properties":{"checked":{"type":"boolean","title":"Checked"}},"type":"object","required":["checked"],"title":"RunItemPatch"},"cbpulse__RunOut":{"properties":{"id":{"type":"string","title":"Id"},"checklist_id":{"type":"string","title":"Checklist Id"},"business_id":{"type":"string","title":"Business Id"},"period_key":{"type":"string","title":"Period Key"},"room_id":{"type":"string","title":"Room Id"},"room_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Name"},"started_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started By Name"},"started_at":{"type":"string","format":"date-time","title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"completed_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed By Name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"items":{"items":{"$ref":"#/components/schemas/cbpulse__RunItemOut"},"type":"array","title":"Items","default":[]}},"type":"object","required":["id","checklist_id","business_id","period_key","room_id","room_name","started_by_name","started_at","completed_at","completed_by_name","notes"],"title":"RunOut"},"cbpulse__RunStart":{"properties":{"checklist_id":{"type":"string","title":"Checklist Id"},"room_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Id"},"room_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Room Name"}},"type":"object","required":["checklist_id"],"title":"RunStart"},"cbpulse__ScanResult":{"properties":{"job":{"type":"string","title":"Job"},"status":{"type":"string","title":"Status"},"emitted":{"additionalProperties":{"type":"integer"},"type":"object","title":"Emitted"},"duration":{"type":"number","title":"Duration","default":0.0},"details":{"type":"string","title":"Details","default":""}},"type":"object","required":["job","status","emitted"],"title":"ScanResult"},"cbpulse__SegmentResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique segment identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this segment belongs to"},"name":{"type":"string","title":"Name","description":"Segment display name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Segment description"},"criteria":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Criteria","description":"Filter criteria JSON. Supported keys: spend_tier, min_stay_count, max_stay_count, min_spend, max_spend, tags, consent_email, consent_sms"},"estimated_size":{"type":"integer","title":"Estimated Size","description":"Number of guests matching the criteria"},"last_computed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Computed At","description":"ISO-8601 timestamp of last evaluation"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"User ID who created this segment"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the segment is active"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 creation timestamp"},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 last-update timestamp"}},"type":"object","required":["id","business_id","name","estimated_size","is_active","created_at","updated_at"],"title":"SegmentResponse","description":"Guest segment with criteria and estimated size.","example":{"business_id":"biz_thefred","created_at":"2026-02-01T08:00:00+00:00","created_by":"usr_admin1","criteria":{"min_stay_count":2,"spend_tier":"gold"},"description":"Guests with gold+ tier who stayed more than once","estimated_size":42,"id":"seg_1a2b3c4d","is_active":true,"last_computed_at":"2026-02-15T14:30:00+00:00","name":"High-Value Returners","updated_at":"2026-02-15T14:30:00+00:00"}},"cbpulse__SeparationDocument":{"properties":{"id":{"type":"string","title":"Id"},"employee_id":{"type":"string","title":"Employee Id"},"termination_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Termination Id"},"file_id":{"type":"string","title":"File Id"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"filename":{"type":"string","title":"Filename"},"content_type":{"type":"string","title":"Content Type"},"size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size Bytes"},"uploaded_by":{"type":"string","title":"Uploaded By"},"uploaded_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uploaded At"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","required":["id","employee_id","termination_id","file_id","label","filename","content_type","size_bytes","uploaded_by","uploaded_at"],"title":"SeparationDocument"},"cbpulse__SeparationLetterResponse":{"properties":{"file_id":{"type":"string","title":"File Id"},"filename":{"type":"string","title":"Filename"},"content_type":{"type":"string","title":"Content Type"},"size_bytes":{"type":"integer","title":"Size Bytes"}},"type":"object","required":["file_id","filename","content_type","size_bytes"],"title":"SeparationLetterResponse"},"cbpulse__ShipmentStatusUpdate":{"properties":{"status":{"type":"string","title":"Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["status"],"title":"ShipmentStatusUpdate"},"cbpulse__SignalIntelligence":{"properties":{"total":{"type":"integer","title":"Total","description":"Total signal count"},"by_status":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Status","description":"Signal counts grouped by lifecycle status"},"by_severity":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Severity","description":"Signal counts grouped by severity level"},"recent":{"items":{"$ref":"#/components/schemas/cbpulse__RecentSignal"},"type":"array","title":"Recent","description":"5 most recent signals"}},"type":"object","required":["total","by_status","by_severity","recent"],"title":"SignalIntelligence","description":"Signal counts by status/severity plus recent signals.","example":{"by_severity":{"critical":2,"info":12,"warning":4},"by_status":{"acknowledged":2,"actioned":8,"dismissed":3,"new":5},"recent":[],"total":18}},"cbpulse__SignalListResponse":{"properties":{"signals":{"items":{"$ref":"#/components/schemas/cbpulse__SignalResponse"},"type":"array","title":"Signals","description":"List of signals"},"total":{"type":"integer","title":"Total","description":"Total number of matching signals"}},"type":"object","required":["signals","total"],"title":"SignalListResponse","description":"Paginated list of marketing signals."},"cbpulse__SignalResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique signal identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this signal belongs to"},"signal_type":{"type":"string","title":"Signal Type","description":"Signal category that triggered detection","examples":["low_occupancy","revenue_dip","booking_surge"]},"severity":{"type":"string","title":"Severity","description":"Signal severity: critical, warning, or info","examples":["critical","warning","info"]},"status":{"type":"string","title":"Status","description":"Lifecycle status: new, acknowledged, actioned, dismissed, or expired","examples":["new","acknowledged","actioned","dismissed"]},"title":{"type":"string","title":"Title","description":"Human-readable signal description","examples":["Low occupancy: 32% (threshold 40%)"]},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail","description":"Extended detail or context"},"metric_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metric Name","description":"Metric that triggered this signal","examples":["occupancy_rate","total_revenue"]},"metric_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Metric Value","description":"Observed metric value at detection time"},"threshold_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Threshold Value","description":"Configured threshold value that was breached"},"signal_date":{"type":"string","title":"Signal Date","description":"Date the signal was detected (YYYY-MM-DD)","examples":["2026-02-15"]},"source_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Source Data","description":"Snapshot of data at detection time"},"campaign_proposal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Proposal Id","description":"Linked campaign proposal if signal was actioned"},"actioned_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actioned At","description":"ISO-8601 timestamp when the signal was actioned"},"actioned_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actioned By","description":"User ID who actioned the signal"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"ISO-8601 timestamp when the signal expires"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the signal was created"},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last state change"}},"type":"object","required":["id","business_id","signal_type","severity","status","title","signal_date","created_at","updated_at"],"title":"SignalResponse","description":"A marketing signal with full lifecycle state and metric context.","example":{"business_id":"biz_thefred","created_at":"2026-02-15T14:30:00+00:00","id":"sig_9a2b3c4d","metric_name":"occupancy_rate","metric_value":32.0,"severity":"warning","signal_date":"2026-02-15","signal_type":"low_occupancy","source_data":{"data_snapshot":{"occupancy_rate":32.0}},"status":"new","threshold_value":40.0,"title":"Low occupancy: 32% (threshold 40%)","updated_at":"2026-02-15T14:30:00+00:00"}},"cbpulse__SignalSummaryResponse":{"properties":{"new":{"type":"integer","title":"New","description":"Count of signals in NEW status"},"acknowledged":{"type":"integer","title":"Acknowledged","description":"Count of signals in ACKNOWLEDGED status"},"actioned":{"type":"integer","title":"Actioned","description":"Count of signals in ACTIONED status"},"dismissed":{"type":"integer","title":"Dismissed","description":"Count of signals in DISMISSED status"},"by_severity":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Severity","description":"Signal counts grouped by severity level"}},"type":"object","required":["new","acknowledged","actioned","dismissed","by_severity"],"title":"SignalSummaryResponse","description":"Dashboard-friendly signal count breakdown.","example":{"acknowledged":2,"actioned":8,"by_severity":{"critical":2,"info":12,"warning":4},"dismissed":3,"new":5}},"cbpulse__SingleAccepted":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Job id to poll: GET /api/nosj/jobs/{job_id}. When the child reports status 'done', children[0].article_id is the produced draft."},"child_job_ids":{"items":{"type":"string"},"type":"array","title":"Child Job Ids","description":"Always exactly one — a single article is a batch of one."}},"type":"object","required":["job_id"],"title":"SingleAccepted","description":"Async handoff for Mode 1 (cbpulse#573). Poll the same jobs endpoint Mode 3 uses."},"cbpulse__SlateItem":{"properties":{"title":{"type":"string","title":"Title","description":"Article title / topic"},"item_type":{"type":"string","title":"Item Type","description":"Refresh | New"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"why_now":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Why Now","description":"Timeliness rationale"},"source_material":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Material"},"key_angle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Angle"},"estimated_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Estimated Type","description":"Light refresh / Full rewrite / New from scratch"},"suggested_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suggested Mode","description":"Which mode to send this to: single | refresh"},"refresh_source_post_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Refresh Source Post Id","description":"WP post id to refresh (only when suggested_mode == 'refresh')"},"article_kind":{"$ref":"#/components/schemas/cbpulse__ArticleKind","description":"Typed QA profile for this recommendation (cbpulse#577). NEWS when the candidate came from the VI news scan, FEATURE otherwise.","default":"feature"},"score":{"anyOf":[{"type":"number","maximum":5.0,"minimum":0.0},{"type":"null"}],"title":"Score","description":"Composite score out of 5"},"score_timeliness":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Score Timeliness"},"score_freshness_decay":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Score Freshness Decay"},"score_audience_value":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Score Audience Value"},"score_production_effort":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Score Production Effort"}},"type":"object","required":["title","item_type"],"title":"SlateItem","description":"One ranked editorial recommendation. A menu item, not an article.\n\nMode 4 (#347) NEVER auto-executes: ``suggested_mode`` tells CJ which mode to\nSEND this to (Mode 1 ``single`` for a new article, Mode 2 ``refresh`` for an\nupdate of ``refresh_source_post_id``). The planner only recommends; CJ picks."},"cbpulse__SourceImagePage":{"properties":{"page":{"type":"integer","title":"Page"},"url":{"type":"string","title":"Url"}},"type":"object","required":["page","url"],"title":"SourceImagePage","description":"One page of the extracted source document with a signed cbfiles URL."},"cbpulse__SourceImagesResponse":{"properties":{"record_type":{"type":"string","title":"Record Type"},"record_id":{"type":"string","title":"Record Id"},"pages":{"items":{"$ref":"#/components/schemas/cbpulse__SourceImagePage"},"type":"array","title":"Pages"}},"type":"object","required":["record_type","record_id","pages"],"title":"SourceImagesResponse","description":"Signed URLs for the normalized page images of a persisted document."},"cbpulse__StatusReport":{"properties":{"overdue_signatures":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Overdue Signatures"},"pending_signatures":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Pending Signatures"},"new_onboarding":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"New Onboarding"},"ot_awaiting":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Ot Awaiting"},"expiring_compliance":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Expiring Compliance"},"counts":{"additionalProperties":true,"type":"object","title":"Counts"}},"type":"object","required":["overdue_signatures","pending_signatures","new_onboarding","ot_awaiting","expiring_compliance","counts"],"title":"StatusReport","description":"Aggregated daily HR queue — feeds the Loom HR Queue tab."},"cbpulse__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbpulse__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbpulse__StatusRollupResponse":{"properties":{"amber_days":{"type":"integer","title":"Amber Days"},"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts"},"by_island":{"additionalProperties":{"additionalProperties":{"type":"integer"},"type":"object"},"type":"object","title":"By Island"},"subjects":{"items":{"$ref":"#/components/schemas/cbpulse__SubjectStatusResponse"},"type":"array","title":"Subjects"}},"type":"object","required":["amber_days","counts","by_island","subjects"],"title":"StatusRollupResponse"},"cbpulse__StatusUpdateRequest":{"properties":{"status":{"type":"string","title":"Status","description":"New status: placed, in_transit, delivered, or cancelled","examples":["in_transit"]},"tracking_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tracking Number","description":"Shipping tracking number","examples":["USPS9400111899223456789012"]},"expected_delivery":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expected Delivery","description":"Expected delivery date (YYYY-MM-DD)","examples":["2026-02-14"]}},"type":"object","required":["status"],"title":"StatusUpdateRequest","description":"Payload for updating an order's fulfillment status."},"cbpulse__SubjectStatusResponse":{"properties":{"subject_type":{"type":"string","title":"Subject Type"},"subject_id":{"type":"string","title":"Subject Id"},"label":{"type":"string","title":"Label"},"island":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Island"},"headline_status":{"type":"string","title":"Headline Status"},"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts"},"items":{"items":{"$ref":"#/components/schemas/cbpulse__ComplianceItemResponse"},"type":"array","title":"Items"},"edc_program":{"anyOf":[{"$ref":"#/components/schemas/cbpulse__EdcProgramResponse"},{"type":"null"}]},"recurring_by_cadence":{"additionalProperties":{"items":{"$ref":"#/components/schemas/cbpulse__ComplianceItemResponse"},"type":"array"},"type":"object","title":"Recurring By Cadence","default":{}}},"type":"object","required":["subject_type","subject_id","label","island","headline_status","counts","items"],"title":"SubjectStatusResponse"},"cbpulse__SummaryResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique summary record identifier","examples":["sum_01HXYZ"]},"summary_date":{"type":"string","title":"Summary Date","description":"Date this summary covers (YYYY-MM-DD)","examples":["2026-02-15"]},"executive_summary":{"type":"string","title":"Executive Summary","description":"AI-generated narrative overview of the day's operations","examples":["Portfolio revenue reached $18,450 across all properties. The Fred led with $12,300 in F&B sales."]},"sections":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Sections","description":"Structured breakdown by topic: revenue, labor, alerts, occupancy, etc.","examples":[{"labor":"Combined labor cost $4,200 (22.8%)","revenue":"Total portfolio revenue $18,450"}]},"total_portfolio_revenue":{"type":"number","title":"Total Portfolio Revenue","description":"Total revenue across all businesses for the summary date","examples":[18450.0]},"total_labor_cost":{"type":"number","title":"Total Labor Cost","description":"Total labor cost across all businesses for the summary date","examples":[4200.0]},"avg_health_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Health Score","description":"Average health score across businesses","examples":[78.5]},"active_alerts_count":{"type":"integer","title":"Active Alerts Count","description":"Number of unresolved alerts at time of summary generation","examples":[2]},"delivered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivered At","description":"ISO-8601 timestamp when the summary was emailed to stakeholders","examples":["2026-02-15T07:00:00Z"]},"delivery_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivery Status","description":"Email delivery status: pending, sent, or failed","examples":["sent","pending","failed"]},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the summary was generated","examples":["2026-02-15T06:55:00Z"]}},"type":"object","required":["id","summary_date","executive_summary","total_portfolio_revenue","total_labor_cost","active_alerts_count","created_at"],"title":"SummaryResponse","description":"AI-generated daily executive summary with portfolio metrics and narrative sections.","example":{"active_alerts_count":2,"avg_health_score":78.5,"created_at":"2026-02-15T06:55:00Z","delivered_at":"2026-02-15T07:00:00Z","delivery_status":"sent","executive_summary":"Portfolio revenue reached $18,450 across all properties. The Fred led with $12,300 in F&B sales. The Saint maintained 87.5% occupancy with 7 of 8 rooms sold. Two labor cost warnings were triggered for Lovango.","id":"sum_01HXYZ","sections":{"alerts":"2 active warnings, 0 critical alerts.","labor":"Combined labor cost $4,200 (22.8% of revenue). Lovango exceeded 30% threshold.","occupancy":"The Saint: 87.5% (7/8 rooms). The Fred: 72% (18/25 rooms).","revenue":"Total portfolio revenue $18,450. The Fred: $12,300, The Saint: $4,150, Lovango: $2,000."},"summary_date":"2026-02-15","total_labor_cost":4200.0,"total_portfolio_revenue":18450.0}},"cbpulse__SupplyBudgetOut":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"year_month":{"type":"string","title":"Year Month"},"amount":{"type":"number","title":"Amount"},"set_by":{"type":"string","title":"Set By"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","business_id","year_month","amount","set_by","created_at","updated_at"],"title":"SupplyBudgetOut"},"cbpulse__SupplyCategoryOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug"},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"},"purpose":{"type":"string","title":"Purpose"},"sort_order":{"type":"integer","title":"Sort Order","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","slug","purpose","created_at"],"title":"SupplyCategoryOut"},"cbpulse__SupplyOrderCreate":{"properties":{"vendor_id":{"type":"string","title":"Vendor Id"},"request_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Request Ids"},"order_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order Ref"},"total_cost":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Cost"},"payment_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Method"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["vendor_id","request_ids"],"title":"SupplyOrderCreate"},"cbpulse__SupplyOrderOut":{"properties":{"id":{"type":"string","title":"Id"},"vendor_id":{"type":"string","title":"Vendor Id"},"order_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order Ref"},"total_cost":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Cost"},"payment_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Method"},"placed_by":{"type":"string","title":"Placed By"},"approved_by":{"type":"string","title":"Approved By"},"status":{"type":"string","title":"Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"request_ids":{"items":{"type":"string"},"type":"array","title":"Request Ids","default":[]}},"type":"object","required":["id","vendor_id","placed_by","approved_by","status","created_at","updated_at"],"title":"SupplyOrderOut"},"cbpulse__SupplyOrderUpdate":{"properties":{"order_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order Ref"},"total_cost":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Cost"},"payment_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Method"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"SupplyOrderUpdate"},"cbpulse__SupplyRequestCreate":{"properties":{"business_id":{"type":"string","title":"Business Id"},"category_id":{"type":"string","title":"Category Id"},"purpose":{"type":"string","pattern":"^(operational|retail)$","title":"Purpose"},"description":{"type":"string","maxLength":2000,"minLength":1,"title":"Description"},"quantity":{"type":"integer","minimum":1.0,"title":"Quantity","default":1},"urgency":{"type":"string","pattern":"^(routine|this_week|urgent)$","title":"Urgency","default":"routine"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"preferred_vendor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preferred Vendor Id"},"budget_estimate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Budget Estimate"},"unit_cost":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Unit Cost"},"retail_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Retail Price"},"is_recurring":{"type":"boolean","title":"Is Recurring","default":false},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"photo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Photo Url"}},"type":"object","required":["business_id","category_id","purpose","description"],"title":"SupplyRequestCreate"},"cbpulse__SupplyRequestOut":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"requested_by":{"type":"string","title":"Requested By"},"category_id":{"type":"string","title":"Category Id"},"purpose":{"type":"string","title":"Purpose"},"description":{"type":"string","title":"Description"},"quantity":{"type":"integer","title":"Quantity"},"urgency":{"type":"string","title":"Urgency"},"status":{"type":"string","title":"Status"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"preferred_vendor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preferred Vendor Id"},"budget_estimate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Budget Estimate"},"unit_cost":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Unit Cost"},"retail_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Retail Price"},"is_recurring":{"type":"boolean","title":"Is Recurring"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"photo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Photo Url"},"approved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved By"},"approved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Approved At"},"rejected_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejected By"},"rejected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Rejected At"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"},"order_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","business_id","requested_by","category_id","purpose","description","quantity","urgency","status","is_recurring","created_at","updated_at"],"title":"SupplyRequestOut"},"cbpulse__SupplyShipmentCreate":{"properties":{"route_type":{"type":"string","title":"Route Type"},"request_ids":{"items":{"type":"string"},"type":"array","title":"Request Ids"},"carrier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Carrier"},"tracking_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tracking Number"},"estimated_delivery":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Estimated Delivery"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["route_type"],"title":"SupplyShipmentCreate"},"cbpulse__SupplyShipmentOut":{"properties":{"id":{"type":"string","title":"Id"},"route_type":{"type":"string","title":"Route Type"},"status":{"type":"string","title":"Status"},"carrier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Carrier"},"tracking_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tracking Number"},"estimated_delivery":{"anyOf":[{},{"type":"null"}],"title":"Estimated Delivery"},"actual_delivery":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Actual Delivery"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_by":{"type":"string","title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"request_ids":{"items":{"type":"string"},"type":"array","title":"Request Ids","default":[]},"history":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"History","default":[]}},"type":"object","required":["id","route_type","status","created_by","created_at","updated_at"],"title":"SupplyShipmentOut"},"cbpulse__SupplyVendorCreate":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name"},"vendor_type":{"type":"string","title":"Vendor Type"},"ships_to_usvi":{"type":"boolean","title":"Ships To Usvi","default":false},"lead_time_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lead Time Days"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name","vendor_type"],"title":"SupplyVendorCreate"},"cbpulse__SupplyVendorOut":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"vendor_type":{"type":"string","title":"Vendor Type"},"ships_to_usvi":{"type":"boolean","title":"Ships To Usvi"},"lead_time_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lead Time Days"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","vendor_type","ships_to_usvi","is_active","created_at","updated_at"],"title":"SupplyVendorOut"},"cbpulse__SupplyVendorUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"vendor_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Type"},"ships_to_usvi":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ships To Usvi"},"lead_time_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lead Time Days"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"SupplyVendorUpdate"},"cbpulse__SyncTriggerResponse":{"properties":{"source":{"type":"string","title":"Source","description":"Integration source that was synced","examples":["clover","cloudbeds","quickbooks"]},"records_synced":{"type":"integer","title":"Records Synced","description":"Number of records synced in this run","examples":[83]},"status":{"type":"string","title":"Status","description":"Sync result: completed or failed with error details","examples":["completed","failed: Connection timeout"]}},"type":"object","required":["source","records_synced","status"],"title":"SyncTriggerResponse","description":"Result of a manual sync trigger for an integration source.","example":{"records_synced":83,"source":"clover","status":"completed"}},"cbpulse__TaskActivityOut":{"properties":{"id":{"type":"string","title":"Id"},"task_id":{"type":"string","title":"Task Id"},"actor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor"},"actor_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Name"},"action":{"type":"string","title":"Action"},"detail":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Detail"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","task_id","actor","actor_name","action","detail","created_at"],"title":"TaskActivityOut"},"cbpulse__TaskCommentOut":{"properties":{"id":{"type":"string","title":"Id"},"task_id":{"type":"string","title":"Task Id"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"author_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Name"},"body":{"type":"string","title":"Body"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","task_id","author","author_name","body","created_at"],"title":"TaskCommentOut"},"cbpulse__TaskCreate":{"properties":{"title":{"type":"string","maxLength":200,"minLength":1,"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"priority":{"type":"string","title":"Priority","default":"medium"},"dept":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"},"assignee":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee"},"assignee_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Assignee Name"},"due_date":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Due Date"}},"type":"object","required":["title"],"title":"TaskCreate"},"cbpulse__TaskDetailOut":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"priority":{"type":"string","title":"Priority"},"status":{"type":"string","title":"Status"},"dept":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"},"assignee":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee"},"assignee_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee Name"},"due_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Date"},"position":{"type":"integer","title":"Position"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"comments":{"items":{"$ref":"#/components/schemas/cbpulse__TaskCommentOut"},"type":"array","title":"Comments","default":[]},"activity":{"items":{"$ref":"#/components/schemas/cbpulse__TaskActivityOut"},"type":"array","title":"Activity","default":[]}},"type":"object","required":["id","business_id","title","description","priority","status","dept","assignee","assignee_name","due_date","position","created_by","created_by_name","completed_at","created_at","updated_at"],"title":"TaskDetailOut"},"cbpulse__TaskOut":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"priority":{"type":"string","title":"Priority"},"status":{"type":"string","title":"Status"},"dept":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"},"assignee":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee"},"assignee_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee Name"},"due_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Date"},"position":{"type":"integer","title":"Position"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","business_id","title","description","priority","status","dept","assignee","assignee_name","due_date","position","created_by","created_by_name","completed_at","created_at","updated_at"],"title":"TaskOut"},"cbpulse__TaskPatch":{"properties":{"title":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"priority":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Priority"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"dept":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dept"},"assignee":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee"},"assignee_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee Name"},"due_date":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Due Date"},"position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Position"}},"type":"object","title":"TaskPatch"},"cbpulse__TemplateCreate":{"properties":{"vendor_name":{"type":"string","title":"Vendor Name"},"doc_type":{"type":"string","title":"Doc Type","default":"invoice"},"extraction_prompt":{"type":"string","title":"Extraction Prompt"},"format_description":{"type":"string","title":"Format Description","default":""},"confidence":{"type":"number","title":"Confidence","default":0.0},"complexity":{"type":"string","title":"Complexity","default":"low"},"classified_by":{"type":"string","title":"Classified By","default":""}},"type":"object","required":["vendor_name","extraction_prompt"],"title":"TemplateCreate"},"cbpulse__TemplateListResponse":{"properties":{"templates":{"items":{"$ref":"#/components/schemas/cbpulse__TemplateResponse"},"type":"array","title":"Templates"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["templates","total"],"title":"TemplateListResponse"},"cbpulse__TemplateResponse":{"properties":{"id":{"type":"string","title":"Id"},"vendor_name":{"type":"string","title":"Vendor Name"},"doc_type":{"type":"string","title":"Doc Type"},"extraction_prompt":{"type":"string","title":"Extraction Prompt"},"format_description":{"type":"string","title":"Format Description"},"confidence":{"type":"number","title":"Confidence"},"complexity":{"type":"string","title":"Complexity"},"classified_by":{"type":"string","title":"Classified By"},"use_count":{"type":"integer","title":"Use Count"},"human_reviewed":{"type":"boolean","title":"Human Reviewed"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","vendor_name","doc_type","extraction_prompt","format_description","confidence","complexity","classified_by","use_count","human_reviewed","created_at","updated_at"],"title":"TemplateResponse"},"cbpulse__TemplateUpdate":{"properties":{"extraction_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extraction Prompt"},"format_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format Description"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"},"complexity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Complexity"},"human_reviewed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Human Reviewed"}},"type":"object","title":"TemplateUpdate"},"cbpulse__TerminateRequest":{"properties":{"termination_date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","title":"Termination Date","description":"ISO date (YYYY-MM-DD) the termination took effect."},"separation_type":{"type":"string","enum":["resignation","termination"],"title":"Separation Type","description":"Resignation/termination discriminator (cbpulse#458). Defaults to 'termination' so legacy callers that omit it stay backwards-compatible.","default":"termination"},"last_day_worked":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Last Day Worked","description":"ISO date the employee last actually worked. Distinct from termination_date (the record date). May be future-dated up to 90 days. Falls back to termination_date when omitted (legacy callers)."},"resignation_letter_file_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resignation Letter File Id","description":"cbfiles file_id of the uploaded resignation letter. Only valid when separation_type='resignation'; rejected otherwise. Upload first via POST /api/hr/separations/letter, then pass the returned file_id here."},"internal_reason_category":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Internal Reason Category","description":"Internal-only categorization: 'performance' | 'conduct' | 'position_eliminated' | 'other'. Never sent to the employee."},"internal_reason_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Internal Reason Notes","description":"Internal-only free-text notes. Never sent to the employee."},"primary_statutory_ground":{"anyOf":[{"type":"integer","maximum":9.0,"minimum":1.0},{"type":"null"}],"title":"Primary Statutory Ground","description":"24 V.I.C. § 76(a) ground code (1-9). Required when separation_type='termination'; omit for resignations. See src/services/hr/statutory_grounds.py for the human label per code."},"summary":{"type":"string","maxLength":4000,"minLength":1,"title":"Summary","description":"Manager's stated reason for termination. Required."},"additional_grounds":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Additional Grounds","description":"Optional additional § 76(a) ground codes (1-9) cited."},"prior_writeup_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Prior Writeup Ids","description":"Optional list of writeup IDs cited as the prior-discipline trail supporting this termination decision. Stored verbatim for the defensibility narrative — joining writeups by employee_id later loses the 'these specific ones were cited' semantic."},"final_pay_date":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Final Pay Date","description":"Optional ISO date the final paycheck issues/issued."},"defensibility_score":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Defensibility Score","description":"Optional 0-100 score for triage."},"risk_flags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Risk Flags","description":"Optional free-form risk flags surfaced at decision time."},"resolves_alert_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolves Alert Id","description":"Optional id of the open ``informal_departure`` HR alert (cbpulse#405) this separation resolves. When set, the alert is confirmed in the same transaction with notes pointing at the new separation record. Tolerates the wizard-open→submit race: an alert that closed in the interim doesn't fail the separation, it just reports ``alert_already_resolved=true``. cbpulse#463."}},"type":"object","required":["termination_date","summary"],"title":"TerminateRequest"},"cbpulse__TerminateResponse":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"termination_id":{"type":"string","title":"Termination Id"},"previous_status":{"type":"string","title":"Previous Status"},"new_status":{"type":"string","title":"New Status"},"termination_date":{"type":"string","title":"Termination Date"},"separation_type":{"type":"string","title":"Separation Type","default":"termination"},"last_day_worked":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Day Worked"},"primary_statutory_ground":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Primary Statutory Ground"},"primary_statutory_ground_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Statutory Ground Label"},"decided_by_user_id":{"type":"string","title":"Decided By User Id"},"decided_by_name":{"type":"string","title":"Decided By Name"},"created_at":{"type":"string","title":"Created At"},"resolved_alert_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved Alert Id","description":"Echoes the ``informal_departure`` alert id that was confirmed by this separation, or NULL when no alert was wired up / it had already closed. cbpulse#463."},"alert_already_resolved":{"type":"boolean","title":"Alert Already Resolved","description":"True when ``resolves_alert_id`` was supplied but the alert had already closed between wizard-open and submit (race). The separation still saved cleanly; the UI shows 'alert was already resolved.' cbpulse#463.","default":false}},"type":"object","required":["employee_id","termination_id","previous_status","new_status","termination_date","decided_by_user_id","decided_by_name","created_at"],"title":"TerminateResponse"},"cbpulse__TerminationNoticeResponse":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"termination_id":{"type":"string","title":"Termination Id"},"boldsign_envelope_id":{"type":"string","title":"Boldsign Envelope Id"},"status":{"type":"string","title":"Status","default":"sent"},"idempotent_replay":{"type":"boolean","title":"Idempotent Replay","default":false}},"type":"object","required":["employee_id","termination_id","boldsign_envelope_id"],"title":"TerminationNoticeResponse"},"cbpulse__TerminationRiskResponse":{"properties":{"employee_id":{"type":"string","title":"Employee Id"},"employee_name":{"type":"string","title":"Employee Name"},"risk_level":{"type":"string","title":"Risk Level","description":"low, medium, high, or do_not_proceed"},"assessment":{"type":"string","title":"Assessment","description":"AI narrative or rule-based summary"},"wda_compliance":{"additionalProperties":true,"type":"object","title":"Wda Compliance","description":"Dict of WDA compliance checks (bool flags and counts)"},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning","description":"Set when using fallback instead of AI"}},"type":"object","required":["employee_id","employee_name","risk_level","assessment","wda_compliance"],"title":"TerminationRiskResponse","description":"AI-powered termination risk assessment against WDA §76."},"cbpulse__ThresholdCreate":{"properties":{"signal_type":{"type":"string","title":"Signal Type","description":"Signal type to trigger","examples":["low_occupancy","revenue_dip"]},"metric_name":{"type":"string","title":"Metric Name","description":"Metric to evaluate","examples":["occupancy_rate","total_revenue","labor_percent"]},"operator":{"type":"string","title":"Operator","description":"Comparison operator","examples":["<",">","<=",">="]},"threshold_value":{"type":"number","title":"Threshold Value","description":"Threshold value","examples":[40.0,10000.0]},"severity":{"type":"string","title":"Severity","description":"Severity for generated signals","examples":["critical","warning","info"]}},"type":"object","required":["signal_type","metric_name","operator","threshold_value","severity"],"title":"ThresholdCreate","description":"Payload to create a new signal threshold.","example":{"metric_name":"occupancy_rate","operator":"<","severity":"warning","signal_type":"low_occupancy","threshold_value":40.0}},"cbpulse__ThresholdResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique threshold identifier"},"business_id":{"type":"string","title":"Business Id","description":"Business this threshold applies to"},"signal_type":{"type":"string","title":"Signal Type","description":"Signal type triggered when threshold is breached","examples":["low_occupancy","revenue_dip"]},"metric_name":{"type":"string","title":"Metric Name","description":"Metric to evaluate against","examples":["occupancy_rate","total_revenue","labor_percent"]},"operator":{"type":"string","title":"Operator","description":"Comparison operator","examples":[">",">=","<","<=","==","!="]},"threshold_value":{"type":"number","title":"Threshold Value","description":"Threshold value to compare against","examples":[40.0,10000.0]},"severity":{"type":"string","title":"Severity","description":"Severity assigned to signals from this threshold","examples":["critical","warning","info"]},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether this threshold is active"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the threshold was created"},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last modification"}},"type":"object","required":["id","business_id","signal_type","metric_name","operator","threshold_value","severity","is_enabled","created_at","updated_at"],"title":"ThresholdResponse","description":"A configurable signal threshold for a business.","example":{"business_id":"biz_thefred","created_at":"2026-02-15T14:30:00+00:00","id":"thr_1a2b3c4d","is_enabled":true,"metric_name":"occupancy_rate","operator":"<","severity":"warning","signal_type":"low_occupancy","threshold_value":40.0,"updated_at":"2026-02-15T14:30:00+00:00"}},"cbpulse__ThresholdUpdate":{"properties":{"signal_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signal Type","description":"Signal type to trigger"},"metric_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metric Name","description":"Metric to evaluate"},"operator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator","description":"Comparison operator"},"threshold_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Threshold Value","description":"Threshold value"},"severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity","description":"Severity level"},"is_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Enabled","description":"Enable or disable"}},"type":"object","title":"ThresholdUpdate","description":"Partial update payload for a signal threshold."},"cbpulse__TickResponse":{"properties":{"jobs_executed":{"type":"integer","title":"Jobs Executed","description":"Number of due jobs that were executed in this tick cycle","examples":[3]},"results":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Results","description":"Per-job execution results with status, record count, and duration"}},"type":"object","required":["jobs_executed","results"],"title":"TickResponse","description":"Result of a tick cycle showing how many jobs were executed and their outcomes.","example":{"jobs_executed":3,"results":[{"duration":1.234,"job":"sync_clover","records":42,"status":"completed"},{"duration":0.312,"job":"check_alerts","records":5,"status":"completed"},{"duration":0.891,"job":"aggregate_financials","records":6,"status":"completed"}]}},"cbpulse__TicketResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique maintenance ticket identifier","examples":["tkt_m3n4o5p6"]},"business_id":{"type":"string","title":"Business Id","description":"Business/property this ticket belongs to","examples":["biz_thefred"]},"title":{"type":"string","title":"Title","description":"Short summary of the maintenance issue","examples":["Leaking faucet in Room 204"]},"description":{"type":"string","title":"Description","description":"Detailed description of the issue and its impact","examples":["Guest reported steady drip from bathroom sink faucet. Water pooling on counter."]},"priority":{"type":"string","title":"Priority","description":"Ticket priority: low, medium, high, or urgent","examples":["high"]},"status":{"type":"string","title":"Status","description":"Lifecycle status: open, in_progress, on_hold, resolved, or closed","examples":["in_progress"]},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Issue category, e.g. 'plumbing', 'electrical', 'hvac', 'general'","examples":["plumbing"]},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location","description":"Specific location within the property, e.g. 'Room 204', 'Pool deck'","examples":["Room 204"]},"reported_by":{"type":"string","title":"Reported By","description":"User ID who reported the issue","examples":["user_mgr_01"]},"reported_at":{"type":"string","title":"Reported At","description":"ISO-8601 timestamp when the issue was reported","examples":["2026-02-12T08:45:00+00:00"]},"assigned_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To","description":"User ID of the person assigned to fix the issue","examples":["user_maint_02"]},"assigned_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned At","description":"ISO-8601 timestamp when the ticket was assigned","examples":["2026-02-12T09:00:00+00:00"]},"resolved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved At","description":"ISO-8601 timestamp when the ticket was resolved","examples":["2026-02-12T15:30:00+00:00"]},"resolved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved By","description":"User ID who resolved the ticket","examples":["user_maint_02"]},"resolution_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Notes","description":"Notes explaining how the issue was fixed","examples":["Replaced faucet cartridge and tested for leaks. No further drip."]},"closed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Closed At","description":"ISO-8601 timestamp when the ticket was closed after verification","examples":["2026-02-13T10:00:00+00:00"]},"closed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Closed By","description":"User ID who closed the ticket","examples":["user_mgr_01"]},"photos":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Photos","description":"List of photo URLs documenting the issue","examples":[["https://files.campaignbrain.dev/tickets/tkt_m3n4o5p6/photo1.jpg"]]},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 timestamp when the record was created","examples":["2026-02-12T08:45:00+00:00"]},"updated_at":{"type":"string","title":"Updated At","description":"ISO-8601 timestamp of the last update","examples":["2026-02-12T09:00:00+00:00"]}},"type":"object","required":["id","business_id","title","description","priority","status","reported_by","reported_at","created_at","updated_at"],"title":"TicketResponse","description":"Full maintenance ticket with lifecycle state, assignment, and resolution details.","example":{"assigned_at":"2026-02-12T09:00:00+00:00","assigned_to":"user_maint_02","business_id":"biz_thefred","category":"plumbing","created_at":"2026-02-12T08:45:00+00:00","description":"Guest reported steady drip from bathroom sink faucet. Water pooling on counter. Needs washer replacement or cartridge swap.","id":"tkt_m3n4o5p6","location":"Room 204","photos":["https://files.campaignbrain.dev/tickets/tkt_m3n4o5p6/photo1.jpg"],"priority":"high","reported_at":"2026-02-12T08:45:00+00:00","reported_by":"user_mgr_01","status":"in_progress","title":"Leaking faucet in Room 204","updated_at":"2026-02-12T09:00:00+00:00"}},"cbpulse__TimesheetExceptionResponse":{"properties":{"id":{"type":"string","title":"Id"},"pay_period_id":{"type":"string","title":"Pay Period Id"},"business_id":{"type":"string","title":"Business Id"},"employee_id":{"type":"string","title":"Employee Id"},"shift_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shift Id"},"exception_type":{"type":"string","title":"Exception Type","description":"missing_clockout, overtime_unapproved, gap, duplicate"},"description":{"type":"string","title":"Description"},"severity":{"type":"string","title":"Severity","description":"info, warning, or blocking (blocks approval)"},"status":{"type":"string","title":"Status","description":"open or resolved"},"resolved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved By"},"resolved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved At"},"resolution_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Notes"}},"type":"object","required":["id","pay_period_id","business_id","employee_id","exception_type","description","severity","status"],"title":"TimesheetExceptionResponse","description":"Timesheet exception requiring review before payroll approval."},"cbpulse__TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"JWT access token for API authentication via Bearer header"},"refresh_token":{"type":"string","title":"Refresh Token","description":"JWT refresh token for obtaining new access tokens"},"token_type":{"type":"string","title":"Token Type","description":"Token type, always 'bearer'","default":"bearer"},"expires_in":{"type":"integer","title":"Expires In","description":"Access token lifetime in seconds"}},"type":"object","required":["access_token","refresh_token","expires_in"],"title":"TokenResponse","description":"JWT token pair returned after successful authentication or refresh.","example":{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","token_type":"bearer"}},"cbpulse__TradeNameResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"parent_entity_id":{"type":"string","title":"Parent Entity Id"},"vip_business_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vip Business Id"},"expiration_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiration Date"},"renewal_status":{"type":"string","title":"Renewal Status"},"days_until_expiration":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Until Expiration"}},"type":"object","required":["id","name","parent_entity_id","vip_business_id","expiration_date","renewal_status"],"title":"TradeNameResponse"},"cbpulse__UnmappedVendor":{"properties":{"vendor_norm":{"type":"string","title":"Vendor Norm"},"raw_names":{"items":{"type":"string"},"type":"array","title":"Raw Names"},"count":{"type":"integer","title":"Count"},"total_amount":{"type":"number","title":"Total Amount"},"last_invoice_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Invoice Date"}},"type":"object","required":["vendor_norm","raw_names","count","total_amount"],"title":"UnmappedVendor"},"cbpulse__UnreadCountResponse":{"properties":{"count":{"type":"integer","title":"Count","description":"Number of unread notifications"}},"type":"object","required":["count"],"title":"UnreadCountResponse","description":"Lightweight unread count for bell badge.","example":{"count":5}},"cbpulse__UpdateArticleRequest":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"New title","examples":["Updated Pool Hours"]},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"New body content"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"New category"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"New tags list"},"is_published":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Published","description":"Publish or unpublish"}},"type":"object","title":"UpdateArticleRequest","description":"Payload for partial update of article fields.","example":{"is_published":true,"title":"Updated Pool Hours"}},"cbpulse__UpdateConsentRequest":{"properties":{"consent_email":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Consent Email","description":"Email marketing consent"},"consent_sms":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Consent Sms","description":"SMS marketing consent"},"consent_whatsapp":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Consent Whatsapp","description":"WhatsApp marketing consent"}},"type":"object","title":"UpdateConsentRequest","description":"Update marketing consent flags."},"cbpulse__UpdateDraftRequest":{"properties":{"copy_ig":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Copy Ig"},"copy_fb":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Copy Fb"},"selected_photo_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Selected Photo Path"},"suggested_publish_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suggested Publish At"}},"type":"object","title":"UpdateDraftRequest","description":"Explicit allowlist — only these 4 fields are ever patchable.\n\nDeliberately does NOT include `status` or `vista_post_ids`: those are\nsystem-owned transitions (generate/regenerate/push/discard), not\nuser-editable fields. `patch_one` below builds the update kwargs from\n``UpdateDraftRequest.model_dump()`` alone, so a client cannot smuggle an\nextra `status`/`vista_post_ids` key through the JSON body — Pydantic\nsilently drops fields not declared on the model."},"cbpulse__UpdateIncidentRequest":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity"},"employee_statement":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employee Statement"},"resolution_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Notes"},"wda_ground":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wda Ground"},"linked_writeup_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linked Writeup Id"}},"type":"object","title":"UpdateIncidentRequest"},"cbpulse__UpdateOrderRequest":{"properties":{"vendor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"invoice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice"},"staff":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Staff"},"lines":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbpulse__LineItem"},"type":"array"},{"type":"null"}],"title":"Lines"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"UpdateOrderRequest"},"cbpulse__UpdateStatusRequest":{"properties":{"status":{"type":"string","title":"Status","description":"New status: open, snoozed, closed"},"snoozed_until":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snoozed Until","description":"ISO-8601 datetime for snooze expiry (required when status=snoozed)"}},"type":"object","required":["status"],"title":"UpdateStatusRequest","description":"Payload for updating conversation status."},"cbpulse__UpdateTagsRequest":{"properties":{"add":{"items":{"type":"string"},"type":"array","title":"Add","description":"Tags to add to the guest profile","examples":[["vip-event","influencer"]]},"remove":{"items":{"type":"string"},"type":"array","title":"Remove","description":"Tags to remove from the guest profile","examples":[["returning"]]}},"type":"object","title":"UpdateTagsRequest","description":"Add or remove tags from a guest profile."},"cbpulse__UpdateTicketRequest":{"properties":{"priority":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Priority","description":"New priority: low, medium, high, or urgent","examples":["urgent"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Updated issue description","examples":["Walk-in cooler compressor tripping breaker every 30 minutes. Food safety risk."]},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Updated issue category","examples":["electrical"]},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location","description":"Updated location within the property","examples":["Kitchen walk-in"]},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"New status: open, in_progress, on_hold, resolved, or closed","examples":["in_progress"]}},"type":"object","title":"UpdateTicketRequest","description":"Payload for partial update of ticket fields.","example":{"category":"electrical","location":"Kitchen walk-in","priority":"urgent","status":"in_progress"}},"cbpulse__UpdateWriteUpRequest":{"properties":{"violation_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Violation Category"},"wda_ground":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wda Ground"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"corrective_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Corrective Action"},"consequences":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consequences"},"discipline_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Discipline Level"},"defensibility_reasoning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Defensibility Reasoning"},"incident_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Incident Date"}},"type":"object","title":"UpdateWriteUpRequest","description":"Partial update to a draft write-up. All fields optional; only draft\nwriteups can be edited."},"cbpulse__UploadRequest":{"properties":{"content_base64":{"type":"string","minLength":1,"title":"Content Base64"},"content_type":{"type":"string","minLength":1,"title":"Content Type"},"filename":{"type":"string","minLength":1,"title":"Filename"},"business_id":{"type":"string","minLength":1,"title":"Business Id"}},"type":"object","required":["content_base64","content_type","filename","business_id"],"title":"UploadRequest"},"cbpulse__UserPreferenceRow":{"properties":{"user_id":{"type":"string","title":"User Id"},"email":{"type":"string","title":"Email"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"role":{"type":"string","title":"Role"},"is_active":{"type":"boolean","title":"Is Active"},"has_explicit_preferences":{"type":"boolean","title":"Has Explicit Preferences","description":"False when this user has no notification_preferences row. The dispatcher treats a missing row as opted IN, so these users receive critical alert email without ever having opted in -- the cbpulse#549 trap. Surface it in the UI rather than showing an indistinguishable 'on' toggle."},"preferences":{"$ref":"#/components/schemas/cbpulse__PreferenceResponse","description":"Effective preferences: the stored row, or the defaults that would apply if none exists."}},"type":"object","required":["user_id","email","first_name","last_name","role","is_active","has_explicit_preferences","preferences"],"title":"UserPreferenceRow","description":"One user's alert-email settings, for the admin roster view.","example":{"email":"cj@stj.media","first_name":"CJ","has_explicit_preferences":true,"is_active":true,"last_name":"Galdes","preferences":{"email_critical":true,"email_enabled":true,"email_info":false,"email_warning":true,"in_app_enabled":true},"role":"sysop","user_id":"usr_a1b2c3d4"}},"cbpulse__UserSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Unique user identifier"},"email":{"type":"string","title":"Email","description":"User email address"},"first_name":{"type":"string","title":"First Name","description":"User first name"},"last_name":{"type":"string","title":"Last Name","description":"User last name"},"role":{"type":"string","title":"Role","description":"System access level"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the user account is enabled"},"functional_roles":{"items":{"type":"string"},"type":"array","title":"Functional Roles","description":"Functional roles controlling module visibility"}},"type":"object","required":["id","email","first_name","last_name","role","is_active"],"title":"UserSummary","description":"User record with functional roles.","example":{"email":"gm@thefred.vi","first_name":"Maria","functional_roles":["finance","operations"],"id":"usr_abc123","is_active":true,"last_name":"Santos","role":"manager"}},"cbpulse__ValidateRequest":{"properties":{"brand":{"type":"string","title":"Brand","description":"Brand slug","examples":["the_saint","villas"]},"platform":{"type":"string","title":"Platform","description":"Target platform (instagram/ig, facebook/fb)","examples":["instagram","fb"]},"copy":{"type":"string","title":"Copy","description":"The post copy to validate"},"photo_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Photo Path","description":"Optional asset path (checked for brand reference)"}},"type":"object","required":["brand","platform","copy"],"title":"ValidateRequest","example":{"brand":"the_saint","copy":"Sunset on the dock. #stjohn #usvi #island #cocktails","platform":"instagram"}},"cbpulse__ValidateResponse":{"properties":{"ok":{"type":"boolean","title":"Ok","description":"True iff there are no error-severity violations"},"violations":{"items":{"$ref":"#/components/schemas/cbpulse__ViolationResponse"},"type":"array","title":"Violations"}},"type":"object","required":["ok"],"title":"ValidateResponse","example":{"ok":false,"violations":[{"code":"hashtag_count","detail":"found 1, allowed 3-5","message":"instagram posts need 3–5 hashtags; found 1.","severity":"error"}]}},"cbpulse__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbpulse__VarianceMetric":{"properties":{"name":{"type":"string","title":"Name","description":"KPI name being measured"},"current":{"type":"number","title":"Current","description":"Current period metric value"},"baseline_avg":{"type":"number","title":"Baseline Avg","description":"Rolling average baseline for comparison"},"deviation_pct":{"type":"number","title":"Deviation Pct","description":"Percentage deviation from baseline average"},"is_anomaly":{"type":"boolean","title":"Is Anomaly","description":"Whether deviation exceeds anomaly threshold"}},"type":"object","required":["name","current","baseline_avg","deviation_pct","is_anomaly"],"title":"VarianceMetric","example":{"baseline_avg":7850.0,"current":8500.0,"deviation_pct":8.3,"is_anomaly":false,"name":"total_revenue"}},"cbpulse__VarianceReport":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Unique business identifier"},"business_name":{"type":"string","title":"Business Name","description":"Business display name"},"metrics":{"items":{"$ref":"#/components/schemas/cbpulse__VarianceMetric"},"type":"array","title":"Metrics","description":"All tracked KPI variances for this business"},"anomalies":{"items":{"$ref":"#/components/schemas/cbpulse__AnomalyItem"},"type":"array","title":"Anomalies","description":"Metrics that exceeded anomaly thresholds"}},"type":"object","required":["business_id","business_name","metrics","anomalies"],"title":"VarianceReport","example":{"anomalies":[],"business_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","business_name":"The Fred","metrics":[]}},"cbpulse__VarianceResponse":{"properties":{"business_id":{"type":"string","title":"Business Id"},"current_period":{"type":"string","title":"Current Period","description":"Date range: YYYY-MM-DD..YYYY-MM-DD"},"previous_period":{"type":"string","title":"Previous Period","description":"Date range: YYYY-MM-DD..YYYY-MM-DD"},"current_gross":{"type":"number","title":"Current Gross"},"previous_gross":{"type":"number","title":"Previous Gross"},"variance_dollars":{"type":"number","title":"Variance Dollars","description":"Absolute difference in USD"},"variance_percent":{"type":"number","title":"Variance Percent","description":"Percentage change"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"}},"type":"object","required":["business_id","current_period","previous_period","current_gross","previous_gross","variance_dollars","variance_percent"],"title":"VarianceResponse","description":"Period-over-period payroll variance for a business."},"cbpulse__VehicleCreate":{"properties":{"vin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vin"},"make":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Make"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"plate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plate"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location"},"owning_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owning Entity Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"VehicleCreate"},"cbpulse__VehicleResponse":{"properties":{"id":{"type":"string","title":"Id"},"vin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vin"},"make":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Make"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"plate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plate"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location"},"owning_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owning Entity Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","vin","make","model","color","year","plate","location","owning_entity_id","notes","is_active"],"title":"VehicleResponse"},"cbpulse__VehicleUpdate":{"properties":{"vin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vin"},"make":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Make"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"plate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plate"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location"},"owning_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owning Entity Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"VehicleUpdate"},"cbpulse__VendorCreate":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name"},"category":{"type":"string","title":"Category","description":"food / drink / other"},"aliases":{"items":{"type":"string"},"type":"array","title":"Aliases"}},"type":"object","required":["name","category"],"title":"VendorCreate"},"cbpulse__VendorResponse":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"category":{"type":"string","title":"Category"},"active":{"type":"boolean","title":"Active"},"aliases":{"items":{"type":"string"},"type":"array","title":"Aliases"}},"type":"object","required":["id","name","category","active"],"title":"VendorResponse"},"cbpulse__VendorUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Name"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"food / drink / other"},"aliases":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Aliases"},"active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Active"}},"type":"object","title":"VendorUpdate"},"cbpulse__ViolationResponse":{"properties":{"code":{"type":"string","title":"Code","description":"Machine-readable check code"},"severity":{"type":"string","title":"Severity","description":"'error' (fails the post) or 'warning'"},"message":{"type":"string","title":"Message","description":"Human-readable explanation"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail","description":"Matched term/snippet/context"}},"type":"object","required":["code","severity","message"],"title":"ViolationResponse"},"cbpulse__VoiceEditListResponse":{"properties":{"edits":{"items":{"$ref":"#/components/schemas/cbpulse__VoiceEditResponse"},"type":"array","title":"Edits","description":"List of voice edit records"},"total":{"type":"integer","title":"Total","description":"Total number of matching edits","examples":[42]}},"type":"object","required":["edits","total"],"title":"VoiceEditListResponse"},"cbpulse__VoiceEditResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Voice edit record identifier","examples":["ve_01HXYZ"]},"business_id":{"type":"string","title":"Business Id","examples":["biz_thefred"]},"brand_profile_id":{"type":"string","title":"Brand Profile Id","examples":["bp_01HXYZ"]},"source_type":{"type":"string","title":"Source Type","description":"Content type that was transformed","examples":["social_post"]},"source_id":{"type":"string","title":"Source Id","description":"ID of the source content","examples":["post_01HXYZ"]},"original_text":{"type":"string","title":"Original Text","description":"Text before transformation"},"edited_text":{"type":"string","title":"Edited Text","description":"Text after brand voice applied"},"edit_field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edit Field","description":"Which field was transformed","examples":["caption"]},"edited_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Edited By","description":"User ID who triggered the transformation","examples":["usr_01HXYZ"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the edit was performed"}},"type":"object","required":["id","business_id","brand_profile_id","source_type","source_id","original_text","edited_text","created_at"],"title":"VoiceEditResponse","example":{"brand_profile_id":"bp_01HXYZ","business_id":"biz_thefred","created_at":"2026-02-15T10:00:00","edit_field":"caption","edited_by":"usr_01HXYZ","edited_text":"Island flavors, reimagined. Discover our fresh menu.","id":"ve_01HXYZ","original_text":"Check out our new menu items.","source_id":"post_01HXYZ","source_type":"social_post"}},"cbpulse__WoWChange":{"properties":{"total_revenue":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Revenue","description":"WoW change in total revenue percentage"},"labor_cost":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Labor Cost","description":"WoW change in labor cost percentage"},"labor_percent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Labor Percent","description":"WoW change in labor-to-revenue ratio"},"cogs":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cogs","description":"WoW change in cost of goods sold percentage"},"prime_cost_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prime Cost Pct","description":"WoW change in prime cost ratio"},"avg_check":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Check","description":"WoW change in average check size"},"rev_per_labor_hour":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rev Per Labor Hour","description":"WoW change in revenue per labor hour"},"health_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Health Score","description":"WoW change in financial health score"}},"type":"object","title":"WoWChange","description":"Week-over-week percentage changes for each KPI.","example":{"avg_check":4.3,"cogs":5.1,"health_score":2.0,"labor_cost":-3.2,"labor_percent":-1.8,"prime_cost_pct":-0.7,"rev_per_labor_hour":8.9,"total_revenue":12.5}},"cbpulse__WriteUpDocumentURLResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"source":{"type":"string","title":"Source"}},"type":"object","required":["url","source"],"title":"WriteUpDocumentURLResponse","description":"Fresh time-limited PDF URL for a write-up's envelope. cbpulse#428."},"cbpulse__WriteUpResponse":{"properties":{"id":{"type":"string","title":"Id"},"business_id":{"type":"string","title":"Business Id"},"employee_id":{"type":"string","title":"Employee Id"},"incident_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incident Id"},"incident_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incident Date","description":"ISO date the incident occurred (cbpulse#444)"},"issued_by":{"type":"string","title":"Issued By","description":"User ID who created the write-up"},"violation_category":{"type":"string","title":"Violation Category","description":"Category: Tardiness, Safety, etc."},"wda_ground":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wda Ground","description":"VI WDA §76 ground code (e.g. 76a1-76a9)"},"description":{"type":"string","title":"Description"},"corrective_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Corrective Action","description":"Required follow-up action (cbpulse#363)"},"consequences":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consequences","description":"Stated consequences if behavior continues (cbpulse#363)"},"discipline_level":{"type":"string","title":"Discipline Level","description":"verbal_warning, first_written, final_written, suspension, termination_rec"},"status":{"type":"string","title":"Status","description":"draft, sent, acknowledged, escalated"},"sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent At"},"acknowledged_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Acknowledged At"},"escalated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Escalated At"},"employee_response":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employee Response"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","business_id","employee_id","issued_by","violation_category","description","discipline_level","status","created_at","updated_at"],"title":"WriteUpResponse","description":"Formal employee write-up document."},"cbpulse__WriteUpSendRequest":{"properties":{"witness_name":{"type":"string","maxLength":120,"minLength":1,"title":"Witness Name"},"witness_email":{"type":"string","format":"email","title":"Witness Email"}},"type":"object","required":["witness_name","witness_email"],"title":"WriteUpSendRequest","description":"Body for POST /api/writeups/{id}/send. Witness identity is supplied at\ndispatch time (not on the draft) — matches the real workflow where the\nwitness is whoever is present in the room when the meeting happens."},"cbpulse__WriteupNarrateRequest":{"properties":{"facts":{"type":"string","maxLength":5000,"minLength":1,"title":"Facts","description":"Manager's raw notes about the incident."},"employee_name":{"type":"string","maxLength":200,"minLength":1,"title":"Employee Name","description":"Employee first name (or first + last)."},"severity":{"type":"string","title":"Severity","description":"verbal | written | final. Comes from compliance_analyze."},"violation_category":{"type":"string","maxLength":80,"minLength":1,"title":"Violation Category","description":"e.g. attendance, conduct, theft."},"incident_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Incident Date","description":"ISO date the incident occurred. Defaults to today."}},"type":"object","required":["facts","employee_name","severity","violation_category"],"title":"WriteupNarrateRequest","description":"Body for POST /api/hr/writeup/narrate."},"cbpulse__WriteupNarrateResponse":{"properties":{"incident_description":{"type":"string","title":"Incident Description"},"corrective_action":{"type":"string","title":"Corrective Action"},"consequences":{"type":"string","title":"Consequences"},"validation_passed":{"type":"boolean","title":"Validation Passed"},"validation_errors":{"items":{"type":"string"},"type":"array","title":"Validation Errors"},"attempts":{"type":"integer","title":"Attempts"}},"type":"object","required":["incident_description","corrective_action","consequences","validation_passed","validation_errors","attempts"],"title":"WriteupNarrateResponse"},"cbpulse__WriteupRecallRequest":{"properties":{"reason":{"type":"string","maxLength":2000,"minLength":1,"title":"Reason","description":"Why the writeup is being recalled (e.g. signer requested a change). Stored on the audit row and forwarded as the BoldSign revoke reason."}},"type":"object","required":["reason"],"title":"WriteupRecallRequest"},"cbpulse__WriteupRecallResponse":{"properties":{"id":{"type":"string","title":"Id"},"previous_status":{"type":"string","title":"Previous Status"},"new_status":{"type":"string","title":"New Status"},"boldsign_doc_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Boldsign Doc Id"},"envelope_void_succeeded":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Envelope Void Succeeded"},"envelope_void_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Envelope Void Error"},"recalled_at":{"type":"string","title":"Recalled At"}},"type":"object","required":["id","previous_status","new_status","boldsign_doc_id","envelope_void_succeeded","envelope_void_error","recalled_at"],"title":"WriteupRecallResponse"},"cbpulse__WriteupRemindRequest":{"properties":{"message":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Message","description":"Optional override for the BoldSign reminder email body."}},"type":"object","title":"WriteupRemindRequest"},"cbpulse__WriteupRemindResponse":{"properties":{"reminded":{"type":"boolean","title":"Reminded"},"boldsign_doc_id":{"type":"string","title":"Boldsign Doc Id"},"already_complete":{"type":"boolean","title":"Already Complete","default":false},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["reminded","boldsign_doc_id"],"title":"WriteupRemindResponse"},"cbpulse__WriteupVoidRequest":{"properties":{"reason":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Reason","description":"Optional note recorded on the audit row explaining the void."}},"type":"object","title":"WriteupVoidRequest"},"cbpulse__src__api__routes__alerts__AssignRequest":{"properties":{"assigned_to":{"type":"string","title":"Assigned To","description":"User ID to assign the alert to","examples":["usr_maria_gomez","usr_james_k","usr_front_desk_lead"]}},"type":"object","required":["assigned_to"],"title":"AssignRequest","description":"Payload to assign an alert to a specific user."},"cbpulse__src__api__routes__alerts__ResolveRequest":{"properties":{"notes":{"type":"string","title":"Notes","description":"Free-text resolution notes explaining how the alert was addressed","default":"","examples":["Adjusted staffing for Friday dinner rush — pulled one server from lunch","Invoice paid via wire transfer, confirmation #WR-8834","Occupancy recovered after weekend bookings came in"]}},"type":"object","title":"ResolveRequest","description":"Payload to resolve an alert with optional resolution notes."},"cbpulse__src__api__routes__auth__RefreshRequest":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token","description":"Valid refresh token to exchange for a new token pair","examples":["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]}},"type":"object","required":["refresh_token"],"title":"RefreshRequest","description":"Payload containing a valid refresh token to exchange for new tokens."},"cbpulse__src__api__routes__businesses__BusinessResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique business identifier"},"name":{"type":"string","title":"Name","description":"Business display name, e.g. 'The Fred', 'The Saint'"},"slug":{"type":"string","title":"Slug","description":"URL-safe identifier, e.g. 'the-fred', 'the-saint'"},"island":{"type":"string","title":"Island","description":"USVI island location: st_croix, st_john, or st_thomas"},"revenue_streams":{"items":{"type":"string"},"type":"array","title":"Revenue Streams","description":"Active revenue streams, e.g. ['hotel', 'restaurant', 'bar', 'spa']"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether this business is currently active in the portfolio"}},"type":"object","required":["id","name","slug","island","revenue_streams","is_active"],"title":"BusinessResponse","description":"A portfolio business entity with its island location and revenue streams.","example":{"id":"biz_thefred","is_active":true,"island":"st_croix","name":"The Fred","revenue_streams":["hotel","restaurant","bar","spa"],"slug":"the-fred"}},"cbpulse__src__api__routes__content_queue__PostResponse":{"properties":{"id":{"type":"string","title":"Id"},"batch_id":{"type":"string","title":"Batch Id"},"profile_id":{"type":"string","title":"Profile Id"},"brand":{"type":"string","title":"Brand"},"platform":{"type":"string","title":"Platform"},"content_type":{"type":"string","title":"Content Type"},"message":{"type":"string","title":"Message"},"media_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Media Path"},"media_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Media Url"},"publish_at":{"type":"string","title":"Publish At"},"status":{"type":"string","title":"Status"},"publish_as":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Publish As"},"labels":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Labels"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"},"vista_post_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vista Post Id"}},"type":"object","required":["id","batch_id","profile_id","brand","platform","content_type","message","media_path","media_url","publish_at","status","publish_as","labels","rejection_reason","vista_post_id"],"title":"PostResponse"},"cbpulse__src__api__routes__fb_orders__CreateOrderRequest":{"properties":{"businessId":{"type":"string","title":"Businessid"},"date":{"type":"string","title":"Date"},"vendor":{"type":"string","title":"Vendor"},"category":{"type":"string","title":"Category"},"invoice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice"},"staff":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Staff"},"lines":{"items":{"$ref":"#/components/schemas/cbpulse__LineItem"},"type":"array","minItems":1,"title":"Lines"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["businessId","date","vendor","category","lines"],"title":"CreateOrderRequest"},"cbpulse__src__api__routes__fb_reference__BusinessResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"type":{"type":"string","title":"Type"}},"type":"object","required":["id","name","type"],"title":"BusinessResponse"},"cbpulse__src__api__routes__fb_statements__RejectBody":{"properties":{"reason":{"type":"string","title":"Reason"}},"type":"object","required":["reason"],"title":"RejectBody"},"cbpulse__src__api__routes__invoices__RejectRequest":{"properties":{"reason":{"type":"string","title":"Reason","description":"Required reason for rejecting the invoice","examples":["Amount does not match purchase order total"]}},"type":"object","required":["reason"],"title":"RejectRequest","description":"Payload for rejecting an invoice with a reason."},"cbpulse__src__api__routes__marketing_content__GenerateRequest":{"properties":{"brand_slug":{"type":"string","title":"Brand Slug"},"content_type":{"type":"string","title":"Content Type"},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id"},"focus_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Focus Text"}},"type":"object","required":["brand_slug","content_type"],"title":"GenerateRequest"},"cbpulse__src__api__routes__nosj__RefreshRequest":{"properties":{"source_post_id":{"type":"integer","title":"Source Post Id","description":"WordPress post id of the NOSJ article to refresh"},"focus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Focus","description":"Optional refresh focus (e.g. 'post-storm hours, new owner')"},"article_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Article Kind","description":"Which QA prompt profile judges the refreshed article: 'news' or 'feature' (cbpulse#577). Same meaning as on Mode 1 — a refreshed news item still needs the news gate variants, since gates 6, 7 and 10 penalise the inline citation a sourced piece requires. Mode 2 is named alongside Mode 1 in CJ's acceptance criterion for that reason."},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","description":"LLM provider override: ollama | anthropic (writer defaults to anthropic/cbai-claude)"}},"type":"object","required":["source_post_id"],"title":"RefreshRequest","description":"Body for Mode 2 (article refresh). ``source_post_id`` is required."},"cbpulse__src__api__routes__nosj__RejectRequest":{"properties":{"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Optional reason CJ rejected the draft"}},"type":"object","title":"RejectRequest","description":"Optional body for reject — a free-text note (why CJ declined it)."},"cbpulse__src__api__routes__orders__CreateOrderRequest":{"properties":{"business_id":{"type":"string","title":"Business Id","description":"Business to create the order for","examples":["biz_thefred"]},"vendor_name":{"type":"string","title":"Vendor Name","description":"Vendor or supplier name","examples":["Island Spirits Distributing"]},"vendor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Id","description":"Optional vendor identifier from external system","examples":["vendor_isd_003"]},"line_items":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Line Items","description":"List of order line items with product, quantity, and price","examples":[[{"product":"Cruzan Rum 750ml","quantity":24,"unit_price":24.5}]]},"subtotal":{"type":"number","title":"Subtotal","description":"Sum of line item costs before tax and shipping","default":0.0,"examples":[966.0]},"tax":{"type":"number","title":"Tax","description":"Tax amount","default":0.0,"examples":[48.3]},"shipping":{"type":"number","title":"Shipping","description":"Shipping/delivery cost","default":0.0,"examples":[35.0]},"total":{"type":"number","title":"Total","description":"Grand total: subtotal + tax + shipping","default":0.0,"examples":[1049.3]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-text notes about the order","examples":["Bar restock and pool supplies for weekend rush"]},"requested_delivery_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requested Delivery Date","description":"Preferred delivery date (YYYY-MM-DD)","examples":["2026-02-14"]}},"type":"object","required":["business_id","vendor_name"],"title":"CreateOrderRequest","description":"Payload for creating a new purchase order request.","example":{"business_id":"biz_thefred","line_items":[{"product":"Cruzan Rum 750ml","quantity":24,"unit_price":24.5},{"product":"Pool Towels (50ct)","quantity":2,"unit_price":189.0}],"notes":"Bar restock and pool supplies for weekend rush","requested_delivery_date":"2026-02-14","shipping":35.0,"subtotal":966.0,"tax":48.3,"total":1049.3,"vendor_id":"vendor_isd_003","vendor_name":"Island Spirits Distributing"}},"cbpulse__src__api__routes__orders__RejectRequest":{"properties":{"reason":{"type":"string","title":"Reason","description":"Required reason for rejecting the order","examples":["Budget exceeded for Q1; defer to next quarter"]}},"type":"object","required":["reason"],"title":"RejectRequest","description":"Payload for rejecting an order with a reason."},"cbpulse__src__api__routes__payroll__RejectRequest":{"properties":{"notes":{"type":"string","title":"Notes","description":"Required rejection reason"}},"type":"object","required":["notes"],"title":"RejectRequest"},"cbpulse__src__api__routes__social__PostResponse":{"properties":{"id":{"type":"string","title":"Id"},"brand_slug":{"type":"string","title":"Brand Slug"},"platform":{"type":"string","title":"Platform"},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type"},"original_copy":{"type":"string","title":"Original Copy"},"photo_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Photo Path"},"vista_post_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vista Post Id"},"pushed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pushed By"},"status":{"type":"string","title":"Status"},"pushed_at":{"type":"string","format":"date-time","title":"Pushed At"},"approved_copy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved Copy"},"edit_diff":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Edit Diff"},"metrics":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metrics"},"synced_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Synced At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","brand_slug","platform","content_type","original_copy","photo_path","vista_post_id","pushed_by","status","pushed_at","approved_copy","edit_diff","metrics","synced_at","created_at","updated_at"],"title":"PostResponse"},"cbpulse__src__api__routes__summaries__GenerateRequest":{"properties":{"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date","description":"Target date for summary generation (YYYY-MM-DD), defaults to today","examples":["2026-02-15"]}},"type":"object","title":"GenerateRequest","description":"Payload for manually triggering summary generation for a specific date.","example":{"date":"2026-02-15"}},"cbpulse__src__api__routes__supply_schemas__RejectBody":{"properties":{"reason":{"type":"string","minLength":1,"title":"Reason"}},"type":"object","required":["reason"],"title":"RejectBody"},"cbpulse__src__api__routes__tickets__AssignRequest":{"properties":{"assigned_to":{"type":"string","title":"Assigned To","description":"User ID to assign the ticket to","examples":["user_maint_02"]}},"type":"object","required":["assigned_to"],"title":"AssignRequest","description":"Payload to assign a ticket to a specific user."},"cbpulse__src__api__routes__tickets__ResolveRequest":{"properties":{"notes":{"type":"string","title":"Notes","description":"Notes explaining how the issue was fixed","default":"","examples":["Replaced compressor relay and recharged refrigerant. Lobby now cooling to 74F."]}},"type":"object","title":"ResolveRequest","description":"Payload to resolve a ticket with optional resolution notes."},"cbquickbooks__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbquickbooks__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbquickbooks__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbquickbooks__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbquickbooks__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbquickbooks__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbquickbooks__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbquickbooks__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbquickbooks__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbquickbooks__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbquickbooks__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbquickbooks__QueryRequest":{"properties":{"query":{"type":"string","title":"Query","description":"A QuickBooks Query Language statement (SQL-like). Forwarded verbatim to Intuit's `/query` endpoint. Read-only; supports SELECT over allowlisted QuickBooks entities.","examples":["SELECT * FROM Account WHERE Active = true MAXRESULTS 100"]}},"type":"object","required":["query"],"title":"QueryRequest","description":"Request body for the QuickBooks query passthrough (`POST /{slug}/query`).\n\nCarries a single QuickBooks Query Language (a SQL-like dialect) statement\nthat is forwarded verbatim to Intuit's `/query` endpoint for the resolved\naccount. The query runs read-only against the company's data.","examples":[{"query":"SELECT * FROM Account WHERE Active = true MAXRESULTS 100"}]},"cbquickbooks__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbquickbooks__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbquickbooks__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbradio-qa__AcceptProposal":{"properties":{"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"AcceptProposal","description":"Request to mark proposal as accepted."},"cbradio-qa__AdTypeEnum":{"type":"string","enum":["candidate","issue"],"title":"AdTypeEnum","description":"Ad type classification for radio buys."},"cbradio-qa__AddStationRequest":{"properties":{"callsign":{"type":"string","maxLength":20,"minLength":1,"title":"Callsign"}},"type":"object","required":["callsign"],"title":"AddStationRequest","description":"Request model for adding a station to a recipient."},"cbradio-qa__AffectedSpots":{"properties":{"dates":{"items":{"type":"string","format":"date"},"type":"array","title":"Dates","default":[]},"dayparts":{"items":{"type":"string"},"type":"array","title":"Dayparts","default":[]}},"type":"object","title":"AffectedSpots","description":"Details of affected spots for make-good."},"cbradio-qa__AffiliateScore":{"properties":{"call_sign":{"type":"string","title":"Call Sign"},"matched_station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matched Station Id"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"},"format_score":{"type":"integer","title":"Format Score"},"coverage_score":{"type":"integer","title":"Coverage Score"},"nielsen_score":{"type":"integer","title":"Nielsen Score"},"voter_efficiency_score":{"type":"integer","title":"Voter Efficiency Score"},"total_score":{"type":"integer","title":"Total Score"},"coverage_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Coverage Pct"},"aqh_share":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aqh Share"}},"type":"object","required":["call_sign","format_score","coverage_score","nielsen_score","voter_efficiency_score","total_score"],"title":"AffiliateScore","description":"Per-affiliate scoring result in a network."},"cbradio-qa__AgencyStatus":{"type":"string","enum":["active","inactive"],"title":"AgencyStatus","description":"Buyer agency status."},"cbradio-qa__AggregateScore":{"properties":{"format_score":{"type":"number","title":"Format Score"},"coverage_score":{"type":"number","title":"Coverage Score"},"nielsen_score":{"type":"number","title":"Nielsen Score"},"voter_efficiency_score":{"type":"number","title":"Voter Efficiency Score"},"total_score":{"type":"number","title":"Total Score"}},"type":"object","required":["format_score","coverage_score","nielsen_score","voter_efficiency_score","total_score"],"title":"AggregateScore","description":"Aggregate (mean) scores across a network's scored affiliates."},"cbradio-qa__AgreementCreate":{"properties":{"station_callsign":{"type":"string","maxLength":20,"minLength":1,"title":"Station Callsign"},"agreement_type":{"$ref":"#/components/schemas/cbradio-qa__AgreementType","default":"standard"},"effective_date":{"type":"string","format":"date","title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date","description":"NULL = perpetual"},"auto_renew":{"type":"boolean","title":"Auto Renew","default":false},"commission_rate":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Commission Rate"},"terms_summary":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Terms Summary"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["station_callsign","effective_date"],"title":"AgreementCreate","description":"Request model for creating an agreement."},"cbradio-qa__AgreementDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"agreement_type":{"$ref":"#/components/schemas/cbradio-qa__AgreementType"},"lifecycle_state":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__agreement__LifecycleState"},"effective_date":{"type":"string","format":"date","title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"auto_renew":{"type":"boolean","title":"Auto Renew"},"commission_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Commission Rate"},"terms_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Terms Summary"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_by":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__UserSummary"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"signed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Signed At"},"signed_by_station":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed By Station"},"terminated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Terminated At"},"termination_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Termination Reason"},"documents_count":{"type":"integer","title":"Documents Count","default":0},"station":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__src__api__models__agreement__StationSummary"},{"type":"null"}]},"documents":{"items":{"$ref":"#/components/schemas/cbradio-qa__AgreementDocumentResponse"},"type":"array","title":"Documents","default":[]},"audit_trail":{"items":{"$ref":"#/components/schemas/cbradio-qa__AuditEntry"},"type":"array","title":"Audit Trail","default":[]}},"type":"object","required":["id","station_callsign","agreement_type","lifecycle_state","effective_date","auto_renew","created_at","updated_at"],"title":"AgreementDetailResponse","description":"Detailed response model for single agreement."},"cbradio-qa__AgreementDocumentResponse":{"properties":{"id":{"type":"string","title":"Id"},"document_type":{"$ref":"#/components/schemas/cbradio-qa__DocumentType"},"file_name":{"type":"string","title":"File Name"},"file_url":{"type":"string","title":"File Url"},"file_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size"},"mime_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime Type"},"uploaded_by":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__UserSummary"},{"type":"null"}]},"uploaded_at":{"type":"string","format":"date-time","title":"Uploaded At"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["id","document_type","file_name","file_url","uploaded_at"],"title":"AgreementDocumentResponse","description":"Response model for agreement document."},"cbradio-qa__AgreementListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/cbradio-qa__AgreementResponse"},"type":"array","title":"Data"},"pagination":{"additionalProperties":true,"type":"object","title":"Pagination"}},"type":"object","required":["data","pagination"],"title":"AgreementListResponse","description":"Paginated list of agreements."},"cbradio-qa__AgreementResponse":{"properties":{"id":{"type":"string","title":"Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"agreement_type":{"$ref":"#/components/schemas/cbradio-qa__AgreementType"},"lifecycle_state":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__agreement__LifecycleState"},"effective_date":{"type":"string","format":"date","title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"auto_renew":{"type":"boolean","title":"Auto Renew"},"commission_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Commission Rate"},"terms_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Terms Summary"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_by":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__UserSummary"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"signed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Signed At"},"signed_by_station":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed By Station"},"terminated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Terminated At"},"termination_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Termination Reason"},"documents_count":{"type":"integer","title":"Documents Count","default":0}},"type":"object","required":["id","station_callsign","agreement_type","lifecycle_state","effective_date","auto_renew","created_at","updated_at"],"title":"AgreementResponse","description":"Response model for agreement."},"cbradio-qa__AgreementTransition":{"properties":{"target_state":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__agreement__LifecycleState"},"signed_by_station":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Signed By Station","description":"Required when transitioning to signed"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["target_state"],"title":"AgreementTransition","description":"Request model for transitioning agreement state."},"cbradio-qa__AgreementType":{"type":"string","enum":["standard","custom","legacy"],"title":"AgreementType","description":"Type of agreement."},"cbradio-qa__AgreementUpdate":{"properties":{"agreement_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AgreementType"},{"type":"null"}]},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"auto_renew":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Auto Renew"},"commission_rate":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Commission Rate"},"terms_summary":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Terms Summary"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"AgreementUpdate","description":"Request model for updating an agreement."},"cbradio-qa__AlertList":{"properties":{"alerts":{"items":{"$ref":"#/components/schemas/cbradio-qa__SRMAlert"},"type":"array","title":"Alerts"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["alerts","total"],"title":"AlertList","description":"Paginated list of alerts."},"cbradio-qa__AlertType":{"type":"string","enum":["ownership_change","agreement_expiring","no_contact"],"title":"AlertType","description":"System alert types."},"cbradio-qa__ApplyCorrectionRequest":{"properties":{"corrections":{"additionalProperties":true,"type":"object","title":"Corrections","description":"Field corrections to apply"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Reason for correction"}},"type":"object","required":["corrections"],"title":"ApplyCorrectionRequest","description":"Request model for applying corrections to a staged record."},"cbradio-qa__ApprovedDaypartRate":{"properties":{"daypart_id":{"type":"string","title":"Daypart Id"},"daypart_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Daypart Label"},"time_range":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Range"},"gross_rate":{"type":"number","exclusiveMinimum":0.0,"title":"Gross Rate"},"source":{"type":"string","title":"Source","description":"extracted, corrected, or manual","default":"extracted"},"ad_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ad Type","description":"Product this rate prices: candidate, issue, commercial. Omit to inherit from the card's existing rows. Required when the card carries more than one ad_type for the same daypart and duration."},"net_rate":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Net Rate","description":"Net the source document states, if it states one. Omit to derive from gross at 15% agency commission."}},"type":"object","required":["daypart_id","gross_rate"],"title":"ApprovedDaypartRate","description":"A single daypart rate within the QA approval grid (#53)."},"cbradio-qa__ApprovedRateGrid":{"properties":{"spot_length":{"type":"integer","title":"Spot Length","description":"30 or 60"},"daypart_rates":{"items":{"$ref":"#/components/schemas/cbradio-qa__ApprovedDaypartRate"},"type":"array","title":"Daypart Rates"}},"type":"object","required":["spot_length","daypart_rates"],"title":"ApprovedRateGrid","description":"Rate grid grouped by spot length, sent on QA approval (#53)."},"cbradio-qa__AssetType":{"type":"string","enum":["audio_spot","nab_form"],"title":"AssetType","description":"Type of traffic asset."},"cbradio-qa__AuditEntry":{"properties":{"action":{"type":"string","title":"Action"},"performed_by":{"type":"string","title":"Performed By"},"performed_at":{"type":"string","format":"date-time","title":"Performed At"},"old_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Old Value"},"new_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"New Value"}},"type":"object","required":["action","performed_by","performed_at"],"title":"AuditEntry","description":"Single audit log entry."},"cbradio-qa__AuditLogResponse":{"properties":{"events":{"items":{"$ref":"#/components/schemas/cbradio-qa__ConfirmationEvent"},"type":"array","title":"Events"}},"type":"object","required":["events"],"title":"AuditLogResponse","description":"Response for order audit log."},"cbradio-qa__Body_decide_api_v1_year_proposals__document_id__post":{"properties":{"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"reviewer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reviewer"}},"type":"object","title":"Body_decide_api_v1_year_proposals__document_id__post"},"cbradio-qa__Body_login_for_access_token_api_auth_token_post":{"properties":{"grant_type":{"anyOf":[{"type":"string","pattern":"^password$"},{"type":"null"}],"title":"Grant Type"},"username":{"type":"string","title":"Username"},"password":{"type":"string","format":"password","title":"Password"},"scope":{"type":"string","title":"Scope","default":""},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"password","title":"Client Secret"}},"type":"object","required":["username","password"],"title":"Body_login_for_access_token_api_auth_token_post"},"cbradio-qa__BudgetRange":{"type":"string","enum":["$10-25K","$25-50K","$50-100K","$100K+"],"title":"BudgetRange","description":"Budget range tiers."},"cbradio-qa__BulkApproveRequest":{"properties":{"ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Ids","description":"Specific IDs to approve"},"filter":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Filter","description":"Filter criteria for bulk approval"}},"type":"object","title":"BulkApproveRequest","description":"Request model for bulk approving staged records."},"cbradio-qa__BulkCloseRequest":{"properties":{"project":{"type":"string","title":"Project","description":"Project slug"},"issues":{"items":{"type":"integer"},"type":"array","minItems":1,"title":"Issues","description":"Issue numbers"},"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment","description":"Closing comment"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Reason: completed or not_planned"}},"type":"object","required":["project","issues"],"title":"BulkCloseRequest","description":"Request body for bulk close operations.","example":{"issues":[1,2,3],"project":"cbradio","reason":"completed"}},"cbradio-qa__BulkContactImportRequest":{"properties":{"stations":{"items":{"$ref":"#/components/schemas/cbradio-qa__BulkContactItem"},"type":"array","title":"Stations"},"source":{"type":"string","title":"Source","default":"bulk_import"}},"type":"object","required":["stations"],"title":"BulkContactImportRequest","description":"Request body for bulk contact import."},"cbradio-qa__BulkContactItem":{"properties":{"callsign":{"type":"string","title":"Callsign"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"mailing_street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Street"},"mailing_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing City"},"mailing_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing State"},"mailing_zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Zip"}},"type":"object","required":["callsign"],"title":"BulkContactItem","description":"Single item in a bulk contact import."},"cbradio-qa__BulkLabelRequest":{"properties":{"project":{"type":"string","title":"Project","description":"Project slug"},"issues":{"items":{"type":"integer"},"type":"array","minItems":1,"title":"Issues","description":"Issue numbers"},"add_labels":{"items":{"type":"string"},"type":"array","title":"Add Labels","description":"Labels to add"},"remove_labels":{"items":{"type":"string"},"type":"array","title":"Remove Labels","description":"Labels to remove"}},"type":"object","required":["project","issues"],"title":"BulkLabelRequest","description":"Request body for bulk label operations.","example":{"add_labels":["reviewed"],"issues":[1,2,3],"project":"cbradio"}},"cbradio-qa__BulkOperationResponse":{"properties":{"success_count":{"type":"integer","title":"Success Count"},"error_count":{"type":"integer","title":"Error Count"},"results":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Results"}},"type":"object","required":["success_count","error_count","results"],"title":"BulkOperationResponse","description":"Response for bulk operations."},"cbradio-qa__BulkOperationResult":{"properties":{"success_count":{"type":"integer","title":"Success Count","description":"Number of successful operations"},"error_count":{"type":"integer","title":"Error Count","description":"Number of failed operations","default":0},"errors":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Errors","description":"List of errors with id and message"},"created_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Created Ids","description":"IDs of created resources (for bulk create)"}},"type":"object","required":["success_count"],"title":"BulkOperationResult","description":"Result of a bulk operation."},"cbradio-qa__BulkOwnershipImportRequest":{"properties":{"stations":{"items":{"$ref":"#/components/schemas/cbradio-qa__BulkOwnershipItem"},"type":"array","title":"Stations"},"source":{"type":"string","title":"Source","default":"bulk_import"}},"type":"object","required":["stations"],"title":"BulkOwnershipImportRequest","description":"Request body for bulk ownership import."},"cbradio-qa__BulkOwnershipItem":{"properties":{"callsign":{"type":"string","title":"Callsign"},"ownership_group":{"type":"string","title":"Ownership Group"}},"type":"object","required":["callsign","ownership_group"],"title":"BulkOwnershipItem","description":"Single item in a bulk ownership import."},"cbradio-qa__BulkRateCreate":{"properties":{"rates":{"items":{"$ref":"#/components/schemas/cbradio-qa__RateCreateForCard"},"type":"array","minItems":1,"title":"Rates"}},"type":"object","required":["rates"],"title":"BulkRateCreate","description":"Create multiple rates for an existing rate card."},"cbradio-qa__BulkRateDelete":{"properties":{"rate_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Rate Ids"}},"type":"object","required":["rate_ids"],"title":"BulkRateDelete","description":"Delete multiple rates by ID."},"cbradio-qa__BulkRateUpdate":{"properties":{"updates":{"items":{"$ref":"#/components/schemas/cbradio-qa__RateUpdateWithId"},"type":"array","minItems":1,"title":"Updates"}},"type":"object","required":["updates"],"title":"BulkRateUpdate","description":"Update multiple rates by ID."},"cbradio-qa__BulkSendRequest":{"properties":{"order_packet_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Order Packet Ids"},"sender_name":{"type":"string","maxLength":255,"minLength":1,"title":"Sender Name"},"sender_title":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Sender Title"},"custom_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Message","description":"Optional message included in email body"}},"type":"object","required":["order_packet_ids","sender_name"],"title":"BulkSendRequest","description":"Request model for bulk sending orders."},"cbradio-qa__BulkSendResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"sent_count":{"type":"integer","title":"Sent Count"},"failed_count":{"type":"integer","title":"Failed Count"},"results":{"items":{"$ref":"#/components/schemas/cbradio-qa__BulkSendResult"},"type":"array","title":"Results"}},"type":"object","required":["success","sent_count","failed_count","results"],"title":"BulkSendResponse","description":"Response after bulk sending orders."},"cbradio-qa__BulkSendResult":{"properties":{"order_packet_id":{"type":"string","title":"Order Packet Id"},"status":{"type":"string","title":"Status"},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At"},"email_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Id"},"delivered_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivered To"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"}},"type":"object","required":["order_packet_id","status"],"title":"BulkSendResult","description":"Result for single order in bulk send."},"cbradio-qa__BuyerAgency":{"properties":{"name":{"type":"string","title":"Name","description":"Agency name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AgencyStatus"},{"type":"null"}],"description":"Agency status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Agency contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Agency contact phone"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name","id","created_at","updated_at"],"title":"BuyerAgency","description":"Complete buyer agency model with database fields."},"cbradio-qa__BuyerAgencyCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Agency name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AgencyStatus"},{"type":"null"}],"description":"Agency status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Agency contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Agency contact phone"}},"type":"object","required":["name"],"title":"BuyerAgencyCreate","description":"Model for creating a new buyer agency."},"cbradio-qa__BuyerAgencySummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AgencyStatus"},{"type":"null"}]},"total_buys":{"type":"integer","title":"Total Buys","default":0},"total_volume":{"type":"number","title":"Total Volume","default":0.0},"client_count":{"type":"integer","title":"Client Count","default":0},"last_buy_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Last Buy Date"}},"type":"object","required":["id","name"],"title":"BuyerAgencySummary","description":"Lightweight buyer agency summary for list views."},"cbradio-qa__BuyerAgencyUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AgencyStatus"},{"type":"null"}]},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone"}},"type":"object","title":"BuyerAgencyUpdate","description":"Model for updating a buyer agency (all fields optional)."},"cbradio-qa__BuyerAgencyWithRadioBuys":{"properties":{"name":{"type":"string","title":"Name","description":"Agency name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AgencyStatus"},{"type":"null"}],"description":"Agency status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Agency contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Agency contact phone"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"radio_buys":{"items":{"$ref":"#/components/schemas/cbradio-qa__RadioBuySummary"},"type":"array","title":"Radio Buys","default":[]}},"type":"object","required":["name","id","created_at","updated_at"],"title":"BuyerAgencyWithRadioBuys","description":"Buyer agency with associated radio buys."},"cbradio-qa__CampaignType":{"type":"string","enum":["candidate_in_window","candidate_out_window","issue"],"title":"CampaignType","description":"Campaign type for proposal context."},"cbradio-qa__CancelOrderRequest":{"properties":{"reason":{"type":"string","minLength":1,"title":"Reason"},"send_cancellation_notice":{"type":"boolean","title":"Send Cancellation Notice","default":false}},"type":"object","required":["reason"],"title":"CancelOrderRequest","description":"Request model for cancelling an order."},"cbradio-qa__CancelOrderResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"cancelled_at":{"type":"string","format":"date-time","title":"Cancelled At"},"cancellation_email_sent":{"type":"boolean","title":"Cancellation Email Sent"}},"type":"object","required":["success","cancelled_at","cancellation_email_sent"],"title":"CancelOrderResponse","description":"Response after cancelling order."},"cbradio-qa__ClassificationAdjustment":{"properties":{"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__src__api__models__rate__AdType"},{"type":"null"}],"description":"Candidate vs Issue advertising"},"rate_context":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__RateContext"},{"type":"null"}],"description":"In-window vs out-of-window"},"preemption":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__PreemptionStatus"},{"type":"null"}],"description":"Preemption status"}},"type":"object","title":"ClassificationAdjustment","description":"Adjust rate classification - for AI correction workflow."},"cbradio-qa__ClassificationBreakdown":{"properties":{"count":{"type":"integer","title":"Count","default":0},"gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross","default":"0"},"commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission","default":"0"}},"type":"object","title":"ClassificationBreakdown","description":"Breakdown for a single classification category."},"cbradio-qa__Client":{"properties":{"name":{"type":"string","title":"Name","description":"Client name (candidate or organization)"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL to client logo"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State abbreviation"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office","description":"Office being sought (Governor, US Senate, etc.)"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientType"},{"type":"null"}],"description":"Client type classification"},"party_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Affiliation","description":"Political party affiliation"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientStatus"},{"type":"null"}],"description":"Client status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Primary contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Primary contact phone"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website","description":"Client website URL"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name","id","created_at","updated_at"],"title":"Client","description":"Complete client model with database fields."},"cbradio-qa__ClientApprovalRequest":{"properties":{"approved_by_name":{"type":"string","maxLength":255,"minLength":1,"title":"Approved By Name"},"approved_by_email":{"type":"string","format":"email","title":"Approved By Email"},"approved_by_title":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Approved By Title"},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes"}},"type":"object","required":["approved_by_name","approved_by_email"],"title":"ClientApprovalRequest","description":"Request model for client approval via public link."},"cbradio-qa__ClientApprovalResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"approved_at":{"type":"string","format":"date-time","title":"Approved At"},"message":{"type":"string","title":"Message","default":"Proposal has been approved."},"proposal_number":{"type":"string","title":"Proposal Number"}},"type":"object","required":["success","approved_at","proposal_number"],"title":"ClientApprovalResponse","description":"Response model for client approval."},"cbradio-qa__ClientApprovalStatus":{"type":"string","enum":["none","client_approved_pending_ops","ops_confirmed"],"title":"ClientApprovalStatus","description":"Client approval status for a proposal."},"cbradio-qa__ClientCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Client name (candidate or organization)"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL to client logo"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State abbreviation"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office","description":"Office being sought (Governor, US Senate, etc.)"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientType"},{"type":"null"}],"description":"Client type classification"},"party_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Affiliation","description":"Political party affiliation"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientStatus"},{"type":"null"}],"description":"Client status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Primary contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Primary contact phone"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website","description":"Client website URL"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name"],"title":"ClientCreate","description":"Model for creating a new client."},"cbradio-qa__ClientStatus":{"type":"string","enum":["active","inactive"],"title":"ClientStatus","description":"Client status."},"cbradio-qa__ClientSummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientType"},{"type":"null"}]},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientStatus"},{"type":"null"}]},"total_buys":{"type":"integer","title":"Total Buys","default":0},"total_spend":{"type":"number","title":"Total Spend","default":0.0},"last_buy_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Last Buy Date"}},"type":"object","required":["id","name"],"title":"ClientSummary","description":"Lightweight client summary for list views."},"cbradio-qa__ClientType":{"type":"string","enum":["candidate","pac","issue","other"],"title":"ClientType","description":"Client type classification."},"cbradio-qa__ClientUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientType"},{"type":"null"}]},"party_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Affiliation"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientStatus"},{"type":"null"}]},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"ClientUpdate","description":"Model for updating a client (all fields optional)."},"cbradio-qa__ClientWithRadioBuys":{"properties":{"name":{"type":"string","title":"Name","description":"Client name (candidate or organization)"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL to client logo"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State abbreviation"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office","description":"Office being sought (Governor, US Senate, etc.)"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientType"},{"type":"null"}],"description":"Client type classification"},"party_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Affiliation","description":"Political party affiliation"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientStatus"},{"type":"null"}],"description":"Client status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Primary contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Primary contact phone"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website","description":"Client website URL"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"radio_buys":{"items":{"$ref":"#/components/schemas/cbradio-qa__RadioBuySummary"},"type":"array","title":"Radio Buys","default":[]}},"type":"object","required":["name","id","created_at","updated_at"],"title":"ClientWithRadioBuys","description":"Client with associated radio buys."},"cbradio-qa__CommentCreate":{"properties":{"body":{"type":"string","minLength":1,"title":"Body","description":"Comment body (markdown supported)"}},"type":"object","required":["body"],"title":"CommentCreate","description":"Request body for adding a comment.","example":{"body":"Thanks for reporting! I'll look into this."}},"cbradio-qa__CommissionBreakdown":{"properties":{"in_network":{"$ref":"#/components/schemas/cbradio-qa__ClassificationBreakdown"},"salem":{"$ref":"#/components/schemas/cbradio-qa__ClassificationBreakdown"},"out_of_network":{"$ref":"#/components/schemas/cbradio-qa__ClassificationBreakdown"},"unknown":{"$ref":"#/components/schemas/cbradio-qa__ClassificationBreakdown"}},"type":"object","title":"CommissionBreakdown","description":"Commission breakdown by station classification."},"cbradio-qa__CommissionRecipientCreate":{"properties":{"recipient_type":{"$ref":"#/components/schemas/cbradio-qa__RecipientType"},"recipient_name":{"type":"string","maxLength":255,"minLength":1,"title":"Recipient Name"},"split_percentage":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Split Percentage"},"is_paid":{"type":"boolean","title":"Is Paid","default":false},"paid_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid Date"}},"type":"object","required":["recipient_type","recipient_name","split_percentage"],"title":"CommissionRecipientCreate","description":"Create a commission recipient."},"cbradio-qa__CommissionRecipientUpdate":{"properties":{"recipient_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__RecipientType"},{"type":"null"}]},"recipient_name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Recipient Name"},"split_percentage":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Split Percentage"},"is_paid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Paid"},"paid_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid Date"}},"type":"object","title":"CommissionRecipientUpdate","description":"Update a commission recipient (all fields optional)."},"cbradio-qa__CommissionRuleCreate":{"properties":{"recipient_type":{"$ref":"#/components/schemas/cbradio-qa__RecipientType"},"recipient_name":{"type":"string","maxLength":255,"minLength":1,"title":"Recipient Name"},"rate_in_network_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate In Network Pct","default":"0"},"rate_non_rural_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate Non Rural Pct","default":"0"},"rate_salem_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate Salem Pct","default":"0"},"rate_total_gross_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate Total Gross Pct","default":"0"},"rate_rural_gross_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate Rural Gross Pct","default":"0"},"custom_basis_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Custom Basis Amount"},"hold_status":{"type":"string","title":"Hold Status","default":"none"},"hold_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hold Reason"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["recipient_type","recipient_name"],"title":"CommissionRuleCreate","description":"Create a commission rule for a recipient on a buy."},"cbradio-qa__CommissionRuleSummary":{"properties":{"recipient_name":{"type":"string","title":"Recipient Name"},"recipient_type":{"type":"string","title":"Recipient Type"},"computed_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Computed Amount"},"hold_status":{"type":"string","title":"Hold Status","default":"none"}},"type":"object","required":["recipient_name","recipient_type"],"title":"CommissionRuleSummary","description":"Lightweight commission rule for list views."},"cbradio-qa__CommissionRuleUpdate":{"properties":{"recipient_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__RecipientType"},{"type":"null"}]},"recipient_name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Recipient Name"},"rate_in_network_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rate In Network Pct"},"rate_non_rural_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rate Non Rural Pct"},"rate_salem_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rate Salem Pct"},"rate_total_gross_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rate Total Gross Pct"},"rate_rural_gross_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rate Rural Gross Pct"},"custom_basis_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Custom Basis Amount"},"hold_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hold Status"},"hold_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hold Reason"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"CommissionRuleUpdate","description":"Update a commission rule (all fields optional)."},"cbradio-qa__CommissionSplit":{"properties":{"recipient_type":{"type":"string","pattern":"^(agency|consultant|station|other)$","title":"Recipient Type"},"recipient_name":{"type":"string","title":"Recipient Name"},"recipient_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Id"},"percentage":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Percentage"},"amount":{"type":"number","minimum":0.0,"title":"Amount"}},"type":"object","required":["recipient_type","recipient_name","percentage","amount"],"title":"CommissionSplit","description":"A single commission split entry."},"cbradio-qa__CommissionSplitsUpdate":{"properties":{"commission_total":{"type":"number","minimum":0.0,"title":"Commission Total"},"splits":{"items":{"$ref":"#/components/schemas/cbradio-qa__CommissionSplit"},"type":"array","title":"Splits"},"has_buyer_agency":{"type":"boolean","title":"Has Buyer Agency","default":false},"rebate_amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Rebate Amount"}},"type":"object","required":["commission_total"],"title":"CommissionSplitsUpdate","description":"Request model for updating commission splits."},"cbradio-qa__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbradio-qa__ConfirmationEvent":{"properties":{"id":{"type":"string","title":"Id"},"event_type":{"$ref":"#/components/schemas/cbradio-qa__ConfirmationEventType"},"event_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Event Data"},"ip_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip Address"},"user_agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","required":["id","event_type","created_at"],"title":"ConfirmationEvent","description":"Single audit event for order confirmation."},"cbradio-qa__ConfirmationEventType":{"type":"string","enum":["generated","previewed","approved_for_send","email_sent","email_delivered","email_bounced","web_viewed","web_confirmed","email_confirmed","manual_confirmed","make_good_requested","cancelled","modified"],"title":"ConfirmationEventType","description":"Types of confirmation events for audit trail."},"cbradio-qa__ConfirmationVia":{"type":"string","enum":["web","email","manual"],"title":"ConfirmationVia","description":"How the order was confirmed."},"cbradio-qa__Consultant":{"properties":{"name":{"type":"string","title":"Name","description":"Consultant name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ConsultantStatus"},{"type":"null"}],"description":"Consultant status"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Consultant email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Consultant phone"},"specialization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Specialization","description":"Area of specialization"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name","id","created_at","updated_at"],"title":"Consultant","description":"Complete consultant model with database fields."},"cbradio-qa__ConsultantCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Consultant name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ConsultantStatus"},{"type":"null"}],"description":"Consultant status"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Consultant email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Consultant phone"},"specialization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Specialization","description":"Area of specialization"}},"type":"object","required":["name"],"title":"ConsultantCreate","description":"Model for creating a new consultant."},"cbradio-qa__ConsultantStatus":{"type":"string","enum":["active","inactive"],"title":"ConsultantStatus","description":"Consultant status."},"cbradio-qa__ConsultantSummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ConsultantStatus"},{"type":"null"}]}},"type":"object","required":["id","name"],"title":"ConsultantSummary","description":"Lightweight consultant summary for list views."},"cbradio-qa__ConsultantUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ConsultantStatus"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"specialization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Specialization"}},"type":"object","title":"ConsultantUpdate","description":"Model for updating a consultant (all fields optional)."},"cbradio-qa__ConsultantWithRelations":{"properties":{"name":{"type":"string","title":"Name","description":"Consultant name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ConsultantStatus"},{"type":"null"}],"description":"Consultant status"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Consultant email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Consultant phone"},"specialization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Specialization","description":"Area of specialization"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"clients":{"items":{"$ref":"#/components/schemas/cbradio-qa__ClientSummary"},"type":"array","title":"Clients","default":[]},"radio_buys":{"items":{"$ref":"#/components/schemas/cbradio-qa__RadioBuySummary"},"type":"array","title":"Radio Buys","default":[]}},"type":"object","required":["name","id","created_at","updated_at"],"title":"ConsultantWithRelations","description":"Consultant with associated clients and radio buys."},"cbradio-qa__ContactFormRequest":{"properties":{"name":{"type":"string","title":"Name"},"email":{"type":"string","title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"company":{"type":"string","title":"Company"},"category":{"type":"string","enum":["advertiser","station_owner","consultant","general","partnership","support"],"title":"Category"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","default":"ruralamfm.com"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"}},"type":"object","required":["name","email","company","category"],"title":"ContactFormRequest"},"cbradio-qa__ContactFormResponse":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status","default":"received"},"message":{"type":"string","title":"Message","default":"Thank you for your inquiry. We'll be in touch soon."}},"type":"object","required":["id"],"title":"ContactFormResponse"},"cbradio-qa__ContactLog":{"properties":{"id":{"type":"string","title":"Id","description":"Unique identifier"},"station_callsign":{"type":"string","title":"Station Callsign","description":"Station callsign"},"contact_type":{"$ref":"#/components/schemas/cbradio-qa__ContactType","description":"Type of contact"},"outcome":{"$ref":"#/components/schemas/cbradio-qa__ContactOutcome","description":"Contact outcome"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Contact notes"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name","description":"Person contacted"},"contact_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Role","description":"Their role at station"},"follow_up_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Follow Up Date","description":"Scheduled follow-up"},"logged_by_user_id":{"type":"string","title":"Logged By User Id","description":"User who logged contact"},"logged_by_name":{"type":"string","title":"Logged By Name","description":"User display name"},"contacted_at":{"type":"string","format":"date-time","title":"Contacted At","description":"When contact occurred"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"}},"type":"object","required":["id","station_callsign","contact_type","outcome","logged_by_user_id","logged_by_name","contacted_at","created_at"],"title":"ContactLog","description":"Contact log entry."},"cbradio-qa__ContactLogCreate":{"properties":{"contact_type":{"$ref":"#/components/schemas/cbradio-qa__ContactType","description":"Type of contact"},"outcome":{"$ref":"#/components/schemas/cbradio-qa__ContactOutcome","description":"Contact outcome"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Contact notes"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name","description":"Person contacted"},"contact_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Role","description":"Their role at station"},"follow_up_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Follow Up Date","description":"Scheduled follow-up date"},"contacted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Contacted At","description":"When contact occurred (defaults to now)"}},"type":"object","required":["contact_type","outcome"],"title":"ContactLogCreate","description":"Request model for logging a contact."},"cbradio-qa__ContactLogList":{"properties":{"contacts":{"items":{"$ref":"#/components/schemas/cbradio-qa__ContactLog"},"type":"array","title":"Contacts"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["contacts","total"],"title":"ContactLogList","description":"Paginated list of contact logs."},"cbradio-qa__ContactOutcome":{"type":"string","enum":["interested","meeting_scheduled","agreement_sent","agreement_signed","callback_requested","info_requested","no_answer","left_voicemail","not_interested","bad_timing","competitor","do_not_contact"],"title":"ContactOutcome","description":"Standardized outcomes for contact logging."},"cbradio-qa__ContactType":{"type":"string","enum":["phone","email","in_person","other"],"title":"ContactType","description":"Types of contact interactions."},"cbradio-qa__ContoursBatchRequest":{"properties":{"callsigns":{"items":{"type":"string"},"type":"array","title":"Callsigns","description":"Station callsigns (suffixed, e.g. 'WOOD-AM'). Deduped server-side."},"contour_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contour Type","description":"Contour type: 'day', 'night', or 'service' (AM only). Defaults to 'day' for AM stations; ignored for FM."}},"type":"object","required":["callsigns"],"title":"ContoursBatchRequest","description":"Request body for POST /geo/contours (cbradio-ui#139)."},"cbradio-qa__ConvertProposal":{"properties":{"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"ConvertProposal","description":"Request to convert proposal to radio buy."},"cbradio-qa__CorrectionStatus":{"type":"string","enum":["pending_review","verified","rejected"],"title":"CorrectionStatus","description":"Status of a rate correction in the review workflow."},"cbradio-qa__CountyContribution":{"properties":{"county_fips":{"type":"string","title":"County Fips","description":"5-digit FIPS code.","examples":["01043"]},"county_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County Name","examples":["Cullman"]},"county_in_district_pct":{"type":"number","title":"County In District Pct","description":"What % of this county lies inside the district. Used as the weight on county_voter_data when aggregating district totals.","examples":[99.99]},"registered_contributed":{"type":"integer","title":"Registered Contributed","description":"Registered voters this county contributes to the district (county_voter_data * county_in_district_pct / 100).","examples":[49000]}},"type":"object","required":["county_fips","county_in_district_pct","registered_contributed"],"title":"CountyContribution","description":"Per-county breakdown of a sub-state district's voter aggregation."},"cbradio-qa__CountyCoverage":{"properties":{"county_fips":{"type":"string","title":"County Fips","description":"5-digit FIPS code for the county.","examples":["26065","26045"]},"county_name":{"type":"string","title":"County Name","description":"County name from voter data.","examples":["Ingham","Eaton"]},"state_code":{"type":"string","title":"State Code","description":"Two-letter state code.","examples":["MI","OH"]},"coverage_pct":{"type":"number","title":"Coverage Pct","description":"Percentage of the county covered by the station's contour (0-100).","examples":[100.0,85.5,3.2]},"registered_voters":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Registered Voters","description":"i360 registered voter count for this county. Null if no voter data.","examples":[245000]},"swing_likely_voters":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Swing Likely Voters","description":"Swing voters (affinity 40-55, propensity 1-4) in this county.","examples":[1200]},"base_likely_voters":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Base Likely Voters","description":"Base voters (affinity 65+, propensity 1-4) in this county.","examples":[3400]}},"type":"object","required":["county_fips","county_name","state_code","coverage_pct"],"title":"CountyCoverage","description":"A county within a station's coverage area."},"cbradio-qa__CountyReportRequest":{"properties":{"state":{"type":"string","maxLength":2,"minLength":2,"title":"State","description":"Two-letter US state abbreviation. Case-insensitive (auto-uppercased).","examples":["NH","FL","TX"]},"swing_affinity_min":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Swing Affinity Min","description":"Minimum political affinity score for swing voters. 0=strong opposition, 100=strong support.","default":40,"examples":[35,40,45]},"swing_affinity_max":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Swing Affinity Max","description":"Maximum political affinity score for swing voters.","default":55,"examples":[50,55,60]},"base_affinity_min":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Base Affinity Min","description":"Minimum political affinity score for base voters. Typically 65+ for strong partisans.","default":65,"examples":[60,65,70]},"voting_propensity_min":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Voting Propensity Min","description":"Minimum voting propensity score. 1=most likely to vote, 4=moderately likely.","default":1,"examples":[1,2]},"voting_propensity_max":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Voting Propensity Max","description":"Maximum voting propensity score. Higher values include less frequent voters.","default":4,"examples":[4,5,6]}},"type":"object","required":["state"],"title":"CountyReportRequest","description":"Request parameters for a statewide county-level voter propensity report.\n\nGenerates a CSV-backed report with one row per county showing registered voter\ncounts plus swing and base universe sizes. Defaults match standard campaign\ntargeting criteria (swing: affinity 40-55, base: affinity 65+, propensity 1-4).","example":{"base_affinity_min":65,"state":"NH","swing_affinity_max":55,"swing_affinity_min":40,"voting_propensity_max":4,"voting_propensity_min":1}},"cbradio-qa__CountyReportResponse":{"properties":{"state":{"type":"string","title":"State","description":"Two-letter state code that was queried.","examples":["NH"]},"county_count":{"type":"integer","title":"County Count","description":"Number of counties in the report.","examples":[11,67]},"counties":{"items":{"$ref":"#/components/schemas/cbradio-qa__CountyRow"},"type":"array","title":"Counties","description":"One row per county, ordered as returned by i360."}},"type":"object","required":["state","county_count","counties"],"title":"CountyReportResponse","description":"Statewide county-level voter propensity report."},"cbradio-qa__CountyRow":{"properties":{"county":{"type":"string","title":"County","description":"County name as returned by i360 (e.g. 'ALACHUA', 'Belknap').","examples":["Belknap","ALACHUA","Hillsborough"]},"registered_voters":{"type":"integer","title":"Registered Voters","description":"All individuals in i360 database for this county. Note: this is total i360 records, not official voter registration count.","examples":[62957,371069]},"swing_likely_voters":{"type":"integer","title":"Swing Likely Voters","description":"Individuals matching swing affinity + propensity criteria.","examples":[101,374]},"swing_likely_hhs":{"type":"integer","title":"Swing Likely Hhs","description":"Unique households (residences) for swing voters. Use for direct mail piece counts.","examples":[95,370]},"base_likely_voters":{"type":"integer","title":"Base Likely Voters","description":"Individuals matching base affinity + propensity criteria.","examples":[198,573]},"base_likely_hhs":{"type":"integer","title":"Base Likely Hhs","description":"Unique households for base voters.","examples":[190,563]}},"type":"object","required":["county","registered_voters","swing_likely_voters","swing_likely_hhs","base_likely_voters","base_likely_hhs"],"title":"CountyRow","description":"One county's voter universe data from the i360 report."},"cbradio-qa__CreateBatchRequest":{"properties":{"source_type":{"type":"string","pattern":"^(buy_tracker|payment_sheet)$","title":"Source Type","description":"Source type: buy_tracker or payment_sheet"},"source_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Source Name"},"records":{"items":{"$ref":"#/components/schemas/cbradio-qa__ImportRecord"},"type":"array","minItems":1,"title":"Records"}},"type":"object","required":["source_type","records"],"title":"CreateBatchRequest","description":"Request model for creating an import batch."},"cbradio-qa__CreateShareLinkRequest":{"properties":{"expires_in_days":{"type":"integer","maximum":90.0,"minimum":1.0,"title":"Expires In Days","description":"Number of days until the link expires (1-90)","default":30}},"type":"object","title":"CreateShareLinkRequest","description":"Request model for creating a share link."},"cbradio-qa__CycleCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"start_year":{"type":"integer","maximum":2040.0,"minimum":2020.0,"title":"Start Year"},"end_year":{"type":"integer","maximum":2040.0,"minimum":2020.0,"title":"End Year"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"type":"object","required":["name","start_year","end_year"],"title":"CycleCreate","description":"Create a political cycle."},"cbradio-qa__CycleUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Name"},"start_year":{"anyOf":[{"type":"integer","maximum":2040.0,"minimum":2020.0},{"type":"null"}],"title":"Start Year"},"end_year":{"anyOf":[{"type":"integer","maximum":2040.0,"minimum":2020.0},{"type":"null"}],"title":"End Year"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"CycleUpdate","description":"Update a political cycle (all fields optional)."},"cbradio-qa__DashboardResponse":{"properties":{"summary":{"$ref":"#/components/schemas/cbradio-qa__DashboardSummary"},"overdue_orders":{"items":{"$ref":"#/components/schemas/cbradio-qa__OverdueOrder"},"type":"array","title":"Overdue Orders","default":[]},"pending_make_goods":{"items":{"$ref":"#/components/schemas/cbradio-qa__PendingMakeGood"},"type":"array","title":"Pending Make Goods","default":[]}},"type":"object","required":["summary"],"title":"DashboardResponse","description":"Dashboard summary response."},"cbradio-qa__DashboardSummary":{"properties":{"pending_review":{"type":"integer","title":"Pending Review","default":0},"ready_to_send":{"type":"integer","title":"Ready To Send","default":0},"sent_awaiting_confirmation":{"type":"integer","title":"Sent Awaiting Confirmation","default":0},"confirmed":{"type":"integer","title":"Confirmed","default":0},"make_good_requested":{"type":"integer","title":"Make Good Requested","default":0},"cancelled":{"type":"integer","title":"Cancelled","default":0}},"type":"object","title":"DashboardSummary","description":"Summary counts by status."},"cbradio-qa__DataFreshness":{"properties":{"fetched_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Fetched At","description":"Earliest fetched_at across contributing counties.","examples":["2026-02-17T10:37:24Z"]},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Earliest expires_at across contributing counties.","examples":["2026-05-18T10:37:24Z"]},"is_stale":{"type":"boolean","title":"Is Stale","description":"True when expires_at is in the past — voter data has aged out of its 90-day TTL.","default":false}},"type":"object","title":"DataFreshness","description":"cbradio#252: freshness window for the i360 voter data backing this response.\n\n`fetched_at` / `expires_at` are the OLDEST values across the counties that\ncontributed (most-pessimistic). `is_stale=True` means `expires_at` is in\nthe past — the underlying `county_voter_data` rows are past their 90-day\nTTL and should be re-fetched. cbradio still returns the data (no hard\nfailure) so consumers can decide how to handle staleness."},"cbradio-qa__DayPattern":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun","m-f","sat-sun","m-sat","all"],"title":"DayPattern","description":"Day of week patterns for time slots."},"cbradio-qa__DeleteVersionResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"deleted_version_id":{"type":"string","title":"Deleted Version Id"},"deleted_version_number":{"type":"integer","title":"Deleted Version Number"},"remaining_versions":{"type":"integer","title":"Remaining Versions"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","deleted_version_id","deleted_version_number","remaining_versions","message"],"title":"DeleteVersionResponse","description":"Response for deleting a version."},"cbradio-qa__DistrictVoterResponse":{"properties":{"district_id":{"type":"string","title":"District Id","description":"FIPS code for the district.","examples":["26065"]},"district_type":{"type":"string","title":"District Type","description":"Type of district.","examples":["county"]},"county_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County Name","description":"County name.","examples":["Ingham"]},"state_code":{"type":"string","title":"State Code","description":"Two-letter state code.","examples":["MI"]},"voter_counts":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__VoterCounts"},{"type":"null"}],"description":"Raw voter counts for this district (unweighted)."},"data_freshness":{"$ref":"#/components/schemas/cbradio-qa__DataFreshness","description":"Freshness window for the i360 voter data (cbradio#252)."},"station_count":{"type":"integer","title":"Station Count","description":"Number of stations covering this district.","examples":[12]},"stations":{"items":{"$ref":"#/components/schemas/cbradio-qa__StationCoverage"},"type":"array","title":"Stations","description":"Stations covering this district, ordered by coverage_pct DESC."}},"type":"object","required":["district_id","district_type","state_code","station_count","stations"],"title":"DistrictVoterResponse","description":"Voter data for a district (county) with all covering stations."},"cbradio-qa__DistrictVoterTotalsResponse":{"properties":{"district_id":{"type":"string","title":"District Id","examples":["AL-SD-004"]},"district_type":{"type":"string","title":"District Type","description":"One of 'county', 'state_senate', 'state_house', 'congressional'.","examples":["state_senate"]},"state_code":{"type":"string","title":"State Code","examples":["AL"]},"total_registered_voters":{"type":"integer","title":"Total Registered Voters","examples":[137000]},"swing_likely_voters":{"type":"integer","title":"Swing Likely Voters","examples":[6800]},"base_likely_voters":{"type":"integer","title":"Base Likely Voters","examples":[32000]},"estimated_listeners":{"type":"integer","title":"Estimated Listeners","description":"total_registered_voters * 0.15 (industry-standard radio penetration rate).","examples":[20550]},"county_count":{"type":"integer","title":"County Count","description":"Number of constituent counties contributing to totals."},"data_freshness":{"$ref":"#/components/schemas/cbradio-qa__DataFreshness","description":"Freshness window for the i360 voter data (cbradio#252)."},"components":{"items":{"$ref":"#/components/schemas/cbradio-qa__CountyContribution"},"type":"array","title":"Components","description":"Per-county breakdown ordered by intersection_pct DESC."}},"type":"object","required":["district_id","district_type","state_code","total_registered_voters","swing_likely_voters","base_likely_voters","estimated_listeners","county_count"],"title":"DistrictVoterTotalsResponse","description":"Aggregated voter totals across a district's constituent counties.\n\nPowers the in-district CPE denominator (cbradio#228). For ``county`` type,\nreturns the row from ``county_voter_data`` directly. For state-leg and\ncongressional types, sums county_voter_data weighted by what fraction of\neach county lies inside the district (precomputed in\ncbdistricts.intersections.duckdb)."},"cbradio-qa__DocumentCreate":{"properties":{"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"callsign":{"type":"string","title":"Callsign"},"document_type":{"type":"string","title":"Document Type","default":"rate_card"},"document_format":{"type":"string","title":"Document Format"},"rate_card_type":{"type":"string","title":"Rate Card Type","default":"political"},"year":{"type":"integer","title":"Year","default":2026},"filename":{"type":"string","title":"Filename"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"source":{"type":"string","title":"Source","default":"airtable_2026"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"}},"type":"object","required":["callsign","document_format","filename","file_path","file_size_bytes"],"title":"DocumentCreate","description":"A single document to create."},"cbradio-qa__DocumentCreateRequest":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/cbradio-qa__DocumentCreate"},"type":"array","title":"Documents"}},"type":"object","required":["documents"],"title":"DocumentCreateRequest","description":"Batch document creation request."},"cbradio-qa__DocumentCreateResponse":{"properties":{"created":{"type":"integer","title":"Created"},"ids":{"items":{"type":"string"},"type":"array","title":"Ids"}},"type":"object","required":["created","ids"],"title":"DocumentCreateResponse","description":"Response for batch document creation."},"cbradio-qa__DocumentType":{"type":"string","enum":["agreement_pdf","rate_card","addendum","correspondence","other"],"title":"DocumentType","description":"Type of agreement document."},"cbradio-qa__DocumentUploadResponse":{"properties":{"id":{"type":"string","title":"Id"},"document_type":{"$ref":"#/components/schemas/cbradio-qa__DocumentType"},"file_name":{"type":"string","title":"File Name"},"file_url":{"type":"string","title":"File Url"},"file_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size"},"mime_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime Type"},"uploaded_at":{"type":"string","format":"date-time","title":"Uploaded At"}},"type":"object","required":["id","document_type","file_name","file_url","uploaded_at"],"title":"DocumentUploadResponse","description":"Response after uploading a document."},"cbradio-qa__ElectionType":{"type":"string","enum":["primary","general"],"title":"ElectionType","description":"Election type for radio buys."},"cbradio-qa__EstimatedListeners":{"properties":{"swing":{"type":"integer","title":"Swing","description":"Estimated swing voter listeners (voters * 0.15 penetration rate).","examples":[675]},"base":{"type":"integer","title":"Base","description":"Estimated base voter listeners.","examples":[1335]},"total":{"type":"integer","title":"Total","description":"Estimated total registered voter listeners.","examples":[37500]}},"type":"object","required":["swing","base","total"],"title":"EstimatedListeners","description":"Estimated radio listeners derived from voter counts."},"cbradio-qa__ExitReason":{"type":"string","enum":["non_performance","station_request","rate_dispute","coverage_change","compliance_issue","other"],"title":"ExitReason","description":"Reason for network exit request."},"cbradio-qa__ExitRequestCreate":{"properties":{"station_callsign":{"type":"string","maxLength":20,"minLength":1,"title":"Station Callsign"},"reason":{"$ref":"#/components/schemas/cbradio-qa__ExitReason"},"reason_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason Details","description":"Required if reason is 'other'"},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"}},"type":"object","required":["station_callsign","reason"],"title":"ExitRequestCreate","description":"Request model for creating exit request."},"cbradio-qa__ExitRequestListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/cbradio-qa__ExitRequestResponse"},"type":"array","title":"Data"},"pagination":{"additionalProperties":true,"type":"object","title":"Pagination"}},"type":"object","required":["data","pagination"],"title":"ExitRequestListResponse","description":"Paginated list of exit requests."},"cbradio-qa__ExitRequestResponse":{"properties":{"id":{"type":"string","title":"Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"station":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__src__api__models__agreement__StationSummary"},{"type":"null"}]},"agreement_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agreement Id"},"requested_by":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__UserSummary"},{"type":"null"}]},"requested_at":{"type":"string","format":"date-time","title":"Requested At"},"reason":{"$ref":"#/components/schemas/cbradio-qa__ExitReason"},"reason_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason Details"},"status":{"$ref":"#/components/schemas/cbradio-qa__ExitStatus"},"reviewed_by":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__UserSummary"},{"type":"null"}]},"reviewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reviewed At"},"review_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Notes"},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"days_pending":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Pending"}},"type":"object","required":["id","station_callsign","requested_at","reason","status"],"title":"ExitRequestResponse","description":"Response model for exit request."},"cbradio-qa__ExitRequestReview":{"properties":{"decision":{"type":"string","pattern":"^(approved|rejected)$","title":"Decision"},"review_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Notes"},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date","description":"Override effective date if approved"}},"type":"object","required":["decision"],"title":"ExitRequestReview","description":"Request model for reviewing exit request."},"cbradio-qa__ExitStatus":{"type":"string","enum":["pending","approved","rejected","cancelled"],"title":"ExitStatus","description":"Status of exit request."},"cbradio-qa__ExportFormat":{"type":"string","enum":["csv","xlsx"],"title":"ExportFormat","description":"Export file format."},"cbradio-qa__ExtractionResultRequest":{"properties":{"ocr_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ocr Text"},"status":{"type":"string","title":"Status"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"},"rates":{"items":{"$ref":"#/components/schemas/cbradio-qa__RateData"},"type":"array","title":"Rates"},"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"callsign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callsign"},"station_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station State"},"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"processing_time_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Processing Time Seconds"},"model_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Used"}},"type":"object","required":["status"],"title":"ExtractionResultRequest","description":"Compound extraction result — OCR text + rates + status in one call."},"cbradio-qa__ExtractionResultResponse":{"properties":{"status":{"type":"string","title":"Status"},"rates_stored":{"type":"integer","title":"Rates Stored"}},"type":"object","required":["status","rates_stored"],"title":"ExtractionResultResponse","description":"Response for extraction result submission."},"cbradio-qa__FCCStation":{"properties":{"facility_id":{"type":"string","title":"Facility Id"},"callsign":{"type":"string","title":"Callsign"},"normalized_callsign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Normalized Callsign"},"service_code":{"type":"string","title":"Service Code"},"frequency":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Frequency"},"channel":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Channel"},"facility_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Facility Status"},"community_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Community City"},"community_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Community State"},"network_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Network Affiliation"},"nielsen_dma_rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Nielsen Dma Rank"},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"},"licensee_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Licensee Name"},"our_station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Our Station Id"},"is_matched":{"type":"boolean","title":"Is Matched","default":false}},"type":"object","required":["facility_id","callsign","service_code"],"title":"FCCStation","description":"FCC Station from LMS database.","example":{"callsign":"WABC","community_city":"NEW YORK","community_state":"NY","facility_id":"12345","frequency":770.0,"is_matched":true,"latitude":40.7128,"longitude":-74.006,"service_code":"AM"}},"cbradio-qa__FCCStatsResponse":{"properties":{"total_fcc_stations":{"type":"integer","title":"Total Fcc Stations"},"am_stations":{"type":"integer","title":"Am Stations"},"fm_stations":{"type":"integer","title":"Fm Stations"},"matched_to_ours":{"type":"integer","title":"Matched To Ours"},"unmatched_potential_partners":{"type":"integer","title":"Unmatched Potential Partners"},"our_stations_with_fcc_id":{"type":"integer","title":"Our Stations With Fcc Id"},"our_stations_total":{"type":"integer","title":"Our Stations Total"},"fcc_coverage_percent":{"type":"number","title":"Fcc Coverage Percent"}},"type":"object","required":["total_fcc_stations","am_stations","fm_stations","matched_to_ours","unmatched_potential_partners","our_stations_with_fcc_id","our_stations_total","fcc_coverage_percent"],"title":"FCCStatsResponse","description":"FCC import statistics."},"cbradio-qa__FormatSummary":{"properties":{"nielsen_format":{"type":"string","title":"Nielsen Format","description":"Nielsen format name.","examples":["Country","AC","News/Talk"]},"station_count":{"type":"integer","title":"Station Count","description":"Number of distinct stations with this format.","examples":[453,236]},"market_count":{"type":"integer","title":"Market Count","description":"Number of distinct markets where this format appears.","examples":[233,191]},"avg_latest_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Latest Rating","description":"Average of rating_4 across all stations with this format. Null if no ratings.","examples":[4.6,6.4]}},"type":"object","required":["nielsen_format","station_count","market_count"],"title":"FormatSummary","description":"Aggregate statistics for a single Nielsen format across all markets."},"cbradio-qa__GenerateDefaultsResponse":{"properties":{"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"placements_created":{"type":"integer","title":"Placements Created"},"source":{"type":"string","title":"Source","description":"Source of placement data: 'proposal_line_items' or 'radio_buy_stations'"}},"type":"object","required":["radio_buy_id","placements_created","source"],"title":"GenerateDefaultsResponse","description":"Response from generating default placements."},"cbradio-qa__GeneratePDFResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"pdf_url":{"type":"string","title":"Pdf Url"}},"type":"object","required":["success","pdf_url"],"title":"GeneratePDFResponse","description":"Response after generating PDF."},"cbradio-qa__GenerateProposalRequest":{"properties":{"name":{"type":"string","minLength":1,"title":"Name","description":"Proposal name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"target_states":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Target States","description":"States to target (2-letter codes)"},"target_dmas":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Dmas","description":"Optional DMA filters"},"ad_type":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__proposal__AdType","description":"Type of advertising","default":"candidate"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date","description":"Election day. With `election_type`, determines the lowest-unit-charge window (47 U.S.C. 315(b)): 45 days before a primary, 60 before a general or special. Omit and rate selection will not rank on the window at all."},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type","description":"primary | general | special — sets the LUR window length"},"rate_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Context","description":"in_window | out_of_window — the caller's own statement of which side of the lowest-unit-charge boundary this buy sits on. Wins over anything derived from `election_date`: a buyer who picks 'candidate, in window' has told us directly, and that is better evidence than arithmetic on a date. Omit both and rate selection will not rank on the window at all (cbradio#376)."},"spot_duration":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Duration","description":"Spot length in seconds","default":60},"preemption":{"$ref":"#/components/schemas/cbradio-qa__PreemptionRequest","description":"Preemption preference","default":"non_preemptible"},"spots_per_week_per_station":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Spots Per Week Per Station","description":"Spots per week per station","default":10},"max_budget":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Max Budget","description":"Maximum total gross budget"},"max_stations":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Max Stations","description":"Maximum stations to include","default":50},"max_rate_per_spot":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Max Rate Per Spot","description":"Max rate per spot"},"prefer_lowest_rates":{"type":"boolean","title":"Prefer Lowest Rates","description":"Sort by lowest rates first","default":true},"rural_only":{"type":"boolean","title":"Rural Only","description":"Only include rural stations","default":false},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name","target_states","start_date","end_date"],"title":"GenerateProposalRequest","description":"Request to auto-generate a proposal based on criteria."},"cbradio-qa__GenerateProposalResponse":{"properties":{"proposal_id":{"type":"string","title":"Proposal Id"},"proposal_number":{"type":"string","title":"Proposal Number"},"stations_matched":{"type":"integer","title":"Stations Matched"},"stations_included":{"type":"integer","title":"Stations Included"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Net"},"total_spots":{"type":"integer","title":"Total Spots"},"budget_remaining":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Budget Remaining"},"message":{"type":"string","title":"Message"},"window_split":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__WindowSplitOffer"},{"type":"null"}],"description":"Present only when the flight crosses the lowest-unit-charge boundary. The proposal was generated WITHOUT ranking the window; each segment should be generated separately to price correctly."}},"type":"object","required":["proposal_id","proposal_number","stations_matched","stations_included","total_gross","total_net","total_spots","message"],"title":"GenerateProposalResponse","description":"Response from proposal generation."},"cbradio-qa__GenerateResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"order_packets":{"items":{"$ref":"#/components/schemas/cbradio-qa__OrderPacketResponse"},"type":"array","title":"Order Packets"},"total_packets":{"type":"integer","title":"Total Packets"}},"type":"object","required":["success","order_packets","total_packets"],"title":"GenerateResponse","description":"Response after generating order packets."},"cbradio-qa__GeographyDiscoveryRequest":{"properties":{"states":{"items":{"type":"string"},"type":"array","minItems":1,"title":"States","description":"Target 2-letter state codes (e.g. ['MO']).","examples":[["MO"]]},"include_border":{"type":"boolean","title":"Include Border","description":"Include border-market stations by DMA or signal contour, not just licensed state. False reproduces today's licensed-state-only behavior.","default":true},"min_contour_pct":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Min Contour Pct","description":"Minimum % of a station's signal contour that must fall inside a target state for in_contour inclusion (cbdistricts pct_of_a).","default":25.0},"exclude_non_commercial":{"type":"boolean","title":"Exclude Non Commercial","description":"Exclude non-commercial (LPFM/NCE) stations.","default":true}},"type":"object","required":["states"],"title":"GeographyDiscoveryRequest","description":"Geography-aware station discovery request (cbradio#290)."},"cbradio-qa__GeographyResponse":{"properties":{"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State filter applied.","examples":["MI","NH"]},"market":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market","description":"Market filter applied.","examples":["Detroit MI","Manchester"]},"total_markets":{"type":"integer","title":"Total Markets","description":"Distinct markets in the result.","examples":[8,1]},"total_stations":{"type":"integer","title":"Total Stations","description":"Total stations returned.","examples":[142,24]},"stations":{"items":{"$ref":"#/components/schemas/cbradio-qa__NielsenRating"},"type":"array","title":"Stations","description":"Station ratings ordered by market rank then station rank."}},"type":"object","required":["total_markets","total_stations","stations"],"title":"GeographyResponse","description":"Bulk Nielsen ratings for stations in a state or market."},"cbradio-qa__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbradio-qa__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbradio-qa__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbradio-qa__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbradio-qa__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbradio-qa__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbradio-qa__ImportRecord":{"properties":{"source_row":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Source Row"},"raw_data":{"additionalProperties":true,"type":"object","title":"Raw Data"}},"type":"object","required":["raw_data"],"title":"ImportRecord","description":"A single record to import."},"cbradio-qa__Issue":{"properties":{"number":{"type":"integer","title":"Number"},"title":{"type":"string","title":"Title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"state":{"type":"string","title":"State"},"stateReason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Statereason"},"author":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Author"},"assignees":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Assignees"},"labels":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Labels"},"milestone":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Milestone"},"isPinned":{"type":"boolean","title":"Ispinned","default":false},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat"},"closedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Closedat"},"closed":{"type":"boolean","title":"Closed","default":false},"comments":{"type":"integer","title":"Comments","default":0},"url":{"type":"string","title":"Url"},"project":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project"}},"type":"object","required":["number","title","state","url"],"title":"Issue","description":"GitHub issue response."},"cbradio-qa__IssueClose":{"properties":{"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment","description":"Closing comment"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Reason: completed or not_planned"}},"type":"object","title":"IssueClose","description":"Request body for closing an issue.","example":{"comment":"Fixed in commit abc123","reason":"completed"}},"cbradio-qa__IssueCreate":{"properties":{"title":{"type":"string","minLength":1,"title":"Title","description":"Issue title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Issue body (markdown supported)"},"labels":{"items":{"type":"string"},"type":"array","title":"Labels","description":"Labels to apply"},"assignees":{"items":{"type":"string"},"type":"array","title":"Assignees","description":"Assignee logins (use @me for self)"},"milestone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Milestone","description":"Milestone name"}},"type":"object","required":["title"],"title":"IssueCreate","description":"Request body for creating an issue.","example":{"assignees":["isenbek"],"body":"## Description\nDetailed bug report...","labels":["bug","priority:high"],"milestone":"v1.5.0","title":"Bug: Rate extraction fails on PDF"}},"cbradio-qa__IssueListResponse":{"properties":{"project":{"type":"string","title":"Project"},"total":{"type":"integer","title":"Total"},"issues":{"items":{"$ref":"#/components/schemas/cbradio-qa__Issue"},"type":"array","title":"Issues"}},"type":"object","required":["project","total","issues"],"title":"IssueListResponse","description":"Response for listing issues."},"cbradio-qa__IssueLock":{"properties":{"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Lock reason: off_topic, resolved, spam, too_heated"}},"type":"object","title":"IssueLock","description":"Request body for locking an issue.","example":{"reason":"resolved"}},"cbradio-qa__IssueReopen":{"properties":{"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment","description":"Reopening comment"}},"type":"object","title":"IssueReopen","description":"Request body for reopening an issue.","example":{"comment":"Reopening - regression found in production"}},"cbradio-qa__IssueUpdate":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"New title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"New body"},"add_labels":{"items":{"type":"string"},"type":"array","title":"Add Labels","description":"Labels to add"},"remove_labels":{"items":{"type":"string"},"type":"array","title":"Remove Labels","description":"Labels to remove"},"add_assignees":{"items":{"type":"string"},"type":"array","title":"Add Assignees","description":"Assignees to add"},"remove_assignees":{"items":{"type":"string"},"type":"array","title":"Remove Assignees","description":"Assignees to remove"},"milestone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Milestone","description":"Milestone name to set"},"remove_milestone":{"type":"boolean","title":"Remove Milestone","description":"Remove milestone association","default":false}},"type":"object","title":"IssueUpdate","description":"Request body for updating an issue.","example":{"add_assignees":["@me"],"add_labels":["needs-review"],"remove_labels":["wip"],"title":"Updated title"}},"cbradio-qa__LegacyBuyImport":{"properties":{"radio_buy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"},"callsign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callsign"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"client_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Name"},"gross_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount"},"net_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount"},"ad_production":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Ad Production"},"is_rural":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Rural"},"rural_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Amount"},"non_rural_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Amount"},"is_paid":{"type":"boolean","title":"Is Paid","default":false},"paid_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid Date"},"political_cycle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Political Cycle"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"commission_recipients":{"items":{"$ref":"#/components/schemas/cbradio-qa__CommissionRecipientCreate"},"type":"array","title":"Commission Recipients"}},"type":"object","title":"LegacyBuyImport","description":"A single historical buy for legacy import."},"cbradio-qa__LineItem":{"properties":{"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"spots_per_week":{"type":"integer","minimum":1.0,"title":"Spots Per Week"},"weeks":{"type":"integer","minimum":1.0,"title":"Weeks"},"gross_rate_per_spot":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Rate Per Spot"},"net_rate_per_spot":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate Per Spot"},"rate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id"},"proposal_id":{"type":"string","title":"Proposal Id"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Net"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"item_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Item Source","default":"manual"},"ai_suggestion_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Suggestion Id"},"action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action"},"modifications":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Modifications"},"decision_timestamp":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Decision Timestamp"},"ai_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ai Confidence"},"ai_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Reason"},"classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification"},"classification_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification Source"}},"type":"object","required":["station_callsign","spots_per_week","weeks","gross_rate_per_spot","id","proposal_id","total_gross","total_net","created_at","updated_at"],"title":"LineItem","description":"Full line item with computed fields."},"cbradio-qa__LineItemCreate":{"properties":{"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"spots_per_week":{"type":"integer","minimum":1.0,"title":"Spots Per Week"},"weeks":{"type":"integer","minimum":1.0,"title":"Weeks"},"gross_rate_per_spot":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Gross Rate Per Spot"},"net_rate_per_spot":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate Per Spot"},"rate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"item_source":{"$ref":"#/components/schemas/cbradio-qa__LineItemSource","default":"manual"},"ai_suggestion_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Suggestion Id"},"action":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__SuggestionAction"},{"type":"null"}]},"modifications":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Modifications"},"ai_confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Ai Confidence"},"ai_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Reason"},"classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification"},"classification_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification Source"}},"type":"object","required":["station_callsign","spots_per_week","weeks","gross_rate_per_spot"],"title":"LineItemCreate","description":"Fields for creating a new line item."},"cbradio-qa__LineItemResponse":{"properties":{"station_callsign":{"type":"string","maxLength":20,"title":"Station Callsign"},"station_format":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Station Format"},"dma":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Dma"},"days":{"type":"string","maxLength":20,"title":"Days","description":"Days of week (MTWRF, Sat, Sun)"},"daypart":{"type":"string","maxLength":20,"title":"Daypart","description":"Time slot (6A-10A, etc.)"},"duration_seconds":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Duration Seconds"},"gross_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Rate","description":"Rate per spot"},"spot_schedule":{"additionalProperties":true,"type":"object","title":"Spot Schedule","description":"Weekly/daily spot counts"},"total_spots":{"type":"integer","minimum":0.0,"title":"Total Spots"},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount"},"net_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Net Amount"},"id":{"type":"string","title":"Id"},"sort_order":{"type":"integer","title":"Sort Order","default":0}},"type":"object","required":["station_callsign","days","daypart","duration_seconds","gross_rate","spot_schedule","total_spots","gross_amount","net_amount","id"],"title":"LineItemResponse","description":"Response model for line item."},"cbradio-qa__LineItemSource":{"type":"string","enum":["ai","manual"],"title":"LineItemSource","description":"How a line item was added."},"cbradio-qa__LineItemUpdate":{"properties":{"spots_per_week":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Spots Per Week"},"weeks":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Weeks"},"gross_rate_per_spot":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate Per Spot"},"net_rate_per_spot":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate Per Spot"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"LineItemUpdate","description":"Fields for updating a line item (all optional)."},"cbradio-qa__LockedSlotRequest":{"properties":{"daypart_id":{"type":"string","title":"Daypart Id"},"slot_days":{"type":"string","title":"Slot Days","description":"m-f | sat | sun | sat-sun | m-sat | all","default":"all"},"spots_per_week":{"type":"integer","maximum":50.0,"minimum":0.0,"title":"Spots Per Week"}},"type":"object","required":["daypart_id","spots_per_week"],"title":"LockedSlotRequest","description":"One (station, daypart, days) held at a weight.\n\n``slot_days`` is required rather than optional because a station can sell\nthe same daypart on different days at different prices, and the solver\noffers those as separate options (cbradio#369). A lock naming only the\ndaypart would be ambiguous between them."},"cbradio-qa__MailingAddress":{"properties":{"street":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Street"},"street2":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Street2"},"city":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"City"},"state":{"anyOf":[{"type":"string","maxLength":2},{"type":"null"}],"title":"State"},"zip":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Zip"}},"type":"object","title":"MailingAddress","description":"Structured mailing address."},"cbradio-qa__MakeGoodListResponse":{"properties":{"make_good_requests":{"items":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodRequestResponse"},"type":"array","title":"Make Good Requests"}},"type":"object","required":["make_good_requests"],"title":"MakeGoodListResponse","description":"Response for listing make-good requests."},"cbradio-qa__MakeGoodReason":{"type":"string","enum":["preemption","technical_issue","scheduling_error","audio_issue","other"],"title":"MakeGoodReason","description":"Reason categories for make-good requests."},"cbradio-qa__MakeGoodRequestCreate":{"properties":{"reason_category":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodReason"},"reason_details":{"type":"string","minLength":1,"title":"Reason Details"},"affected_spots":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AffectedSpots"},{"type":"null"}]},"proposed_resolution":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proposed Resolution"},"contact_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Email"}},"type":"object","required":["reason_category","reason_details"],"title":"MakeGoodRequestCreate","description":"Request model for creating make-good request."},"cbradio-qa__MakeGoodRequestResolve":{"properties":{"status":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodStatus","description":"New status (approved, completed, rejected)"},"resolution_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Notes"}},"type":"object","required":["status"],"title":"MakeGoodRequestResolve","description":"Request model for resolving make-good request."},"cbradio-qa__MakeGoodRequestResponse":{"properties":{"id":{"type":"string","title":"Id"},"order_packet_id":{"type":"string","title":"Order Packet Id"},"estimate_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Estimate Number"},"station_group_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Group Name"},"reason_category":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodReason"},"reason_details":{"type":"string","title":"Reason Details"},"affected_spots":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AffectedSpots"},{"type":"null"}]},"proposed_resolution":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proposed Resolution"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"status":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodStatus"},"resolution_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Notes"},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At"},"resolved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved By"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","order_packet_id","reason_category","reason_details","status","created_at"],"title":"MakeGoodRequestResponse","description":"Response model for make-good request."},"cbradio-qa__MakeGoodStatus":{"type":"string","enum":["pending","approved","completed","rejected"],"title":"MakeGoodStatus","description":"Status of make-good request."},"cbradio-qa__ManualConfirmRequest":{"properties":{"confirmed_by":{"type":"string","maxLength":255,"minLength":1,"title":"Confirmed By"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["confirmed_by"],"title":"ManualConfirmRequest","description":"Request model for manual confirmation."},"cbradio-qa__ManualRateWriteRequest":{"properties":{"rates":{"items":{"$ref":"#/components/schemas/cbradio-qa__RateWriteItem"},"type":"array","title":"Rates"},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"ad_type":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__rate__AdType","default":"all"},"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"source":{"type":"string","title":"Source","description":"manual_entry or qa_correction","default":"manual_entry"},"entered_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entered By"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["rates"],"title":"ManualRateWriteRequest","description":"Request body for POST /stations/{callsign}/rates (#54)."},"cbradio-qa__MarketDetail":{"properties":{"market_rank":{"type":"integer","title":"Market Rank","description":"Nielsen market rank.","examples":[1]},"market_name":{"type":"string","title":"Market Name","description":"Market name.","examples":["New York"]},"market_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market State","description":"State code(s).","examples":["NH",null]},"population":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Population","description":"Metro population.","examples":[16563900]},"pop_black":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Black","description":"Black population.","examples":[2696600]},"pop_hispanic":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Hispanic","description":"Hispanic population.","examples":[4228100]},"station_count":{"type":"integer","title":"Station Count","description":"Number of stations returned (may be less than total if filtered).","examples":[53,4]},"stations":{"items":{"$ref":"#/components/schemas/cbradio-qa__NielsenRating"},"type":"array","title":"Stations","description":"Station ratings sorted by rating_4 descending (strongest first)."}},"type":"object","required":["market_rank","market_name","station_count","stations"],"title":"MarketDetail","description":"Full market detail including all station ratings, sorted by latest rating descending."},"cbradio-qa__MarketSummary":{"properties":{"market_rank":{"type":"integer","title":"Market Rank","description":"Nielsen market rank (1=largest).","examples":[1,50]},"market_name":{"type":"string","title":"Market Name","description":"Market name.","examples":["New York","Manchester"]},"market_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market State","description":"State code(s).","examples":["NH","TX",null]},"population":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Population","description":"Metro population.","examples":[16563900,425000]},"pop_black":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Black","description":"Black population.","examples":[2696600]},"pop_hispanic":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Hispanic","description":"Hispanic population.","examples":[4228100]},"station_count":{"type":"integer","title":"Station Count","description":"Number of distinct stations with ratings in this market.","examples":[53,15]}},"type":"object","required":["market_rank","market_name","station_count"],"title":"MarketSummary","description":"Summary of a Nielsen market with station count and demographics."},"cbradio-qa__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbradio-qa__NetworkBuyCreate":{"properties":{"network_id":{"type":"string","title":"Network Id"},"rate_type":{"type":"string","title":"Rate Type"},"spot_length":{"type":"string","title":"Spot Length"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"num_weeks":{"type":"integer","title":"Num Weeks"},"flight_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flight End"},"proposal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proposal Id"},"radio_buy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"}},"type":"object","required":["network_id","rate_type","spot_length","spots_per_week","num_weeks"],"title":"NetworkBuyCreate","description":"Fields required to create a StateNets network buy."},"cbradio-qa__NetworkBuyUpdate":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"flight_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flight End"}},"type":"object","title":"NetworkBuyUpdate","description":"Fields that can be updated on a StateNets network buy."},"cbradio-qa__NetworkItemIn":{"properties":{"network_id":{"type":"string","title":"Network Id"},"network_name":{"type":"string","title":"Network Name"},"network_state":{"type":"string","title":"Network State"},"wired_network_type":{"type":"string","enum":["statenets","westwood_one_wire","premiere_networks_wire"],"title":"Wired Network Type"},"product_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Type"},"station_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Station Count"},"rate_type":{"type":"string","title":"Rate Type"},"spot_length":{"type":"string","title":"Spot Length"},"rate_per_spot":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate Per Spot"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"num_weeks":{"type":"integer","title":"Num Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"total_gross":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Total Gross"},"ruralam_commission":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Ruralam Commission"},"statenets_commission":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Statenets Commission"},"buyer_agency_commission":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Buyer Agency Commission"},"station_share":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Station Share"},"flight_start":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight End"},"status":{"type":"string","title":"Status","default":"draft"}},"type":"object","required":["network_id","network_name","network_state","wired_network_type","rate_type","spot_length","rate_per_spot","spots_per_week","num_weeks","total_spots","total_gross","ruralam_commission","statenets_commission","buyer_agency_commission","station_share"],"title":"NetworkItemIn","description":"Inbound network-item row pushed from cbradio-ui on radio-buy sync."},"cbradio-qa__NetworkItemOut":{"properties":{"network_id":{"type":"string","title":"Network Id"},"network_name":{"type":"string","title":"Network Name"},"network_state":{"type":"string","title":"Network State"},"wired_network_type":{"type":"string","enum":["statenets","westwood_one_wire","premiere_networks_wire"],"title":"Wired Network Type"},"product_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Type"},"station_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Station Count"},"rate_type":{"type":"string","title":"Rate Type"},"spot_length":{"type":"string","title":"Spot Length"},"rate_per_spot":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Rate Per Spot"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"num_weeks":{"type":"integer","title":"Num Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"ruralam_commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Ruralam Commission"},"statenets_commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Statenets Commission"},"buyer_agency_commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Buyer Agency Commission"},"station_share":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Share"},"flight_start":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight End"},"status":{"type":"string","title":"Status","default":"draft"},"id":{"type":"string","title":"Id"},"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["network_id","network_name","network_state","wired_network_type","rate_type","spot_length","rate_per_spot","spots_per_week","num_weeks","total_spots","total_gross","ruralam_commission","statenets_commission","buyer_agency_commission","station_share","id","radio_buy_id","created_at","updated_at"],"title":"NetworkItemOut","description":"Network-item row as stored — includes id and timestamps."},"cbradio-qa__NetworkItemsReplaceRequest":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbradio-qa__NetworkItemIn"},"type":"array","title":"Items"}},"type":"object","title":"NetworkItemsReplaceRequest","description":"Replace-all payload for PUT /radio-buys/{id}/network-items."},"cbradio-qa__NetworkItemsReplaceResponse":{"properties":{"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"inserted":{"type":"integer","title":"Inserted"},"deleted":{"type":"integer","title":"Deleted"},"items":{"items":{"$ref":"#/components/schemas/cbradio-qa__NetworkItemOut"},"type":"array","title":"Items"}},"type":"object","required":["radio_buy_id","inserted","deleted","items"],"title":"NetworkItemsReplaceResponse","description":"Response from a replace-all sync."},"cbradio-qa__NetworkLineItemResponse":{"properties":{"id":{"type":"string","title":"Id"},"network_id":{"type":"string","title":"Network Id"},"network_name":{"type":"string","title":"Network Name"},"network_state":{"type":"string","title":"Network State"},"wired_network_type":{"type":"string","title":"Wired Network Type"},"rate_type":{"type":"string","title":"Rate Type"},"spot_length":{"type":"string","title":"Spot Length"},"rate_per_spot":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Rate Per Spot"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"num_weeks":{"type":"integer","title":"Num Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount"},"net_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Net Amount"},"flight_start":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight End"}},"type":"object","required":["id","network_id","network_name","network_state","wired_network_type","rate_type","spot_length","rate_per_spot","spots_per_week","num_weeks","total_spots","gross_amount","net_amount"],"title":"NetworkLineItemResponse","description":"Response model for a wired-network line item (#201 Phase 3).\n\nOne row per (network, rate_type, spot_length) on a network-aggregator\norder packet. Distinct from station line items — no callsign, per-network\ntotals instead of per-station."},"cbradio-qa__NetworkScoresResponse":{"properties":{"network_id":{"type":"string","title":"Network Id"},"network_name":{"type":"string","title":"Network Name"},"affiliate_count":{"type":"integer","title":"Affiliate Count"},"scored_affiliate_count":{"type":"integer","title":"Scored Affiliate Count"},"aggregate_score":{"$ref":"#/components/schemas/cbradio-qa__AggregateScore"},"affiliates":{"items":{"$ref":"#/components/schemas/cbradio-qa__AffiliateScore"},"type":"array","title":"Affiliates"}},"type":"object","required":["network_id","network_name","affiliate_count","scored_affiliate_count","aggregate_score","affiliates"],"title":"NetworkScoresResponse","description":"Response for GET /networks/{network_id}/scores."},"cbradio-qa__NetworkUpdate":{"properties":{"candidate_rate_60":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Candidate Rate 60"},"candidate_rate_30":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Candidate Rate 30"},"issue_rate_60":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Issue Rate 60"},"issue_rate_30":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Issue Rate 30"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"daypart":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Daypart"}},"type":"object","title":"NetworkUpdate","description":"Fields that can be updated on a StateNets network."},"cbradio-qa__NielsenRating":{"properties":{"id":{"type":"integer","title":"Id","description":"Internal row ID."},"callsign":{"type":"string","title":"Callsign","description":"Station callsign including band suffix.","examples":["WLTW-FM","WINS-AM","WSKQ-FM"]},"market_rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Market Rank","description":"Nielsen market rank, 1-246. #1 = New York, #2 = Los Angeles, etc.","examples":[1,50,239]},"market_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Name","description":"Market name as listed by Nielsen.","examples":["New York","Manchester","Dallas-Fort Worth"]},"market_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market State","description":"State code(s). Multi-state markets hyphenated (e.g. 'IA-IL'). Null for major metros.","examples":["NH","TX","IA-IL",null]},"population":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Population","description":"Metro area population (persons 12+).","examples":[16563900,425000]},"pop_black":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Black","description":"Black population in metro area.","examples":[2696600,12000]},"pop_hispanic":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Hispanic","description":"Hispanic population in metro area.","examples":[4228100,18000]},"survey_period":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Survey Period","description":"Survey period label (e.g. 'Holiday 25', 'Fall 2025').","examples":["Holiday 25","Fall 2025","December 2025"]},"methodology":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Methodology","description":"Measurement method. 'PPM 6+' = electronic (top ~50 markets), '12+' = diary (smaller markets).","examples":["PPM 6+","12+"]},"nielsen_format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Format","description":"Station format per Nielsen (67 known formats). Different from station.format (our normalized version).","examples":["AC","Classic Rock","News/Talk","Country","CHR"]},"nielsen_owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Owner","description":"Station owner as listed by Nielsen.","examples":["iHeartMedia","Audacy","Townsquare","Spanish Broadcasting System"]},"rating_period_1":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating Period 1","description":"Label for rating period 1 (oldest).","examples":["Oct 25","Spr 24"]},"rating_1":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating 1","description":"AQH Share for period 1. Null if station not measured.","examples":[7.4,3.8]},"rating_period_2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating Period 2","description":"Label for rating period 2.","examples":["Nov 25","Fall 24"]},"rating_2":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating 2","description":"AQH Share for period 2.","examples":[7.4,4.1]},"rating_period_3":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating Period 3","description":"Label for rating period 3.","examples":["Dec 25","Spr 25"]},"rating_3":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating 3","description":"AQH Share for period 3.","examples":[9.9,3.5]},"rating_period_4":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating Period 4","description":"Label for rating period 4 (most recent). Use this for 'current' rating.","examples":["Hol 25","Fall 25"]},"rating_4":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating 4","description":"AQH Share for period 4 — the most recent rating. >5.0 is strong, >10.0 is dominant.","examples":[12.5,5.6,3.9]},"source_file":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source File","description":"Source data file that this rating was parsed from.","examples":["arb001.txt","arb050.txt"]},"market_station_rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Market Station Rank","description":"Station's rank within its market (1 = highest AQH share). Computed from rating_4 DESC.","examples":[1,5,15]},"market_station_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Market Station Count","description":"Total rated stations in this market. Use with rank to compute percentile.","examples":[53,24,12]},"market_percentile":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Market Percentile","description":"Normalized 0-1 score. 1.0 = top station, 0.0 = bottom. Formula: 1 - (rank-1)/count.","examples":[1.0,0.87,0.5]},"is_top_quartile":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Top Quartile","description":"True if station is in top 25% of its market by AQH share."},"ratings_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ratings Score","description":"0-4 score for station scoring. 4=top 25%, 3=25-50%, 2=50-75%, 1=bottom 25%, 0=no data.","examples":[4,3,2,1]}},"type":"object","required":["id","callsign"],"title":"NielsenRating","description":"A single Nielsen rating entry for a station in a market.\n\nEach row represents one station's performance in one market during one survey period.\nStations can appear in multiple markets (e.g. WLTW-FM appears in New York, Nassau-Suffolk,\nHudson Valley, etc.). Ratings are AQH Share (Average Quarter-Hour percentage of listeners)."},"cbradio-qa__NielsenStatsResponse":{"properties":{"total_ratings":{"type":"integer","title":"Total Ratings","description":"Total number of rating rows in the database.","examples":[5151]},"unique_callsigns":{"type":"integer","title":"Unique Callsigns","description":"Number of distinct station callsigns.","examples":[4638]},"unique_markets":{"type":"integer","title":"Unique Markets","description":"Number of distinct Nielsen markets.","examples":[243]},"unique_formats":{"type":"integer","title":"Unique Formats","description":"Number of distinct Nielsen format values.","examples":[66]}},"type":"object","required":["total_ratings","unique_callsigns","unique_markets","unique_formats"],"title":"NielsenStatsResponse","description":"High-level statistics about the Nielsen ratings dataset."},"cbradio-qa__OrderPacketDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"estimate_number":{"type":"string","title":"Estimate Number"},"payee_group_key":{"type":"string","title":"Payee Group Key"},"station_group_name":{"type":"string","title":"Station Group Name"},"contact_email":{"type":"string","title":"Contact Email"},"cc_emails":{"items":{"type":"string"},"type":"array","title":"Cc Emails","default":[]},"status":{"$ref":"#/components/schemas/cbradio-qa__OrderPacketStatus"},"confirmation_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Token"},"token_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Token Expires At"},"total_spots":{"type":"integer","title":"Total Spots"},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount"},"net_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Net Amount"},"station_payment":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Payment"},"total_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Total Gross"},"total_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Total Net"},"spot_breakdown_type":{"$ref":"#/components/schemas/cbradio-qa__SpotBreakdownType"},"pdf_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pdf Url"},"audio_spot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Spot Url"},"nab_form_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nab Form Url"},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At"},"confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmed At"},"confirmed_via":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ConfirmationVia"},{"type":"null"}]},"confirmed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmed By"},"cancelled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cancelled At"},"cancellation_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cancellation Reason"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"stations":{"items":{"type":"string"},"type":"array","title":"Stations","default":[]},"confirmation_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Url"},"payee_type":{"type":"string","title":"Payee Type","default":"station_rep"},"line_items":{"items":{"$ref":"#/components/schemas/cbradio-qa__LineItemResponse"},"type":"array","title":"Line Items","default":[]},"network_line_items":{"items":{"$ref":"#/components/schemas/cbradio-qa__NetworkLineItemResponse"},"type":"array","title":"Network Line Items","default":[]}},"type":"object","required":["id","radio_buy_id","estimate_number","payee_group_key","station_group_name","contact_email","status","total_spots","gross_amount","net_amount","station_payment","spot_breakdown_type","created_at","updated_at"],"title":"OrderPacketDetailResponse","description":"Detailed response with line items.\n\nFor network-aggregator packets (`payee_type='network_aggregator'`),\n`line_items` is empty and `network_line_items` is populated.\nFor station packets, `network_line_items` is empty and `line_items`\ncarries per-station detail."},"cbradio-qa__OrderPacketGenerate":{"properties":{"spot_breakdown_type":{"$ref":"#/components/schemas/cbradio-qa__SpotBreakdownType","default":"weekly"},"audio_spot_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Audio Spot Url"},"include_nab_form":{"type":"boolean","title":"Include Nab Form","default":true}},"type":"object","title":"OrderPacketGenerate","description":"Request model for generating order packets."},"cbradio-qa__OrderPacketListResponse":{"properties":{"order_packets":{"items":{"$ref":"#/components/schemas/cbradio-qa__OrderPacketDetailResponse"},"type":"array","title":"Order Packets"}},"type":"object","required":["order_packets"],"title":"OrderPacketListResponse","description":"Response for listing order packets."},"cbradio-qa__OrderPacketResponse":{"properties":{"id":{"type":"string","title":"Id"},"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"estimate_number":{"type":"string","title":"Estimate Number"},"payee_group_key":{"type":"string","title":"Payee Group Key"},"station_group_name":{"type":"string","title":"Station Group Name"},"contact_email":{"type":"string","title":"Contact Email"},"cc_emails":{"items":{"type":"string"},"type":"array","title":"Cc Emails","default":[]},"status":{"$ref":"#/components/schemas/cbradio-qa__OrderPacketStatus"},"confirmation_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Token"},"token_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Token Expires At"},"total_spots":{"type":"integer","title":"Total Spots"},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount"},"net_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Net Amount"},"station_payment":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Payment"},"total_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Total Gross"},"total_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Total Net"},"spot_breakdown_type":{"$ref":"#/components/schemas/cbradio-qa__SpotBreakdownType"},"pdf_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pdf Url"},"audio_spot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Spot Url"},"nab_form_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nab Form Url"},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At"},"confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmed At"},"confirmed_via":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ConfirmationVia"},{"type":"null"}]},"confirmed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmed By"},"cancelled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cancelled At"},"cancellation_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cancellation Reason"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"stations":{"items":{"type":"string"},"type":"array","title":"Stations","default":[]},"confirmation_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Url"},"payee_type":{"type":"string","title":"Payee Type","default":"station_rep"}},"type":"object","required":["id","radio_buy_id","estimate_number","payee_group_key","station_group_name","contact_email","status","total_spots","gross_amount","net_amount","station_payment","spot_breakdown_type","created_at","updated_at"],"title":"OrderPacketResponse","description":"Response model for order packet."},"cbradio-qa__OrderPacketStatus":{"type":"string","enum":["pending_review","ready_to_send","sent","confirmed","make_good_requested","cancelled"],"title":"OrderPacketStatus","description":"Status of an order packet."},"cbradio-qa__OrderPacketUpdate":{"properties":{"contact_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Email"},"cc_emails":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Cc Emails"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__OrderPacketStatus"},{"type":"null"}]},"audio_spot_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Audio Spot Url"},"nab_form_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Nab Form Url"}},"type":"object","title":"OrderPacketUpdate","description":"Request model for updating an order packet."},"cbradio-qa__OverdueOrder":{"properties":{"id":{"type":"string","title":"Id"},"estimate_number":{"type":"string","title":"Estimate Number"},"station_group_name":{"type":"string","title":"Station Group Name"},"sent_at":{"type":"string","format":"date-time","title":"Sent At"},"days_since_sent":{"type":"integer","title":"Days Since Sent"}},"type":"object","required":["id","estimate_number","station_group_name","sent_at","days_since_sent"],"title":"OverdueOrder","description":"Order that hasn't been confirmed."},"cbradio-qa__OwnershipGroupCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Display name"},"legal_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Legal Name","description":"Full legal entity name for documents"},"payment_method":{"type":"string","pattern":"^(ach|portal|check|wire)$","title":"Payment Method","description":"Payment method: ach, portal, check, or wire","default":"check"},"stations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Stations","description":"List of station callsigns"},"contact_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Contact Phone"},"mailing_address":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__MailingAddress"},{"type":"null"}],"description":"Structured mailing address"},"memo_template":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Memo Template","description":"Default memo line for checks"},"portal_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Portal Url"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name"],"title":"OwnershipGroupCreate","description":"Request model for creating an ownership group (payment recipient)."},"cbradio-qa__OwnershipGroupResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"legal_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legal Name"},"payment_method":{"type":"string","title":"Payment Method"},"stations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Stations"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone"},"mailing_address":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__MailingAddress"},{"type":"null"}]},"mailing_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mailing Lat"},"mailing_lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mailing Lon"},"memo_template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Memo Template"},"portal_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Portal Url"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","payment_method","is_active","created_at","updated_at"],"title":"OwnershipGroupResponse","description":"Response model for an ownership group (payment recipient)."},"cbradio-qa__OwnershipGroupUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"legal_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Legal Name"},"payment_method":{"anyOf":[{"type":"string","pattern":"^(ach|portal|check|wire)$"},{"type":"null"}],"title":"Payment Method"},"stations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Stations"},"contact_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Contact Phone"},"mailing_address":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__MailingAddress"},{"type":"null"}]},"memo_template":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Memo Template"},"portal_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Portal Url"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"OwnershipGroupUpdate","description":"Request model for updating an ownership group."},"cbradio-qa__PayeeGroupDelta":{"properties":{"group_key":{"type":"string","title":"Group Key"},"pay_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pay To"},"change_type":{"type":"string","title":"Change Type"},"old_values":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Old Values"},"new_values":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"New Values"}},"type":"object","required":["group_key","change_type"],"title":"PayeeGroupDelta","description":"Change summary for a payee group."},"cbradio-qa__PayeeGroupWithStatus":{"properties":{"group_key":{"type":"string","title":"Group Key"},"pay_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pay To"},"payment_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Method"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"station_count":{"type":"integer","title":"Station Count","default":0},"spot_count":{"type":"integer","title":"Spot Count","default":0},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount","default":"0"},"station_payments":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Payments","default":"0"},"commission_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission Amount","default":"0"},"station_callsigns":{"items":{"type":"string"},"type":"array","title":"Station Callsigns"},"has_provisional":{"type":"boolean","title":"Has Provisional","default":false},"payment_status":{"$ref":"#/components/schemas/cbradio-qa__PaymentStatus"}},"type":"object","required":["group_key","payment_status"],"title":"PayeeGroupWithStatus","description":"Payee group with payment status info."},"cbradio-qa__PaymentHistoryResponse":{"properties":{"version_id":{"type":"string","title":"Version Id"},"version_number":{"type":"integer","title":"Version Number"},"total_changes":{"type":"integer","title":"Total Changes"},"history":{"items":{"$ref":"#/components/schemas/cbradio-qa__PaymentStatusHistory"},"type":"array","title":"History"}},"type":"object","required":["version_id","version_number","total_changes","history"],"title":"PaymentHistoryResponse","description":"Response for payment status history."},"cbradio-qa__PaymentStatus":{"properties":{"status":{"$ref":"#/components/schemas/cbradio-qa__PaymentStatusEnum","default":"not_started"},"paid_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Paid Date"},"reference_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference Number"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id"},"version_id":{"type":"string","title":"Version Id"},"group_key":{"type":"string","title":"Group Key"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"}},"type":"object","required":["id","version_id","group_key"],"title":"PaymentStatus","description":"Full payment status record."},"cbradio-qa__PaymentStatusEnum":{"type":"string","enum":["not_started","in_progress","paid","issue"],"title":"PaymentStatusEnum","description":"Payment status for payee groups."},"cbradio-qa__PaymentStatusHistory":{"properties":{"id":{"type":"string","title":"Id"},"payment_status_id":{"type":"string","title":"Payment Status Id"},"version_id":{"type":"string","title":"Version Id"},"group_key":{"type":"string","title":"Group Key"},"previous_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous Status"},"new_status":{"type":"string","title":"New Status"},"previous_paid_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Previous Paid Date"},"new_paid_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"New Paid Date"},"previous_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous Reference"},"new_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"New Reference"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"changed_at":{"type":"string","format":"date-time","title":"Changed At"},"changed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Changed By"}},"type":"object","required":["id","payment_status_id","version_id","group_key","new_status","changed_at"],"title":"PaymentStatusHistory","description":"Payment status change history record."},"cbradio-qa__PendingMakeGood":{"properties":{"id":{"type":"string","title":"Id"},"estimate_number":{"type":"string","title":"Estimate Number"},"station_group_name":{"type":"string","title":"Station Group Name"},"reason_category":{"$ref":"#/components/schemas/cbradio-qa__MakeGoodReason"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","estimate_number","station_group_name","reason_category","created_at"],"title":"PendingMakeGood","description":"Pending make-good for dashboard."},"cbradio-qa__PlacementCreate":{"properties":{"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id","description":"Station ID"},"station_callsign":{"type":"string","title":"Station Callsign","description":"Station callsign (e.g., WABC-AM)"},"daypart":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Daypart","description":"Time slot/daypart name"},"spots_per_week":{"type":"integer","minimum":1.0,"title":"Spots Per Week","description":"Number of spots per week"},"rate":{"type":"number","minimum":0.0,"title":"Rate","description":"Rate per spot"},"weeks":{"items":{"type":"integer"},"type":"array","title":"Weeks","description":"ISO week numbers in flight. If empty, backend computes from the parent radio_buy's start_date/end_date (cbradio#251)."},"source_rate_card_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Rate Card Id","description":"The rate_card.id whose rates this placement was priced from. Snapshot for audit + the three-axis gates (cbradio#251). Backend resolves from station_id+year if not provided."},"simulcast_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulcast Group Id","description":"Simulcast group ID if this station is part of a simulcast group"},"simulcast_callsigns":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Simulcast Callsigns","description":"All callsigns in the simulcast group (e.g., ['WABC-AM', 'WABC-FM', 'W201AB'])"}},"type":"object","required":["station_callsign","spots_per_week","rate"],"title":"PlacementCreate","description":"Model for creating a placement on a radio buy."},"cbradio-qa__PlanFlightShape":{"type":"string","enum":["even","front_loaded","back_loaded","closing_surge"],"title":"PlanFlightShape","description":"How a station's spots distribute across the weeks of the flight."},"cbradio-qa__PlanLineResponse":{"properties":{"callsign":{"type":"string","title":"Callsign"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"daypart_id":{"type":"string","title":"Daypart Id"},"slot_days":{"type":"string","title":"Slot Days"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"weeks":{"type":"integer","title":"Weeks"},"weekly_spots":{"items":{"type":"integer"},"type":"array","title":"Weekly Spots"},"total_spots":{"type":"integer","title":"Total Spots"},"gross_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Rate"},"net_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"basis":{"type":"string","title":"Basis","description":"card | benchmark"},"basis_label":{"type":"string","title":"Basis Label"},"verification":{"type":"string","title":"Verification","description":"How much scrutiny this price has had: human_reviewed | auto_approved | unreviewed. 94% of live rates are auto_approved — a structured feed approved on arrival because it parses cleanly, with nobody having checked the number itself.","default":""},"priced_from":{"type":"string","title":"Priced From","description":"Which daypart slug on the card supplied this price. Equal to daypart_id when the card priced the slot being bought; 'ros', 'evening' and so on when a rate is standing in. We buy only the three standard dayparts (6a-10a, 10a-3p, 3p-7p), so a borrowed price is routine — and must be visible on the quote.","default":""},"card_is_current":{"type":"boolean","title":"Card Is Current"},"classification":{"type":"string","title":"Classification"},"score":{"type":"integer","title":"Score"},"rationale":{"type":"string","title":"Rationale"},"rate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Id"}},"type":"object","required":["callsign","state","daypart_id","slot_days","spots_per_week","weeks","weekly_spots","total_spots","gross_rate","net_rate","total_gross","basis","basis_label","card_is_current","classification","score","rationale","rate_id"],"title":"PlanLineResponse","description":"One station-daypart buy — the grain buys are actually placed at."},"cbradio-qa__PlanPostureRequest":{"properties":{"flight_shape":{"$ref":"#/components/schemas/cbradio-qa__PlanFlightShape","default":"even"},"min_spots_per_week":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Min Spots Per Week","description":"Station-level floor, summed across dayparts","default":8},"max_spots_per_week":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Max Spots Per Week","description":"Station-level cap; 50 is the hard rail","default":40},"max_stations":{"anyOf":[{"type":"integer","maximum":1000.0,"minimum":1.0},{"type":"null"}],"title":"Max Stations"},"min_score":{"type":"integer","maximum":16.0,"minimum":0.0,"title":"Min Score","description":"Relevance floor. Statewide queries cap at 8 of 16 (coverage needs district geography, CPE needs voter data), so a floor above 8 will empty a statewide plan. District targeting activates coverage and raises the ceiling to 12.","default":0},"network_preference":{"type":"number","maximum":2.0,"minimum":0.0,"title":"Network Preference","description":"Margin lever. 0 = pure client outcome (the default, per CJ's 2026-08-15 ruling). Its position is echoed back on the plan.","default":0.0},"reach_bias":{"type":"number","maximum":10.0,"exclusiveMinimum":0.0,"title":"Reach Bias","description":">1 more stations, <1 fewer and harder","default":1.0},"require_card_rates":{"type":"boolean","title":"Require Card Rates","description":"Refuse benchmark-estimated prices","default":false},"exclude_dayparts":{"items":{"type":"string"},"type":"array","title":"Exclude Dayparts"},"locked_stations":{"additionalProperties":{"items":{"$ref":"#/components/schemas/cbradio-qa__LockedSlotRequest"},"type":"array"},"type":"object","title":"Locked Stations","description":"Stations the buyer has committed to: callsign -> list of locked slots. A non-empty list locks exact weekly spot counts and the solver leaves that station alone in every daypart; an empty list pins the station into the plan and lets the solver choose the weight. Locking a plan's own output and re-solving reproduces it exactly, which is what makes lock-and-adjust safe. A lock overrides min_score — naming a station is the relevance judgment that floor approximates — but not require_card_rates."},"excluded_stations":{"items":{"type":"string"},"type":"array","maxItems":500,"title":"Excluded Stations","description":"Callsigns never to buy. Wins over a lock on the same station."}},"type":"object","title":"PlanPostureRequest","description":"The strategy knobs — shape decisions, never prices.\n\nAn LLM may fill these in and a human confirms them before the solve. None of\nit can produce a wrong rate: a bad value here yields a differently *shaped*\nplan, which is the whole point of keeping the model on this side of the line."},"cbradio-qa__PlanRequest":{"properties":{"target_states":{"items":{"type":"string"},"type":"array","maxItems":20,"title":"Target States"},"target_districts":{"items":{"type":"string"},"type":"array","maxItems":200,"title":"Target Districts","description":"District ids, e.g. ['MI-SD-020','MI-SD-019']"},"district_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"District Type","description":"congressional | state_senate | state_house | county"},"budget":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Budget"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type","description":"primary | general | special — sets the audience profile, and with `election_date` the length of the LUR window (cbradio#376)"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date","description":"Election day. With `election_type`, determines the lowest-unit-charge window (47 U.S.C. 315(b)): 45 days before a primary, 60 before a general or special. Omit and the solver will not rank on the window — it will not guess one."},"rate_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Context","description":"in_window | out_of_window — the caller's own statement of which side of the lowest-unit-charge boundary this buy sits on. Wins over anything derived from `election_date`: a buyer who picks 'candidate, in window' has told us directly, and that is better evidence than arithmetic on a date. Omit both and rate selection will not rank on the window at all (cbradio#376)."},"target_audience":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Audience","description":"republican_primary | democratic_primary | swing_general. Wins over election_type when given. A caller that already knows the audience should say so: election_type cannot express partisan lean, so a Democratic primary reaching this endpoint as 'primary' would be scored against a Republican format profile."},"ad_type":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__proposal__AdType","default":"candidate"},"spot_duration":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Duration","default":60},"preemption":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__PreemptionRequest"},{"type":"null"}]},"station_pool":{"$ref":"#/components/schemas/cbradio-qa__StationPool","default":"all"},"election_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Election Year","description":"Freshness bar for rate cards. Defaults to the current year."},"posture":{"$ref":"#/components/schemas/cbradio-qa__PlanPostureRequest"}},"type":"object","required":["budget","start_date","end_date"],"title":"PlanRequest","description":"Campaign parameters plus a posture. Writes nothing.\n\nTarget **either** whole states or a set of districts. Districts win when\nboth are supplied, and they are the better mode: sub-state targeting\nactivates the coverage dimension (geo-fit — district reach blended with the\nshare of the station's own signal that lands inside it), where a statewide\nquery has no fit dimension to blend and scores cap at 8 of 16."},"cbradio-qa__PlanResponse":{"properties":{"lines":{"items":{"$ref":"#/components/schemas/cbradio-qa__PlanLineResponse"},"type":"array","title":"Lines"},"budget":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Budget"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_spots":{"type":"integer","title":"Total Spots"},"weeks":{"type":"integer","title":"Weeks"},"budget_used_pct":{"type":"number","title":"Budget Used Pct"},"budget_remaining":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Budget Remaining"},"stations_included":{"type":"integer","title":"Stations Included"},"stations_estimated":{"type":"integer","title":"Stations Estimated","description":"Stations priced from a benchmark rather than a card"},"station_spots_per_week":{"additionalProperties":{"type":"integer"},"type":"object","title":"Station Spots Per Week"},"targeting":{"type":"string","title":"Targeting","description":"statewide | district"},"districts_targeted":{"items":{"type":"string"},"type":"array","title":"Districts Targeted"},"stations_considered":{"type":"integer","title":"Stations Considered"},"stations_priced_from_card":{"type":"integer","title":"Stations Priced From Card"},"stations_priced_from_benchmark":{"type":"integer","title":"Stations Priced From Benchmark"},"stations_unpriced":{"type":"integer","title":"Stations Unpriced"},"candidates_eligible":{"type":"integer","title":"Candidates Eligible"},"audience":{"type":"string","title":"Audience"},"posture":{"$ref":"#/components/schemas/cbradio-qa__PlanPostureRequest"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"},"window_split":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__WindowSplitOffer"},{"type":"null"}],"description":"Present only when the flight crosses the lowest-unit-charge boundary. The plan above is priced WITHOUT ranking the window (there is no single correct side), so treat this as an offer to re-request each segment separately — see cbradio#376."}},"type":"object","required":["lines","budget","total_gross","total_spots","weeks","budget_used_pct","budget_remaining","stations_included","stations_estimated","station_spots_per_week","targeting","stations_considered","stations_priced_from_card","stations_priced_from_benchmark","stations_unpriced","candidates_eligible","audience","posture","warnings"],"title":"PlanResponse","description":"A complete, priced, flighted plan and an account of how it got there."},"cbradio-qa__PlanningApprovalBody":{"properties":{"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","title":"PlanningApprovalBody","description":"Request body for POST /rate-cards/{id}/approve-planning (#205).\n\nOptional note — e.g. \"Confirmed with Angie Martin at iHeart 2026-04-22,\nrate card is accurate for this flight window\"."},"cbradio-qa__PlanningApprovalResponse":{"properties":{"rate_card_id":{"type":"string","title":"Rate Card Id"},"lifecycle":{"type":"string","title":"Lifecycle"},"planning_approved_by":{"type":"string","title":"Planning Approved By"},"planning_approved_at":{"type":"string","format":"date-time","title":"Planning Approved At"},"planning_approval_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Planning Approval Note"}},"type":"object","required":["rate_card_id","lifecycle","planning_approved_by","planning_approved_at"],"title":"PlanningApprovalResponse","description":"Response for POST /rate-cards/{id}/approve-planning."},"cbradio-qa__PrecisionTrakImportItem":{"properties":{"callsign":{"type":"string","title":"Callsign"},"band":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Band"},"national_rep_firm":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"National Rep Firm"},"is_commercial":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Commercial"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"simulcast_group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulcast Group"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"mailing_street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Street"},"mailing_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing City"},"mailing_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing State"},"mailing_zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Zip"}},"type":"object","required":["callsign"],"title":"PrecisionTrakImportItem","description":"Single item in a PrecisionTrak enrichment import."},"cbradio-qa__PrecisionTrakImportRequest":{"properties":{"stations":{"items":{"$ref":"#/components/schemas/cbradio-qa__PrecisionTrakImportItem"},"type":"array","title":"Stations"}},"type":"object","required":["stations"],"title":"PrecisionTrakImportRequest","description":"Request body for PrecisionTrak CSV import."},"cbradio-qa__PreemptionRequest":{"type":"string","enum":["non_preemptible","preemptible_with_notice","preemptible"],"title":"PreemptionRequest","description":"What a buyer can ask for — the guarantee, not the row's status.\n\ncbradio#353. This used to be a second class named ``PreemptionStatus``\ncarrying two of the four members its namesake in ``models/rate.py`` has, so\n``preemptible_with_notice`` was unrequestable and **2,077 stations holding\n71,159 rows could not be reached by any request**. CJ's ruling, 2026-08-04:\nthree rungs, because the middle one is a distinct promise and is priced like\none.\n\n| what the buyer gets       | median gross | stations |\n|---------------------------|--------------|----------|\n| ``non_preemptible``       | ``$35.00``   | 4,100    |\n| ``preemptible_with_notice`` | ``$20.00`` | 2,077    |\n| ``preemptible``           | ``$15.00``   | 2,941    |\n\nWith-notice is 33% above plain preemptible, so folding it into the\n``preemptible`` search would answer a request for a ``$15`` rate with a\n``$20`` one carrying a different guarantee.\n\n``PreemptionStatus.ALL`` is deliberately absent: ``all`` describes a rate row\n(\"this price applies whatever the preemption status\"), not something a buyer\nasks for, and it already reaches every request through the\n``OR r.preemption = 'all'`` branch in ``generate_proposal``. Every member here\nmust be a valid ``PreemptionStatus`` — the canonical vocabulary lives there."},"cbradio-qa__PreemptionStatus":{"type":"string","enum":["non_preemptible","preemptible","preemptible_with_notice","all"],"title":"PreemptionStatus","description":"Whether a spot can be preempted by higher-paying advertisers."},"cbradio-qa__PricingAdjustment":{"properties":{"gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate","description":"Gross rate (before commission)"},"net_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate","description":"Net rate (after commission)"}},"type":"object","title":"PricingAdjustment","description":"Adjust pricing fields only - for AI correction workflow.\n\nIf only gross_rate provided, net_rate is auto-calculated as gross * 0.85.\nIf only net_rate provided, gross_rate is auto-calculated as net / 0.85."},"cbradio-qa__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbradio-qa__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbradio-qa__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbradio-qa__Project":{"properties":{"slug":{"type":"string","title":"Slug","description":"Project slug (e.g., cbradio)"},"repo":{"type":"string","title":"Repo","description":"GitHub repo path (e.g., isenbek/cbradio)"},"description":{"type":"string","title":"Description","description":"Project description"}},"type":"object","required":["slug","repo","description"],"title":"Project","description":"CB* project metadata."},"cbradio-qa__ProposalClassEnum":{"type":"string","enum":["candidate_in_window","candidate_out_of_window","issue"],"title":"ProposalClassEnum","description":"Political-window classification for a radio buy (cbradio#241 / #249).\n\nDrives the `window_mismatch` gate in order-packet generation and the\n`?proposal_class=` filter on `/stations/{cs}/rates`. Maps via\n`PROPOSAL_CLASS_MAP` in `src/lib/rate_filters.py` to the allowed\n(ad_type, rate_context) tuples for the buy's placements."},"cbradio-qa__ProposalCreate":{"properties":{"name":{"type":"string","minLength":1,"title":"Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"consultant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consultant Id"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"},"ad_type":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__proposal__AdType","default":"candidate"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date"},"spot_duration":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Duration","default":60},"preemption":{"$ref":"#/components/schemas/cbradio-qa__PreemptionRequest","default":"non_preemptible"},"time_slot_preference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Slot Preference"},"target_states":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target States"},"target_dmas":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Dmas"},"rural_only":{"type":"boolean","title":"Rural Only","default":false},"station_pool":{"$ref":"#/components/schemas/cbradio-qa__StationPool","description":"Station pool: 'all' = full FCC universe, 'network_only' = In-Network + Salem only.","default":"all"},"commission_rate":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Commission Rate","default":"0.15"},"target_geography":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Target Geography","description":"Structured geography: {states, districts, DMAs, counties}"},"budget_range":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__BudgetRange"},{"type":"null"}]},"exact_budget":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Exact Budget"},"campaign_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__CampaignType"},{"type":"null"}]},"brief_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Notes"},"brief_attachment_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Attachment Url"},"proposal_source":{"$ref":"#/components/schemas/cbradio-qa__ProposalSource","default":"direct"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"line_items":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbradio-qa__LineItemCreate"},"type":"array"},{"type":"null"}],"title":"Line Items"}},"type":"object","required":["name","start_date","end_date"],"title":"ProposalCreate","description":"Fields for creating a new proposal."},"cbradio-qa__ProposalFinancialSummary":{"properties":{"proposal_id":{"type":"string","title":"Proposal Id"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Net"},"total_spots":{"type":"integer","title":"Total Spots"},"commission_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission Amount"},"line_item_count":{"type":"integer","title":"Line Item Count"},"stations":{"items":{"type":"string"},"type":"array","title":"Stations"}},"type":"object","required":["proposal_id","total_gross","total_net","total_spots","commission_amount","line_item_count","stations"],"title":"ProposalFinancialSummary","description":"Financial summary of a proposal."},"cbradio-qa__ProposalSource":{"type":"string","enum":["brief","direct"],"title":"ProposalSource","description":"How the proposal originated."},"cbradio-qa__ProposalStatus":{"type":"string","enum":["draft","pending_review","sent","accepted","rejected","expired","converted"],"title":"ProposalStatus","description":"Status of a proposal in the workflow."},"cbradio-qa__ProposalUpdate":{"properties":{"name":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"consultant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consultant Id"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__src__api__models__proposal__AdType"},{"type":"null"}]},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date"},"spot_duration":{"anyOf":[{"type":"integer","maximum":120.0,"minimum":15.0},{"type":"null"}],"title":"Spot Duration"},"preemption":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__PreemptionRequest"},{"type":"null"}]},"time_slot_preference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Slot Preference"},"target_states":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target States"},"target_dmas":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Dmas"},"rural_only":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rural Only"},"station_pool":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__StationPool"},{"type":"null"}]},"commission_rate":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Commission Rate"},"target_geography":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Target Geography"},"budget_range":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__BudgetRange"},{"type":"null"}]},"exact_budget":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Exact Budget"},"campaign_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__CampaignType"},{"type":"null"}]},"brief_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Notes"},"brief_attachment_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Attachment Url"},"proposal_source":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ProposalSource"},{"type":"null"}]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"ProposalUpdate","description":"Fields for updating a proposal (all optional)."},"cbradio-qa__ProposalWithLineItems":{"properties":{"name":{"type":"string","minLength":1,"title":"Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"consultant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consultant Id"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"},"ad_type":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__proposal__AdType","default":"candidate"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date"},"spot_duration":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Duration","default":60},"preemption":{"$ref":"#/components/schemas/cbradio-qa__PreemptionRequest","default":"non_preemptible"},"time_slot_preference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Slot Preference"},"target_states":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target States"},"target_dmas":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Dmas"},"rural_only":{"type":"boolean","title":"Rural Only","default":false},"station_pool":{"$ref":"#/components/schemas/cbradio-qa__StationPool","description":"Station pool: 'all' = full FCC universe, 'network_only' = In-Network + Salem only.","default":"all"},"commission_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission Rate","default":"0.15"},"target_geography":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Target Geography","description":"Structured geography: {states, districts, DMAs, counties}"},"budget_range":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__BudgetRange"},{"type":"null"}]},"exact_budget":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Exact Budget"},"campaign_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__CampaignType"},{"type":"null"}]},"brief_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Notes"},"brief_attachment_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Attachment Url"},"proposal_source":{"$ref":"#/components/schemas/cbradio-qa__ProposalSource","default":"direct"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id"},"proposal_number":{"type":"string","title":"Proposal Number"},"status":{"$ref":"#/components/schemas/cbradio-qa__ProposalStatus","default":"draft"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accepted At"},"rejected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Rejected At"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"},"converted_to_buy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Converted To Buy Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"line_items":{"items":{"$ref":"#/components/schemas/cbradio-qa__LineItem"},"type":"array","title":"Line Items","default":[]},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross","default":"0"},"total_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Net","default":"0"},"total_spots":{"type":"integer","title":"Total Spots","default":0}},"type":"object","required":["name","start_date","end_date","id","proposal_number","created_at","updated_at"],"title":"ProposalWithLineItems","description":"Proposal with embedded line items."},"cbradio-qa__PublicConfirmRequest":{"properties":{"confirmed_by_name":{"type":"string","maxLength":255,"minLength":1,"title":"Confirmed By Name"},"confirmed_by_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Confirmed By Email"}},"type":"object","required":["confirmed_by_name"],"title":"PublicConfirmRequest","description":"Request model for public order confirmation."},"cbradio-qa__PublicConfirmResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"confirmed_at":{"type":"string","format":"date-time","title":"Confirmed At"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","confirmed_at","message"],"title":"PublicConfirmResponse","description":"Response after public confirmation."},"cbradio-qa__PublicLineItem":{"properties":{"station_callsign":{"type":"string","title":"Station Callsign"},"station_format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Format"},"dma":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dma"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"weeks":{"type":"integer","title":"Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"gross_rate_per_spot":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate Per Spot"}},"type":"object","required":["station_callsign","spots_per_week","weeks","total_spots"],"title":"PublicLineItem","description":"Public-safe line item data."},"cbradio-qa__PublicMakeGoodResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"make_good_request_id":{"type":"string","title":"Make Good Request Id"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","make_good_request_id","message"],"title":"PublicMakeGoodResponse","description":"Response after submitting make-good via public endpoint."},"cbradio-qa__PublicNetworkLineItem":{"properties":{"network_name":{"type":"string","title":"Network Name"},"network_state":{"type":"string","title":"Network State"},"rate_type":{"type":"string","title":"Rate Type"},"spot_length":{"type":"string","title":"Spot Length"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"num_weeks":{"type":"integer","title":"Num Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"rate_per_spot":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Rate Per Spot"},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount"}},"type":"object","required":["network_name","network_state","rate_type","spot_length","spots_per_week","num_weeks","total_spots","rate_per_spot","gross_amount"],"title":"PublicNetworkLineItem","description":"Slim network line item for public confirmation page (#201 Phase 3)."},"cbradio-qa__PublicOrderResponse":{"properties":{"estimate_number":{"type":"string","title":"Estimate Number"},"client_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Name"},"campaign_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Name"},"station_group_name":{"type":"string","title":"Station Group Name"},"run_dates":{"additionalProperties":true,"type":"object","title":"Run Dates"},"ad_length_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ad Length Seconds"},"status":{"$ref":"#/components/schemas/cbradio-qa__OrderPacketStatus"},"confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmed At"},"total_spots":{"type":"integer","title":"Total Spots"},"station_payment":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Payment"},"stations":{"items":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__order_packet__StationSummary"},"type":"array","title":"Stations","default":[]},"network_line_items":{"items":{"$ref":"#/components/schemas/cbradio-qa__PublicNetworkLineItem"},"type":"array","title":"Network Line Items","default":[]},"payee_type":{"type":"string","title":"Payee Type","default":"station_rep"},"files":{"additionalProperties":true,"type":"object","title":"Files"},"can_confirm":{"type":"boolean","title":"Can Confirm"},"can_request_make_good":{"type":"boolean","title":"Can Request Make Good"}},"type":"object","required":["estimate_number","station_group_name","run_dates","status","total_spots","station_payment","files","can_confirm","can_request_make_good"],"title":"PublicOrderResponse","description":"Public view of order for confirmation.\n\nFor station packets (`payee_type='station_rep'`), `stations` carries the\nper-callsign list; `network_line_items` is empty.\n\nFor network-aggregator packets (`payee_type='network_aggregator'`, #201\nPhase 3), `network_line_items` carries the per-network detail and\n`stations` is empty."},"cbradio-qa__PublicProposalResponse":{"properties":{"proposal_number":{"type":"string","title":"Proposal Number"},"name":{"type":"string","title":"Name"},"client_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Name"},"status":{"type":"string","title":"Status"},"ad_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ad Type"},"ad_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ad Length"},"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"election_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Date"},"target_states":{"items":{"type":"string"},"type":"array","title":"Target States","default":[]},"target_dmas":{"items":{"type":"string"},"type":"array","title":"Target Dmas","default":[]},"line_items":{"items":{"$ref":"#/components/schemas/cbradio-qa__PublicLineItem"},"type":"array","title":"Line Items","default":[]},"total_spots":{"type":"integer","title":"Total Spots","default":0},"total_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Total Gross"},"station_count":{"type":"integer","title":"Station Count","default":0},"share_expires_at":{"type":"string","format":"date-time","title":"Share Expires At"},"client_approval_status":{"$ref":"#/components/schemas/cbradio-qa__ClientApprovalStatus","default":"none"},"client_approved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Client Approved At"},"can_approve":{"type":"boolean","title":"Can Approve","default":true}},"type":"object","required":["proposal_number","name","status","share_expires_at"],"title":"PublicProposalResponse","description":"Response model for public proposal view."},"cbradio-qa__RadioBuy":{"properties":{"name":{"type":"string","title":"Name","description":"Campaign/buy name"},"client_id":{"type":"string","title":"Client Id","description":"Reference to client"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id","description":"Reference to buyer agency"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AdTypeEnum"},{"type":"null"}],"description":"candidate or issue"},"election_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ElectionType"},{"type":"null"}],"description":"primary or general"},"proposal_class":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ProposalClassEnum"},{"type":"null"}],"description":"Political-window classification: candidate_in_window / candidate_out_of_window / issue. NULL passes the window_mismatch gate (legacy backwards compat); set explicitly to enforce."},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Campaign start date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Campaign end date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date","description":"Election date"},"project_lead":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Lead","description":"Project lead name"},"project_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Team","description":"Comma-separated team member names"},"is_complete":{"type":"boolean","title":"Is Complete","description":"Whether the buy is complete","default":false},"gross_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount","description":"Total gross amount"},"net_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount","description":"Total net amount"},"rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Gross","description":"Rural gross amount"},"rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Net","description":"Rural net amount"},"non_rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Gross","description":"Non-rural gross amount"},"non_rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Net","description":"Non-rural net amount"},"buyer_agency_commission":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Commission","description":"Agency commission"},"payments_to_stations":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Payments To Stations","description":"Total station payments"},"buyer_agency_rebate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Rebate","description":"Agency rebate"},"consultant_rebate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Consultant Rebate","description":"Consultant rebate"},"profit":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Profit","description":"Profit (can be negative)"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name","client_id","id","created_at","updated_at"],"title":"RadioBuy","description":"Complete radio buy model with database fields."},"cbradio-qa__RadioBuyCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Campaign/buy name"},"client_id":{"type":"string","title":"Client Id","description":"Reference to client"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id","description":"Reference to buyer agency"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AdTypeEnum"},{"type":"null"}],"description":"candidate or issue"},"election_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ElectionType"},{"type":"null"}],"description":"primary or general"},"proposal_class":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ProposalClassEnum"},{"type":"null"}],"description":"Political-window classification: candidate_in_window / candidate_out_of_window / issue. NULL passes the window_mismatch gate (legacy backwards compat); set explicitly to enforce."},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Campaign start date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Campaign end date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date","description":"Election date"},"project_lead":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Lead","description":"Project lead name"},"project_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Team","description":"Comma-separated team member names"},"is_complete":{"type":"boolean","title":"Is Complete","description":"Whether the buy is complete","default":false},"gross_amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount","description":"Total gross amount"},"net_amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount","description":"Total net amount"},"rural_gross":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Gross","description":"Rural gross amount"},"rural_net":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Net","description":"Rural net amount"},"non_rural_gross":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Gross","description":"Non-rural gross amount"},"non_rural_net":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Net","description":"Non-rural net amount"},"buyer_agency_commission":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Commission","description":"Agency commission"},"payments_to_stations":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Payments To Stations","description":"Total station payments"},"buyer_agency_rebate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Rebate","description":"Agency rebate"},"consultant_rebate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Consultant Rebate","description":"Consultant rebate"},"profit":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Profit","description":"Profit (can be negative)"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"consultant_ids":{"items":{"type":"string"},"type":"array","title":"Consultant Ids","description":"List of consultant IDs"}},"type":"object","required":["name","client_id"],"title":"RadioBuyCreate","description":"Model for creating a new radio buy."},"cbradio-qa__RadioBuyFinancialSummary":{"properties":{"total_buys":{"type":"integer","title":"Total Buys"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Net"},"total_rural_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Rural Gross"},"total_rural_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Rural Net"},"total_non_rural_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Non Rural Gross"},"total_non_rural_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Non Rural Net"},"total_profit":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Profit"},"completed_buys":{"type":"integer","title":"Completed Buys"},"active_buys":{"type":"integer","title":"Active Buys"}},"type":"object","required":["total_buys","total_gross","total_net","total_rural_gross","total_rural_net","total_non_rural_gross","total_non_rural_net","total_profit","completed_buys","active_buys"],"title":"RadioBuyFinancialSummary","description":"Aggregated financial summary for radio buys."},"cbradio-qa__RadioBuyPlacement":{"properties":{"id":{"type":"string","title":"Id"},"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"daypart":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Daypart"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"rate":{"type":"number","title":"Rate"},"weeks":{"items":{"type":"integer"},"type":"array","title":"Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"total_cost":{"type":"number","title":"Total Cost"},"source_rate_card_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Rate Card Id"},"simulcast_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulcast Group Id"},"simulcast_callsigns":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Simulcast Callsigns"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["id","radio_buy_id","station_callsign","spots_per_week","rate","total_spots","total_cost"],"title":"RadioBuyPlacement","description":"Radio buy placement (station-level line item)."},"cbradio-qa__RadioBuySummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AdTypeEnum"},{"type":"null"}]},"election_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ElectionType"},{"type":"null"}]},"proposal_class":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ProposalClassEnum"},{"type":"null"}]},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"is_complete":{"type":"boolean","title":"Is Complete","default":false},"gross_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount"},"net_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount"},"rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Gross"},"rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Net"},"non_rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Gross"},"non_rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Net"},"buyer_agency_commission":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Commission"},"payments_to_stations":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Payments To Stations"},"consultant_rebate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Consultant Rebate"},"profit":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Profit"},"is_paid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Paid"},"paid_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid Date"},"amount_received":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Amount Received"},"cycle_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cycle Id"},"total_commissions":{"type":"number","title":"Total Commissions","default":0},"held_amount":{"type":"number","title":"Held Amount","default":0},"commission_rules":{"items":{"$ref":"#/components/schemas/cbradio-qa__CommissionRuleSummary"},"type":"array","title":"Commission Rules","default":[]}},"type":"object","required":["id","name"],"title":"RadioBuySummary","description":"Radio buy summary for list views — includes financial fields."},"cbradio-qa__RadioBuyUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AdTypeEnum"},{"type":"null"}]},"election_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ElectionType"},{"type":"null"}]},"proposal_class":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ProposalClassEnum"},{"type":"null"}]},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date"},"project_lead":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Lead"},"project_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Team"},"is_complete":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Complete"},"gross_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount"},"net_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount"},"rural_gross":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Gross"},"rural_net":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Net"},"non_rural_gross":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Gross"},"non_rural_net":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Net"},"buyer_agency_commission":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Commission"},"payments_to_stations":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Payments To Stations"},"buyer_agency_rebate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Rebate"},"consultant_rebate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Consultant Rebate"},"profit":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Profit"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"consultant_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Consultant Ids"}},"type":"object","title":"RadioBuyUpdate","description":"Model for updating a radio buy (all fields optional)."},"cbradio-qa__RadioBuyVersion":{"properties":{"change_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Summary"},"id":{"type":"string","title":"Id"},"buy_id":{"type":"string","title":"Buy Id"},"version_number":{"type":"integer","title":"Version Number"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross","default":"0"},"total_station_payments":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Station Payments","default":"0"},"total_commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Commission","default":"0"},"total_spots":{"type":"integer","title":"Total Spots","default":0},"station_count":{"type":"integer","title":"Station Count","default":0},"has_provisional":{"type":"boolean","title":"Has Provisional","default":false}},"type":"object","required":["id","buy_id","version_number","created_at"],"title":"RadioBuyVersion","description":"Radio buy version summary."},"cbradio-qa__RadioBuyVersionCreate":{"properties":{"change_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Summary"}},"type":"object","title":"RadioBuyVersionCreate","description":"Fields for creating a new version."},"cbradio-qa__RadioBuyVersionDetail":{"properties":{"change_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Summary"},"id":{"type":"string","title":"Id"},"buy_id":{"type":"string","title":"Buy Id"},"version_number":{"type":"integer","title":"Version Number"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross","default":"0"},"total_station_payments":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Station Payments","default":"0"},"total_commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Commission","default":"0"},"total_spots":{"type":"integer","title":"Total Spots","default":0},"station_count":{"type":"integer","title":"Station Count","default":0},"has_provisional":{"type":"boolean","title":"Has Provisional","default":false},"commission_breakdown":{"$ref":"#/components/schemas/cbradio-qa__CommissionBreakdown"},"payee_groups":{"items":{"$ref":"#/components/schemas/cbradio-qa__PayeeGroupWithStatus"},"type":"array","title":"Payee Groups"}},"type":"object","required":["id","buy_id","version_number","created_at"],"title":"RadioBuyVersionDetail","description":"Full radio buy version with payee groups and commission breakdown."},"cbradio-qa__RadioBuyWithRelations":{"properties":{"name":{"type":"string","title":"Name","description":"Campaign/buy name"},"client_id":{"type":"string","title":"Client Id","description":"Reference to client"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id","description":"Reference to buyer agency"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__AdTypeEnum"},{"type":"null"}],"description":"candidate or issue"},"election_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ElectionType"},{"type":"null"}],"description":"primary or general"},"proposal_class":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ProposalClassEnum"},{"type":"null"}],"description":"Political-window classification: candidate_in_window / candidate_out_of_window / issue. NULL passes the window_mismatch gate (legacy backwards compat); set explicitly to enforce."},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Campaign start date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Campaign end date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date","description":"Election date"},"project_lead":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Lead","description":"Project lead name"},"project_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Team","description":"Comma-separated team member names"},"is_complete":{"type":"boolean","title":"Is Complete","description":"Whether the buy is complete","default":false},"gross_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount","description":"Total gross amount"},"net_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount","description":"Total net amount"},"rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Gross","description":"Rural gross amount"},"rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Net","description":"Rural net amount"},"non_rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Gross","description":"Non-rural gross amount"},"non_rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Net","description":"Non-rural net amount"},"buyer_agency_commission":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Commission","description":"Agency commission"},"payments_to_stations":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Payments To Stations","description":"Total station payments"},"buyer_agency_rebate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Rebate","description":"Agency rebate"},"consultant_rebate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Consultant Rebate","description":"Consultant rebate"},"profit":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Profit","description":"Profit (can be negative)"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"client":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ClientSummary"},{"type":"null"}]},"buyer_agency":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__BuyerAgencySummary"},{"type":"null"}]},"consultants":{"items":{"$ref":"#/components/schemas/cbradio-qa__ConsultantSummary"},"type":"array","title":"Consultants","default":[]}},"type":"object","required":["name","client_id","id","created_at","updated_at"],"title":"RadioBuyWithRelations","description":"Radio buy with full related entities."},"cbradio-qa__Rate":{"properties":{"station_id":{"type":"string","title":"Station Id","description":"Reference to station"},"effective_date":{"type":"string","format":"date","title":"Effective Date","description":"When this rate becomes effective"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date","description":"When this rate expires"},"ad_type":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__rate__AdType","description":"Candidate vs Issue advertising"},"rate_context":{"$ref":"#/components/schemas/cbradio-qa__RateContext","description":"In-window vs out-of-window","default":"all"},"preemption":{"$ref":"#/components/schemas/cbradio-qa__PreemptionStatus","description":"Preemption status","default":"non_preemptible"},"slot_days":{"$ref":"#/components/schemas/cbradio-qa__DayPattern","description":"Days pattern","default":"all"},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time","description":"Time slot start"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time","description":"Time slot end"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name","description":"Named time slot"},"duration_seconds":{"$ref":"#/components/schemas/cbradio-qa__SpotDuration","description":"Spot duration"},"gross_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate","description":"Gross rate (before commission)"},"net_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate","description":"Net rate (after commission)"},"rate_card_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Card Year","description":"Year of rate card"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id","description":"Source document reference"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_lur":{"type":"boolean","title":"Is Lur","description":"Is this a Lowest Unit Rate","default":false},"id":{"type":"string","title":"Id","description":"Unique identifier"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"station_callsign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Callsign"},"station_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station State"},"rate_card_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Card Id","description":"Associated rate card ID"},"rate_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Source","description":"Origin of rate data"},"rate_card_review_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Card Review Status","description":"QA verification status"},"rate_last_verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Rate Last Verified At","description":"When rate was last verified"},"rate_last_verified_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Last Verified By","description":"User who verified the rate"}},"type":"object","required":["station_id","effective_date","ad_type","duration_seconds","id"],"title":"Rate","description":"Complete rate model with database fields."},"cbradio-qa__RateCardBackfillRequest":{"properties":{"station_id":{"type":"string","title":"Station Id"},"callsign":{"type":"string","title":"Callsign"},"source":{"type":"string","title":"Source","default":"airtable_2026"},"year":{"type":"integer","title":"Year","default":2026}},"type":"object","required":["station_id","callsign"],"title":"RateCardBackfillRequest","description":"Request to create a rate_card and link orphaned rates."},"cbradio-qa__RateCardBackfillResponse":{"properties":{"rate_card_id":{"type":"string","title":"Rate Card Id"},"created":{"type":"boolean","title":"Created"},"rates_linked":{"type":"integer","title":"Rates Linked"}},"type":"object","required":["rate_card_id","created","rates_linked"],"title":"RateCardBackfillResponse","description":"Response for rate card backfill."},"cbradio-qa__RateCardCreateWithRates":{"properties":{"station_id":{"type":"string","title":"Station Id"},"year":{"type":"integer","title":"Year"},"rate_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Type","description":"e.g., 'Political', 'Gross', 'Net'"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"source":{"type":"string","title":"Source","description":"Ingestion source (manual, katz, iheart, airtable_2026, amfm)","default":"manual"},"rates":{"items":{"$ref":"#/components/schemas/cbradio-qa__RateCreateForCard"},"type":"array","title":"Rates"}},"type":"object","required":["station_id","year"],"title":"RateCardCreateWithRates","description":"Create rate card with nested rates in one operation."},"cbradio-qa__RateCardUpdate":{"properties":{"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"rate_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Type"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"RateCardUpdate","description":"Model for partial rate card updates."},"cbradio-qa__RateCardWithRates":{"properties":{"station_id":{"type":"string","title":"Station Id"},"year":{"type":"integer","title":"Year"},"rate_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Type","description":"e.g., 'Political', 'Gross', 'Net'"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"source":{"type":"string","title":"Source","description":"Ingestion source (manual, katz, iheart, airtable_2026, amfm)","default":"manual"},"id":{"type":"string","title":"Id"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"rate_count":{"type":"integer","title":"Rate Count","default":0},"rates":{"items":{"$ref":"#/components/schemas/cbradio-qa__Rate"},"type":"array","title":"Rates"},"station_callsign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Callsign"}},"type":"object","required":["station_id","year","id"],"title":"RateCardWithRates","description":"Rate card with embedded rates for read operations."},"cbradio-qa__RateContext":{"type":"string","enum":["in_window","out_of_window","all"],"title":"RateContext","description":"Context/window for the rate."},"cbradio-qa__RateCorrection":{"properties":{"station_callsign":{"type":"string","title":"Station Callsign","description":"Station callsign"},"facility_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Facility Id","description":"FCC facility ID for disambiguation"},"daypart":{"type":"string","title":"Daypart","description":"Daypart being corrected (e.g., 'Morning Drive')"},"spot_length_seconds":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Length Seconds","description":"Spot duration"},"original_gross_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Original Gross Rate","description":"Rate before correction"},"corrected_gross_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Corrected Gross Rate","description":"New corrected rate"},"effective_start_date":{"type":"string","format":"date","title":"Effective Start Date","description":"When correction takes effect"},"effective_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective End Date","description":"When correction expires"},"reason":{"type":"string","minLength":1,"title":"Reason","description":"Why the correction is needed"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Additional context"},"status":{"$ref":"#/components/schemas/cbradio-qa__CorrectionStatus","description":"Correction status","default":"pending_review"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"created_by":{"type":"string","title":"Created By","description":"User who created the correction"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When correction was created"},"verified_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verified By","description":"Admin who verified"},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"When verified"},"verification_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verification Notes","description":"Verification notes"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["station_callsign","daypart","spot_length_seconds","original_gross_rate","corrected_gross_rate","effective_start_date","reason","id","created_by","created_at"],"title":"RateCorrection","description":"Complete rate correction with database fields."},"cbradio-qa__RateCorrectionCreate":{"properties":{"station_callsign":{"type":"string","title":"Station Callsign","description":"Station callsign"},"facility_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Facility Id","description":"FCC facility ID for disambiguation"},"daypart":{"type":"string","title":"Daypart","description":"Daypart being corrected (e.g., 'Morning Drive')"},"spot_length_seconds":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Length Seconds","description":"Spot duration"},"original_gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Original Gross Rate","description":"Rate before correction"},"corrected_gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Corrected Gross Rate","description":"New corrected rate"},"effective_start_date":{"type":"string","format":"date","title":"Effective Start Date","description":"When correction takes effect"},"effective_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective End Date","description":"When correction expires"},"reason":{"type":"string","minLength":1,"title":"Reason","description":"Why the correction is needed"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Additional context"},"status":{"$ref":"#/components/schemas/cbradio-qa__CorrectionStatus","description":"Correction status","default":"pending_review"}},"type":"object","required":["station_callsign","daypart","spot_length_seconds","original_gross_rate","corrected_gross_rate","effective_start_date","reason"],"title":"RateCorrectionCreate","description":"Model for creating a rate correction."},"cbradio-qa__RateCorrectionVerify":{"properties":{"verified":{"type":"boolean","title":"Verified","description":"True to verify, False to reject"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Verification notes"}},"type":"object","required":["verified"],"title":"RateCorrectionVerify","description":"Request to verify or reject a rate correction."},"cbradio-qa__RateCreate":{"properties":{"station_id":{"type":"string","title":"Station Id","description":"Reference to station"},"effective_date":{"type":"string","format":"date","title":"Effective Date","description":"When this rate becomes effective"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date","description":"When this rate expires"},"ad_type":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__rate__AdType","description":"Candidate vs Issue advertising"},"rate_context":{"$ref":"#/components/schemas/cbradio-qa__RateContext","description":"In-window vs out-of-window","default":"all"},"preemption":{"$ref":"#/components/schemas/cbradio-qa__PreemptionStatus","description":"Preemption status","default":"non_preemptible"},"slot_days":{"$ref":"#/components/schemas/cbradio-qa__DayPattern","description":"Days pattern","default":"all"},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time","description":"Time slot start"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time","description":"Time slot end"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name","description":"Named time slot"},"duration_seconds":{"$ref":"#/components/schemas/cbradio-qa__SpotDuration","description":"Spot duration"},"gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate","description":"Gross rate (before commission)"},"net_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate","description":"Net rate (after commission)"},"rate_card_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Card Year","description":"Year of rate card"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id","description":"Source document reference"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_lur":{"type":"boolean","title":"Is Lur","description":"Is this a Lowest Unit Rate","default":false}},"type":"object","required":["station_id","effective_date","ad_type","duration_seconds"],"title":"RateCreate","description":"Model for creating a new rate."},"cbradio-qa__RateCreateForCard":{"properties":{"effective_date":{"type":"string","format":"date","title":"Effective Date","description":"When this rate becomes effective"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"ad_type":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__rate__AdType","description":"Candidate vs Issue advertising"},"rate_context":{"$ref":"#/components/schemas/cbradio-qa__RateContext","description":"In-window vs out-of-window","default":"all"},"preemption":{"$ref":"#/components/schemas/cbradio-qa__PreemptionStatus","description":"Preemption status","default":"non_preemptible"},"slot_days":{"$ref":"#/components/schemas/cbradio-qa__DayPattern","description":"Days pattern","default":"all"},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time","description":"Time slot start"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time","description":"Time slot end"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name","description":"Named time slot"},"duration_seconds":{"$ref":"#/components/schemas/cbradio-qa__SpotDuration","description":"Spot duration in seconds"},"gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate","description":"Gross rate (before commission)"},"net_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate","description":"Net rate (after commission)"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id","description":"Source document reference"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_lur":{"type":"boolean","title":"Is Lur","description":"Is this a Lowest Unit Rate","default":false}},"type":"object","required":["effective_date","ad_type","duration_seconds"],"title":"RateCreateForCard","description":"Rate creation model when nested inside a rate card.\n\nOmits station_id and rate_card_id (inferred from parent)."},"cbradio-qa__RateData":{"properties":{"ad_type":{"type":"string","title":"Ad Type"},"rate_context":{"type":"string","title":"Rate Context","default":"all"},"preemption":{"type":"string","title":"Preemption"},"slot_days":{"type":"string","title":"Slot Days"},"slot_start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Start Time"},"slot_end_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot End Time"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name"},"duration_seconds":{"type":"integer","title":"Duration Seconds"},"gross_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gross Rate"},"net_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Net Rate"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["ad_type","preemption","slot_days","duration_seconds"],"title":"RateData","description":"A single extracted rate."},"cbradio-qa__RateUpdate":{"properties":{"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__src__api__models__rate__AdType"},{"type":"null"}]},"rate_context":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__RateContext"},{"type":"null"}]},"preemption":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__PreemptionStatus"},{"type":"null"}]},"slot_days":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__DayPattern"},{"type":"null"}]},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name"},"duration_seconds":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__SpotDuration"},{"type":"null"}]},"gross_rate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate"},"net_rate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate"},"rate_card_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Card Year"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_lur":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Lur"}},"type":"object","title":"RateUpdate","description":"Model for updating a rate (all fields optional)."},"cbradio-qa__RateUpdateWithId":{"properties":{"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__src__api__models__rate__AdType"},{"type":"null"}]},"rate_context":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__RateContext"},{"type":"null"}]},"preemption":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__PreemptionStatus"},{"type":"null"}]},"slot_days":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__DayPattern"},{"type":"null"}]},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name"},"duration_seconds":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__SpotDuration"},{"type":"null"}]},"gross_rate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate"},"net_rate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate"},"rate_card_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Card Year"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_lur":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Lur"},"id":{"type":"string","title":"Id","description":"Rate ID to update"}},"type":"object","required":["id"],"title":"RateUpdateWithId","description":"Rate update with explicit ID for bulk operations."},"cbradio-qa__RateWriteItem":{"properties":{"daypart_id":{"type":"string","title":"Daypart Id","description":"One of 13 standard daypart IDs"},"spot_length":{"type":"integer","title":"Spot Length","description":"Spot duration: 30 or 60 seconds"},"gross_rate":{"type":"number","exclusiveMinimum":0.0,"title":"Gross Rate","description":"Gross rate in dollars"},"source":{"type":"string","title":"Source","description":"Rate provenance: extracted, corrected, manual","default":"manual"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["daypart_id","spot_length","gross_rate"],"title":"RateWriteItem","description":"A single rate to write (used by both #53 and #54)."},"cbradio-qa__RecipientType":{"type":"string","enum":["salesman","agency","consultant","station","other"],"title":"RecipientType","description":"Commission recipient role."},"cbradio-qa__ReextractGapsResponse":{"properties":{"total":{"type":"integer","title":"Total","description":"Gap documents attempted in this call"},"succeeded":{"type":"integer","title":"Succeeded","description":"Successfully reprocessed"},"failed":{"type":"integer","title":"Failed","description":"Failed to reprocess"},"remaining":{"type":"integer","title":"Remaining","description":"Gap documents still waiting after this call. Each one is a synchronous LLM round trip, so a run is bounded and an operator works the queue down across several calls.","default":0},"errors":{"items":{"type":"string"},"type":"array","title":"Errors","description":"Error messages"}},"type":"object","required":["total","succeeded","failed"],"title":"ReextractGapsResponse","description":"Response from reprocessing gap documents."},"cbradio-qa__RejectProposal":{"properties":{"reason":{"type":"string","minLength":1,"title":"Reason"}},"type":"object","required":["reason"],"title":"RejectProposal","description":"Request to mark proposal as rejected."},"cbradio-qa__RejectRequest":{"properties":{"reason":{"type":"string","minLength":1,"title":"Reason","description":"Reason for rejection"}},"type":"object","required":["reason"],"title":"RejectRequest","description":"Request model for rejecting a staged record."},"cbradio-qa__ReorderItem":{"properties":{"id":{"type":"string","title":"Id"},"rotation_order":{"type":"integer","minimum":1.0,"title":"Rotation Order"}},"type":"object","required":["id","rotation_order"],"title":"ReorderItem","description":"Single item in reorder request."},"cbradio-qa__ReorderRequest":{"properties":{"order":{"items":{"$ref":"#/components/schemas/cbradio-qa__ReorderItem"},"type":"array","title":"Order"}},"type":"object","required":["order"],"title":"ReorderRequest","description":"Request model for reordering audio spots."},"cbradio-qa__ReviewApprovalBody":{"properties":{"corrections":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Corrections"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"approved_rates":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbradio-qa__ApprovedRateGrid"},"type":"array"},{"type":"null"}],"title":"Approved Rates"}},"type":"object","title":"ReviewApprovalBody","description":"Request body for POST /rate-cards/{id}/review when decision=approved."},"cbradio-qa__SRMAlert":{"properties":{"id":{"type":"string","title":"Id","description":"Unique identifier"},"type":{"$ref":"#/components/schemas/cbradio-qa__AlertType","description":"Alert type"},"station_callsign":{"type":"string","title":"Station Callsign","description":"Related station"},"title":{"type":"string","title":"Title","description":"Alert title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Alert details"},"dismissed":{"type":"boolean","title":"Dismissed","description":"Whether dismissed","default":false},"dismissed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Dismissed At","description":"When dismissed"},"dismissed_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dismissed By User Id","description":"Who dismissed"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"}},"type":"object","required":["id","type","station_callsign","title","created_at"],"title":"SRMAlert","description":"System-generated alert."},"cbradio-qa__SRMTask":{"properties":{"id":{"type":"string","title":"Id","description":"Unique identifier"},"station_callsign":{"type":"string","title":"Station Callsign","description":"Station callsign"},"task_type":{"$ref":"#/components/schemas/cbradio-qa__TaskType","description":"Type of task"},"title":{"type":"string","title":"Title","description":"Task title"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why task was generated"},"priority":{"type":"integer","maximum":3.0,"minimum":0.0,"title":"Priority","description":"Priority (0=highest, 3=lowest)"},"status":{"$ref":"#/components/schemas/cbradio-qa__TaskStatus","description":"Task status"},"owner_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner User Id","description":"Assigned user ID"},"owner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Name","description":"Assigned user name"},"due_date":{"type":"string","format":"date","title":"Due Date","description":"Task due date"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"When completed"},"completed_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed By User Id","description":"Who completed"},"completion_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completion Notes","description":"Completion notes"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"},"station":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Station","description":"Station summary"}},"type":"object","required":["id","station_callsign","task_type","title","priority","status","due_date","created_at","updated_at"],"title":"SRMTask","description":"SRM task for sales rep outreach."},"cbradio-qa__ScoreRequest":{"properties":{"callsigns":{"items":{"type":"string"},"type":"array","maxItems":500,"minItems":1,"title":"Callsigns","description":"Station callsigns to score"},"audience":{"type":"string","title":"Audience","description":"Target audience: republican_primary, democratic_primary, swing_general","default":"republican_primary"},"states":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"States","description":"Target states for coverage scoring (e.g., ['TN', 'MI'])"}},"type":"object","required":["callsigns"],"title":"ScoreRequest","description":"Request to score stations for a target audience/geography."},"cbradio-qa__SearchCountsRequest":{"properties":{"state":{"type":"string","maxLength":2,"minLength":2,"title":"State","description":"Two-letter US state abbreviation. Case-insensitive.","examples":["NH","FL"]},"county":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"County","description":"Numeric i360 county ID. Omit for statewide totals. Get IDs from i360 county list.","examples":[306,307]},"affinity_min":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Affinity Min","description":"Minimum political affinity score. Must pair with affinity_max or API returns zeros.","examples":[40,65]},"affinity_max":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Affinity Max","description":"Maximum political affinity score. Must pair with affinity_min.","examples":[55,100]},"propensity_min":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Propensity Min","description":"Minimum voting propensity score (1=most likely). Must pair with propensity_max.","examples":[1]},"propensity_max":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Propensity Max","description":"Maximum voting propensity score. Must pair with propensity_min.","examples":[4,6]}},"type":"object","required":["state"],"title":"SearchCountsRequest","description":"Request parameters for a single voter universe count query.\n\nReturns aggregate counts for a state or state+county combination,\noptionally filtered by political affinity and voting propensity ranges.\nMuch faster than county-report (single API call vs N*3 calls).","example":{"affinity_max":55,"affinity_min":40,"county":306,"propensity_max":4,"propensity_min":1,"state":"FL"}},"cbradio-qa__SearchCountsResponse":{"properties":{"state":{"type":"string","title":"State","description":"State that was queried.","examples":["FL"]},"county":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"County","description":"County ID if a county filter was applied, null for statewide.","examples":[306,null]},"individuals":{"type":"integer","title":"Individuals","description":"Total individuals matching query. This is the primary 'universe size' number.","examples":[4048,1309995]},"residences":{"type":"integer","title":"Residences","description":"Unique residential addresses. Always <= individuals. Use for household-level targeting.","examples":[3855,564277]},"apartments":{"type":"integer","title":"Apartments","description":"Subset of residences that are apartment units.","examples":[1447,200569]},"mailing_addresses":{"type":"integer","title":"Mailing Addresses","description":"Deliverable postal addresses. Use for direct mail piece counts.","examples":[3867,578094]},"landlines":{"type":"integer","title":"Landlines","description":"Available landline phone numbers. Use for phone banking estimates.","examples":[609,204792]},"mobiles":{"type":"integer","title":"Mobiles","description":"Available mobile phone numbers. Use for P2P texting estimates.","examples":[2187,628521]}},"type":"object","required":["state","individuals","residences","apartments","mailing_addresses","landlines","mobiles"],"title":"SearchCountsResponse","description":"Aggregate voter universe counts from i360."},"cbradio-qa__SearchResponse":{"properties":{"query":{"type":"string","title":"Query"},"total":{"type":"integer","title":"Total"},"issues":{"items":{"$ref":"#/components/schemas/cbradio-qa__Issue"},"type":"array","title":"Issues"}},"type":"object","required":["query","total","issues"],"title":"SearchResponse","description":"Response for cross-project search."},"cbradio-qa__SendOrderRequest":{"properties":{"sender_name":{"type":"string","maxLength":255,"minLength":1,"title":"Sender Name"},"sender_title":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Sender Title"},"additional_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Additional Message"}},"type":"object","required":["sender_name"],"title":"SendOrderRequest","description":"Request model for sending order packet email."},"cbradio-qa__SendOrderResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"sent_at":{"type":"string","format":"date-time","title":"Sent At"},"email_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Id"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"}},"type":"object","required":["success","sent_at"],"title":"SendOrderResponse","description":"Response after sending order."},"cbradio-qa__SendProposal":{"properties":{"expires_in_days":{"type":"integer","maximum":90.0,"minimum":1.0,"title":"Expires In Days","default":30},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"SendProposal","description":"Request to send a proposal to client."},"cbradio-qa__ShareLinkResponse":{"properties":{"share_url":{"type":"string","title":"Share Url","description":"The shareable short URL"},"slug":{"type":"string","title":"Slug","description":"The slug portion of the URL"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["share_url","slug","expires_at","created_at"],"title":"ShareLinkResponse","description":"Response model for share link creation."},"cbradio-qa__ShareLinkStatusResponse":{"properties":{"share_url":{"type":"string","title":"Share Url"},"slug":{"type":"string","title":"Slug"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"type":"string","title":"Created By"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"is_expired":{"type":"boolean","title":"Is Expired"},"is_revoked":{"type":"boolean","title":"Is Revoked"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"},"revoked_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked By"},"view_count":{"type":"integer","title":"View Count","default":0},"unique_view_count":{"type":"integer","title":"Unique View Count","default":0},"last_viewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Viewed At"},"cblinks_stats":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cblinks Stats","description":"Analytics from CBLinks service"}},"type":"object","required":["share_url","slug","created_at","created_by","expires_at","is_expired","is_revoked"],"title":"ShareLinkStatusResponse","description":"Response model for share link status."},"cbradio-qa__SimulcastGroupCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"primary_callsign":{"type":"string","maxLength":20,"minLength":1,"title":"Primary Callsign"},"callsigns":{"items":{"type":"string"},"type":"array","minItems":2,"title":"Callsigns"},"rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name","primary_callsign","callsigns"],"title":"SimulcastGroupCreate","description":"Request model for creating a simulcast group."},"cbradio-qa__SimulcastGroupResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"primary_callsign":{"type":"string","title":"Primary Callsign"},"callsigns":{"items":{"type":"string"},"type":"array","title":"Callsigns"},"rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","primary_callsign","callsigns","created_at","updated_at"],"title":"SimulcastGroupResponse","description":"Response model for a simulcast group."},"cbradio-qa__SimulcastGroupUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"primary_callsign":{"anyOf":[{"type":"string","maxLength":20,"minLength":1},{"type":"null"}],"title":"Primary Callsign"},"callsigns":{"anyOf":[{"items":{"type":"string"},"type":"array","minItems":2},{"type":"null"}],"title":"Callsigns"},"rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"SimulcastGroupUpdate","description":"Request model for updating a simulcast group."},"cbradio-qa__SpotBreakdownType":{"type":"string","enum":["weekly","daily","flight"],"title":"SpotBreakdownType","description":"How spots are broken down in the order."},"cbradio-qa__SpotDuration":{"type":"integer","enum":[15,30,60,90,120],"title":"SpotDuration","description":"Standard spot durations in seconds."},"cbradio-qa__StationContact":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"}},"type":"object","title":"StationContact","description":"Contact information for a station."},"cbradio-qa__StationCoverage":{"properties":{"callsign":{"type":"string","title":"Callsign","description":"Station callsign.","examples":["WITL-FM"]},"facility_id":{"type":"string","title":"Facility Id","description":"FCC facility ID.","examples":["46706"]},"coverage_pct":{"type":"number","title":"Coverage Pct","description":"Percentage of the district covered by this station (0-100).","examples":[100.0,85.5]},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format","description":"Station format.","examples":["Country"]},"nielsen_dma":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Dma","description":"Nielsen DMA market.","examples":["Lansing, MI"]},"is_in_network":{"type":"boolean","title":"Is In Network","description":"Whether the station is in-network.","default":false},"simulcast_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulcast With","description":"Simulcast partner callsign, if any.","examples":["WCAB-FM"]}},"type":"object","required":["callsign","facility_id","coverage_pct"],"title":"StationCoverage","description":"A station covering a given district."},"cbradio-qa__StationDelta":{"properties":{"station_callsign":{"type":"string","title":"Station Callsign"},"station_id":{"type":"string","title":"Station Id"},"change_type":{"type":"string","title":"Change Type"},"old_values":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Old Values"},"new_values":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"New Values"}},"type":"object","required":["station_callsign","station_id","change_type"],"title":"StationDelta","description":"Change summary for a single station."},"cbradio-qa__StationFormat":{"type":"string","enum":["talk","news_talk","classic_hits","classic_rock","country","hot_ac","adult_contemporary","regional_mexican","southern_gospel","sports","religious","other"],"title":"StationFormat","description":"Radio station programming formats."},"cbradio-qa__StationPool":{"type":"string","enum":["all","network_only"],"title":"StationPool","description":"Station pool selection for proposals."},"cbradio-qa__StationRatingsResponse":{"properties":{"callsign":{"type":"string","title":"Callsign","description":"Station callsign (uppercased).","examples":["WLTW-FM"]},"ratings":{"items":{"$ref":"#/components/schemas/cbradio-qa__NielsenRating"},"type":"array","title":"Ratings","description":"All rating entries for this callsign, ordered by market_rank then most recent first."}},"type":"object","required":["callsign","ratings"],"title":"StationRatingsResponse","description":"All Nielsen ratings for a specific station across markets and survey periods."},"cbradio-qa__StationSRM":{"properties":{"callsign":{"type":"string","title":"Callsign","description":"Station callsign (FK to station)"},"lifecycle_state":{"$ref":"#/components/schemas/cbradio-qa__src__api__models__srm__LifecycleState","description":"Current lifecycle state","default":"prospect"},"lifecycle_state_changed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lifecycle State Changed At","description":"When lifecycle state last changed"},"last_contacted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Contacted At","description":"Most recent contact timestamp"},"last_contact_outcome":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ContactOutcome"},{"type":"null"}],"description":"Outcome of last contact"},"last_contact_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Contact By User Id","description":"User who made last contact"},"agreement_start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Agreement Start Date","description":"Current agreement start date"},"agreement_expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Agreement Expiration Date","description":"Current agreement expiration date"},"previous_owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous Owner","description":"Previous owner if changed"},"ownership_changed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ownership Changed At","description":"When ownership changed"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"General notes"},"outreach_priority_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Outreach Priority Score","description":"Outreach priority score (0-100), higher = more urgent"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["callsign","created_at","updated_at"],"title":"StationSRM","description":"SRM data for a station.","example":{"agreement_expiration_date":"2026-05-31","agreement_start_date":"2025-06-01","callsign":"WAAA-FM","last_contact_outcome":"meeting_scheduled","last_contacted_at":"2026-01-10T14:30:00Z","lifecycle_state":"active","notes":"Key account, quarterly check-ins"}},"cbradio-qa__StationSRMUpdate":{"properties":{"lifecycle_state":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__src__api__models__srm__LifecycleState"},{"type":"null"}],"description":"New lifecycle state"},"agreement_start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Agreement Start Date","description":"Agreement start date"},"agreement_expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Agreement Expiration Date","description":"Agreement expiration date"},"previous_owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous Owner","description":"Previous owner"},"ownership_changed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ownership Changed At","description":"When ownership changed"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"General notes"}},"type":"object","title":"StationSRMUpdate","description":"Request model for updating SRM data."},"cbradio-qa__StationStatus":{"type":"string","enum":["signed","pending","inactive","prospect","exclusive"],"title":"StationStatus","description":"Station network status."},"cbradio-qa__StationStatusUpdate":{"properties":{"status":{"type":"string","title":"Status","description":"New status for the station: signed, pending, inactive, prospect","examples":["inactive"]}},"type":"object","required":["status"],"title":"StationStatusUpdate","description":"Request model for updating station status."},"cbradio-qa__StationUpdate":{"properties":{"frequency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Frequency"},"format":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__StationFormat"},{"type":"null"}]},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"state_full":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State Full"},"nielsen_dma":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Dma"},"nielsen_msa":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Msa"},"mailing_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Address"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"},"ownership_group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ownership Group"},"ownership_group_override":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ownership Group Override"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"mailing_street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Street"},"mailing_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing City"},"mailing_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing State"},"mailing_zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Zip"},"primary_contact":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__StationContact"},{"type":"null"}]},"general_manager":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__StationContact"},{"type":"null"}]},"sales_manager":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__StationContact"},{"type":"null"}]},"program_director":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__StationContact"},{"type":"null"}]},"billing_manager":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__StationContact"},{"type":"null"}]},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__StationStatus"},{"type":"null"}]},"is_in_network":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is In Network"},"commission_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Commission Rate"},"status_tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Status Tags"},"status_tags_scope":{"anyOf":[{"type":"string","const":"airtable"},{"type":"null"}],"title":"Status Tags Scope"},"simulcast_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulcast With"},"multicast_stations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Multicast Stations"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"airtable_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Id"},"airtable_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Status"},"airtable_synced_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Synced At"}},"type":"object","title":"StationUpdate","description":"Model for updating a station (all fields optional)."},"cbradio-qa__StationVoterResponse":{"properties":{"callsign":{"type":"string","title":"Callsign","description":"Station callsign.","examples":["WITL-FM"]},"facility_id":{"type":"string","title":"Facility Id","description":"FCC facility ID.","examples":["46706"]},"simulcast_partners":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Simulcast Partners","description":"Simulcast partner callsigns whose coverage was unioned. Null if no partners.","examples":[["WCAB-FM"]]},"coverage_county_count":{"type":"integer","title":"Coverage County Count","description":"Number of counties in the station's coverage area.","examples":[14]},"counties_with_voter_data":{"type":"integer","title":"Counties With Voter Data","description":"Counties that have i360 voter data.","examples":[12]},"voter_counts":{"$ref":"#/components/schemas/cbradio-qa__VoterCounts"},"estimated_listeners":{"$ref":"#/components/schemas/cbradio-qa__EstimatedListeners"},"data_freshness":{"$ref":"#/components/schemas/cbradio-qa__DataFreshness","description":"Freshness window for the i360 voter data (cbradio#252)."},"coverage_counties":{"items":{"$ref":"#/components/schemas/cbradio-qa__CountyCoverage"},"type":"array","title":"Coverage Counties","description":"Per-county breakdown ordered by coverage percentage descending."}},"type":"object","required":["callsign","facility_id","coverage_county_count","counties_with_voter_data","voter_counts","estimated_listeners","coverage_counties"],"title":"StationVoterResponse","description":"Complete voter data for a station's coverage area."},"cbradio-qa__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbradio-qa__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbradio-qa__SuggestStationsRequest":{"properties":{"target_geography":{"additionalProperties":true,"type":"object","title":"Target Geography","description":"e.g. {states: ['TX'], districts: ['TX-SD3']}"},"budget":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Budget"},"campaign_type":{"$ref":"#/components/schemas/cbradio-qa__CampaignType","default":"candidate_in_window"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"station_pool":{"$ref":"#/components/schemas/cbradio-qa__StationPool","default":"all"},"spot_duration":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Duration","default":60},"spots_per_week":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Spots Per Week","default":10},"max_stations":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Max Stations","default":50}},"type":"object","required":["target_geography","budget","start_date","end_date"],"title":"SuggestStationsRequest","description":"Request for station suggestions (non-committing)."},"cbradio-qa__SuggestionAction":{"type":"string","enum":["accepted","rejected","modified","ignored"],"title":"SuggestionAction","description":"User action on an AI suggestion."},"cbradio-qa__SyncStatusResponse":{"properties":{"status":{"type":"string","title":"Status"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"},"pid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pid"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"last_run_summary":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Last Run Summary"},"log_tail":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Log Tail"}},"type":"object","required":["status"],"title":"SyncStatusResponse","description":"Current or last sync pipeline status."},"cbradio-qa__SyncTriggerRequest":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run","description":"Preview mode — no DB writes","default":false},"skip_extraction":{"type":"boolean","title":"Skip Extraction","description":"Skip OCR/extraction (download + document records only)","default":false},"limit":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Limit","description":"Max documents to process in extraction step","default":50}},"type":"object","title":"SyncTriggerRequest","description":"Request body for triggering the sync pipeline."},"cbradio-qa__SyncTriggerResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"log_file":{"type":"string","title":"Log File"}},"type":"object","required":["job_id","status","log_file"],"title":"SyncTriggerResponse","description":"Response from triggering the sync pipeline."},"cbradio-qa__TaskCompleteRequest":{"properties":{"outcome":{"$ref":"#/components/schemas/cbradio-qa__ContactOutcome","description":"Contact outcome"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Completion notes"},"contact_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__ContactType"},{"type":"null"}],"description":"Type of contact","default":"phone"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name","description":"Person contacted"}},"type":"object","required":["outcome"],"title":"TaskCompleteRequest","description":"Request model for completing a task."},"cbradio-qa__TaskList":{"properties":{"tasks":{"items":{"$ref":"#/components/schemas/cbradio-qa__SRMTask"},"type":"array","title":"Tasks"},"total":{"type":"integer","title":"Total"},"due_today":{"type":"integer","title":"Due Today","default":0},"overdue":{"type":"integer","title":"Overdue","default":0}},"type":"object","required":["tasks","total"],"title":"TaskList","description":"Paginated list of tasks with summary stats."},"cbradio-qa__TaskStatus":{"type":"string","enum":["pending","in_progress","completed","cancelled"],"title":"TaskStatus","description":"Task workflow states."},"cbradio-qa__TaskType":{"type":"string","enum":["initial_outreach","follow_up","relationship_maintenance","agreement_renewal","win_back","ownership_change"],"title":"TaskType","description":"Types of SRM tasks."},"cbradio-qa__TimeSlotAdjustment":{"properties":{"slot_days":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__DayPattern"},{"type":"null"}],"description":"Days pattern"},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time","description":"Time slot start"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time","description":"Time slot end"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name","description":"Named time slot"}},"type":"object","title":"TimeSlotAdjustment","description":"Adjust time slot fields only - for AI correction workflow."},"cbradio-qa__Token":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["access_token"],"title":"Token","description":"Token model for JWT authentication."},"cbradio-qa__TrafficAssetCreate":{"properties":{"asset_type":{"$ref":"#/components/schemas/cbradio-qa__AssetType"},"file_url":{"type":"string","maxLength":512,"title":"File Url"},"file_name":{"type":"string","maxLength":255,"title":"File Name"},"file_size":{"type":"integer","exclusiveMinimum":0.0,"title":"File Size"},"mime_type":{"type":"string","maxLength":100,"title":"Mime Type"},"label":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Label"},"duration_seconds":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Duration Seconds"},"rotation_order":{"type":"integer","minimum":1.0,"title":"Rotation Order","default":1},"rotation_weight":{"type":"integer","minimum":1.0,"title":"Rotation Weight","default":1}},"type":"object","required":["asset_type","file_url","file_name","file_size","mime_type"],"title":"TrafficAssetCreate","description":"Request model for creating a traffic asset."},"cbradio-qa__TrafficAssetResponse":{"properties":{"id":{"type":"string","title":"Id"},"radio_buy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"},"asset_type":{"$ref":"#/components/schemas/cbradio-qa__AssetType"},"file_url":{"type":"string","title":"File Url"},"file_name":{"type":"string","title":"File Name"},"file_size":{"type":"integer","title":"File Size"},"mime_type":{"type":"string","title":"Mime Type"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds"},"rotation_order":{"type":"integer","title":"Rotation Order"},"rotation_weight":{"type":"integer","title":"Rotation Weight"},"created_by":{"type":"string","title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","asset_type","file_url","file_name","file_size","mime_type","rotation_order","rotation_weight","created_by","created_at"],"title":"TrafficAssetResponse","description":"Response model for a traffic asset."},"cbradio-qa__TrafficAssetUpdate":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Label"},"rotation_order":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Rotation Order"},"rotation_weight":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Rotation Weight"}},"type":"object","title":"TrafficAssetUpdate","description":"Request model for updating a traffic asset."},"cbradio-qa__TrafficAssetsResponse":{"properties":{"audio_spots":{"items":{"$ref":"#/components/schemas/cbradio-qa__TrafficAssetResponse"},"type":"array","title":"Audio Spots","default":[]},"nab_form":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__TrafficAssetResponse"},{"type":"null"}]}},"type":"object","title":"TrafficAssetsResponse","description":"Response model for listing traffic assets."},"cbradio-qa__UploadUrlRequest":{"properties":{"file_name":{"type":"string","maxLength":255,"title":"File Name"},"mime_type":{"type":"string","maxLength":100,"title":"Mime Type"},"file_size":{"type":"integer","exclusiveMinimum":0.0,"title":"File Size"}},"type":"object","required":["file_name","mime_type","file_size"],"title":"UploadUrlRequest","description":"Request model for getting presigned upload URL."},"cbradio-qa__UploadUrlResponse":{"properties":{"upload_url":{"type":"string","title":"Upload Url"},"file_key":{"type":"string","title":"File Key"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["upload_url","file_key","expires_at"],"title":"UploadUrlResponse","description":"Response model for presigned upload URL."},"cbradio-qa__User":{"properties":{"username":{"type":"string","title":"Username"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"role":{"type":"string","title":"Role","default":"admin"},"id":{"type":"string","title":"Id"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["username","first_name","last_name","id","is_active","created_at","updated_at"],"title":"User","description":"Complete user model with all data."},"cbradio-qa__UserSummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"UserSummary","description":"Embedded user summary."},"cbradio-qa__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbradio-qa__VerifyRateCardBody":{"properties":{"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","title":"VerifyRateCardBody","description":"Request body for POST /rate-cards/{id}/verify (cbradio#240).\n\nOptional note — e.g. \"Re-confirmed with iHeart 2026-05-08, all rates\naccurate for the next political flight window\"."},"cbradio-qa__VerifyRateCardResponse":{"properties":{"rate_card_id":{"type":"string","title":"Rate Card Id"},"last_verified_by":{"type":"string","title":"Last Verified By"},"last_verified_at":{"type":"string","format":"date-time","title":"Last Verified At"},"last_verified_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Verified Note"},"is_stale":{"type":"boolean","title":"Is Stale"}},"type":"object","required":["rate_card_id","last_verified_by","last_verified_at","is_stale"],"title":"VerifyRateCardResponse","description":"Response for POST /rate-cards/{id}/verify (cbradio#240)."},"cbradio-qa__VersionDelta":{"properties":{"from_version_id":{"type":"string","title":"From Version Id"},"from_version_number":{"type":"integer","title":"From Version Number"},"to_version_id":{"type":"string","title":"To Version Id"},"to_version_number":{"type":"integer","title":"To Version Number"},"stations_added":{"type":"integer","title":"Stations Added","default":0},"stations_removed":{"type":"integer","title":"Stations Removed","default":0},"stations_modified":{"type":"integer","title":"Stations Modified","default":0},"payee_groups_added":{"type":"integer","title":"Payee Groups Added","default":0},"payee_groups_removed":{"type":"integer","title":"Payee Groups Removed","default":0},"payee_groups_modified":{"type":"integer","title":"Payee Groups Modified","default":0},"gross_delta":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Delta","default":"0"},"commission_delta":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission Delta","default":"0"},"station_payments_delta":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Payments Delta","default":"0"},"station_changes":{"items":{"$ref":"#/components/schemas/cbradio-qa__StationDelta"},"type":"array","title":"Station Changes"},"payee_group_changes":{"items":{"$ref":"#/components/schemas/cbradio-qa__PayeeGroupDelta"},"type":"array","title":"Payee Group Changes"}},"type":"object","required":["from_version_id","from_version_number","to_version_id","to_version_number"],"title":"VersionDelta","description":"Delta comparison between two versions."},"cbradio-qa__VersionListResponse":{"properties":{"buy_id":{"type":"string","title":"Buy Id"},"total_versions":{"type":"integer","title":"Total Versions"},"versions":{"items":{"$ref":"#/components/schemas/cbradio-qa__RadioBuyVersion"},"type":"array","title":"Versions"}},"type":"object","required":["buy_id","total_versions","versions"],"title":"VersionListResponse","description":"Response for listing versions of a radio buy."},"cbradio-qa__VoterCounts":{"properties":{"swing_likely_voters":{"type":"integer","title":"Swing Likely Voters","description":"Coverage-weighted swing voters across all counties.","examples":[4500]},"swing_likely_hhs":{"type":"integer","title":"Swing Likely Hhs","description":"Coverage-weighted swing households.","examples":[4200]},"base_likely_voters":{"type":"integer","title":"Base Likely Voters","description":"Coverage-weighted base voters across all counties.","examples":[8900]},"base_likely_hhs":{"type":"integer","title":"Base Likely Hhs","description":"Coverage-weighted base households.","examples":[8400]},"total_registered":{"type":"integer","title":"Total Registered","description":"Coverage-weighted registered voters across all counties.","examples":[250000]}},"type":"object","required":["swing_likely_voters","swing_likely_hhs","base_likely_voters","base_likely_hhs","total_registered"],"title":"VoterCounts","description":"Aggregated voter counts across a station's coverage area."},"cbradio-qa__WindowSplitOffer":{"properties":{"boundary":{"type":"string","format":"date","title":"Boundary","description":"First day of the LUR window"},"election_date":{"type":"string","format":"date","title":"Election Date"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type"},"window_days":{"type":"integer","title":"Window Days","description":"45 for a primary, 60 for a general or special"},"segments":{"items":{"$ref":"#/components/schemas/cbradio-qa__WindowSplitSegment"},"type":"array","title":"Segments"}},"type":"object","required":["boundary","election_date","window_days","segments"],"title":"WindowSplitOffer","description":"A straddling flight, and the split that would price it correctly.\n\n47 U.S.C. §315(b) obliges a station to give a candidate its lowest unit\ncharge inside the window and lets it charge comparable commercial rates\noutside. A flight crossing that boundary has **no single correct rate**, so\nthe quote it produces is not the one the buy will settle at.\n\nOffered, never applied. Dividing a budget between the segments is a buying\ndecision — a candidate may well want the money weighted toward the\nregulated window rather than spread evenly — and a flight quietly cut in two\nis worse than one that says it should be. Re-request each segment with its\nown dates and budget to be priced correctly on both."},"cbradio-qa__WindowSplitSegment":{"properties":{"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"rate_context":{"type":"string","title":"Rate Context","description":"in_window | out_of_window"},"weeks":{"type":"integer","title":"Weeks","description":"Calendar weeks this segment spans"}},"type":"object","required":["start_date","end_date","rate_context","weeks"],"title":"WindowSplitSegment","description":"One side of a flight that crosses the lowest-unit-charge boundary."},"cbradio-qa__src__api__models__agreement__LifecycleState":{"type":"string","enum":["draft","pending_signature","signed","expired","terminated"],"title":"LifecycleState","description":"Agreement lifecycle state."},"cbradio-qa__src__api__models__agreement__StationSummary":{"properties":{"callsign":{"type":"string","title":"Callsign"},"market":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"}},"type":"object","required":["callsign"],"title":"StationSummary","description":"Embedded station summary."},"cbradio-qa__src__api__models__order_packet__StationSummary":{"properties":{"callsign":{"type":"string","title":"Callsign"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"},"dma":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dma"}},"type":"object","required":["callsign"],"title":"StationSummary","description":"Minimal station info for order responses."},"cbradio-qa__src__api__models__proposal__AdType":{"type":"string","enum":["candidate","issue","all"],"title":"AdType","description":"Type of political advertising."},"cbradio-qa__src__api__models__rate__AdType":{"type":"string","enum":["candidate","issue","all"],"title":"AdType","description":"Type of political advertisement."},"cbradio-qa__src__api__models__srm__LifecycleState":{"type":"string","enum":["prospect","contacted","negotiating","active","at_risk","churned","inactive"],"title":"LifecycleState","description":"Station lifecycle states for SRM tracking."},"cbradio-qa__src__api__models__version__PaymentStatusUpdate":{"properties":{"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio-qa__PaymentStatusEnum"},{"type":"null"}]},"paid_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Paid Date"},"reference_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference Number"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"PaymentStatusUpdate","description":"Fields for updating payment status."},"cbradio-qa__src__api__routes__radio_buys__PaymentStatusUpdate":{"properties":{"is_paid":{"type":"boolean","title":"Is Paid"}},"type":"object","required":["is_paid"],"title":"PaymentStatusUpdate","description":"Request model for updating payment status."},"cbradio2__Allocation":{"properties":{"buy_id":{"type":"string","minLength":1,"title":"Buy Id"},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Amount"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"additionalProperties":false,"type":"object","required":["buy_id","amount"],"title":"Allocation","description":"One flight's share of this remittance."},"cbradio2__Attempt":{"properties":{"station_id":{"type":"string","title":"Station Id"},"channel":{"type":"string","title":"Channel"},"outcome":{"type":"string","title":"Outcome"},"logged_by":{"type":"string","minLength":1,"title":"Logged By"},"direction":{"type":"string","title":"Direction","default":"out"},"occurred_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Occurred At"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"follow_up_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Follow Up Date"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone"},"contact_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Role"},"contact_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Title"},"campaign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign"},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Id"},"in_reply_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"In Reply To"}},"additionalProperties":false,"type":"object","required":["station_id","channel","outcome","logged_by"],"title":"Attempt","description":"One attempt, or one answer. An unknown field is a 422, never a no-op —\nthe same rule as `/quote`, `/plan` and `/manual-rate` (cbradio2#20)."},"cbradio2__BatchQuoteRequest":{"properties":{"station_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Station Ids"},"card_year":{"type":"integer","maximum":2100.0,"minimum":2000.0,"title":"Card Year"},"duration_seconds":{"type":"integer","maximum":3600.0,"exclusiveMinimum":0.0,"title":"Duration Seconds"},"ad_type":{"type":"string","enum":["candidate","issue","any"],"title":"Ad Type"},"days_mask":{"type":"integer","maximum":127.0,"minimum":1.0,"title":"Days Mask"},"daypart_id":{"anyOf":[{"type":"string","enum":["am_drive","midday","pm_drive","evening","overnight","ros"]},{"type":"null"}],"title":"Daypart Id","description":"Which daypart the buy is for. OPTIONAL: absent means the engine picks, which is the pre-1.4.0 behaviour."},"preemption":{"anyOf":[{"type":"string","enum":["non_preemptible","preemptible_with_notice","preemptible"]},{"type":"null"}],"title":"Preemption","description":"Insist the spot is offered on this basis. OPTIONAL: absent means the cheapest tier wins, which is the pre-1.5.0 behaviour. ⚠️ `any` is NOT accepted — it is a card that did not state a tier, not a tier you can buy. An unavailable tier is a refusal, never a substitution."},"policy":{"type":"string","title":"Policy","default":"default"}},"additionalProperties":false,"type":"object","required":["station_ids","card_year","duration_seconds","ad_type","days_mask"],"title":"BatchQuoteRequest","description":"One spot specification, priced across many stations."},"cbradio2__CheckResult":{"properties":{"ok":{"type":"boolean","title":"Ok"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"digest":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Digest"},"problems":{"items":{"type":"string"},"type":"array","title":"Problems"}},"type":"object","required":["ok"],"title":"CheckResult"},"cbradio2__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbradio2__Correction":{"properties":{"reason":{"type":"string","minLength":8,"title":"Reason"},"deleted_by":{"type":"string","minLength":1,"title":"Deleted By"},"added_by":{"type":"string","minLength":1,"title":"Added By"}},"additionalProperties":false,"type":"object","required":["reason","deleted_by","added_by"],"title":"Correction","description":"Fix the reason or the attributed actor on a STANDING retirement.\n\n⚠️ This exists because the alternative is worse. Reinstating and\nre-retiring would put the row back in every total for as long as the two\ncalls take, and would restamp `deleted_at` with the moment of the\ncorrection rather than the moment of the decision.\n\nThe first retirement this service ever recorded needed exactly this: it\nwas attributed to the OPERATOR by name on a write the cbradio2 lane\nperformed on a relayed instruction."},"cbradio2__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbradio2__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbradio2__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbradio2__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbradio2__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbradio2__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbradio2__Logged":{"properties":{"event":{"additionalProperties":true,"type":"object","title":"Event"},"contact":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Contact"}},"type":"object","required":["event","contact"],"title":"Logged","description":"What the caller gets back: the event as the list would render it, and an\nexplicit account of what happened to the contact."},"cbradio2__ManualRate":{"properties":{"station_id":{"type":"string","title":"Station Id"},"card_year":{"type":"integer","maximum":2100.0,"minimum":2000.0,"title":"Card Year"},"daypart_id":{"type":"string","title":"Daypart Id"},"days_mask":{"type":"integer","maximum":127.0,"minimum":1.0,"title":"Days Mask"},"duration_seconds":{"type":"integer","exclusiveMinimum":0.0,"title":"Duration Seconds"},"ad_type":{"type":"string","title":"Ad Type"},"gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate"},"net_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate"},"entered_by":{"type":"string","minLength":1,"title":"Entered By"},"rate_context":{"type":"string","title":"Rate Context","default":"any"},"preemption":{"type":"string","title":"Preemption","default":"any"},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"replace":{"type":"boolean","title":"Replace","default":false}},"additionalProperties":false,"type":"object","required":["station_id","card_year","daypart_id","days_mask","duration_seconds","ad_type","entered_by"],"title":"ManualRate","description":"What a person can supply from a phone call, and nothing else.\n\nAn unknown body field is a 422 rather than a no-op, the same as `/quote`\nand `/plan` (cbradio2#20): a form that sends `grossRate` and gets a 201 has\nbeen told its typo was accepted."},"cbradio2__ManualRateResult":{"properties":{"rate_id":{"type":"string","title":"Rate Id"},"rate_card_id":{"type":"string","title":"Rate Card Id"},"station_id":{"type":"string","title":"Station Id"},"card_year":{"type":"integer","title":"Card Year"},"price_source":{"type":"string","title":"Price Source","default":"human"},"review_status":{"type":"string","title":"Review Status","default":"unreviewed"},"gross_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Rate"},"net_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate"},"gross_basis":{"type":"string","title":"Gross Basis"},"net_basis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Net Basis"},"effective_date":{"type":"string","format":"date","title":"Effective Date"},"entered_by":{"type":"string","title":"Entered By"},"card_created":{"type":"boolean","title":"Card Created"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"replaced":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Replaced"}},"type":"object","required":["rate_id","rate_card_id","station_id","card_year","gross_rate","gross_basis","effective_date","entered_by","card_created"],"title":"ManualRateResult","description":"What the form gets back. Every id it needs to link to the row, and an\nexplicit account of what the write actually did."},"cbradio2__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbradio2__NetworkAgencySlice":{"properties":{"proposal_line_item_id":{"type":"string","minLength":1,"title":"Proposal Line Item Id"},"agency_party":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agency Party"},"agency_amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Agency Amount"},"rural_amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rural Amount"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"additionalProperties":false,"type":"object","required":["proposal_line_item_id","agency_amount","rural_amount"],"title":"NetworkAgencySlice","description":"Who takes the buyer agency's 15% on ONE network line, and how much.\n\nOperator ruling, 2026-08-30 (#43): **it depends on the deal, so the screen\nasks every time** — no default and no standing rate.\n\n⚠️ The question is *who takes the slice*, not *what is the agency cut with\n0 as an accepted answer*. The 15% is a fixed part of the cascade and does\nnot vanish when there is no outside agency; it has to be assigned or the\nline does not close. So both halves are stated and they must SUM to the\nslice — a partial cut has to say where the remainder goes, because\nsilently leaving it with Rural is the same defect one layer down."},"cbradio2__NetworkLineRequest":{"properties":{"network_id":{"type":"string","title":"Network Id"},"ad_type":{"type":"string","enum":["candidate","issue"],"title":"Ad Type"},"duration_seconds":{"type":"integer","maximum":3600.0,"exclusiveMinimum":0.0,"title":"Duration Seconds"},"spots_per_week":{"type":"integer","exclusiveMinimum":0.0,"title":"Spots Per Week"},"weeks":{"type":"integer","exclusiveMinimum":0.0,"title":"Weeks"},"days_mask":{"type":"integer","maximum":127.0,"minimum":1.0,"title":"Days Mask","default":127},"origin":{"type":"string","enum":["manual","solver","imported"],"title":"Origin","default":"manual"},"rationale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rationale"}},"additionalProperties":false,"type":"object","required":["network_id","ad_type","duration_seconds","spots_per_week","weeks"],"title":"NetworkLineRequest"},"cbradio2__NetworkQuote":{"properties":{"network_id":{"type":"string","title":"Network Id"},"ok":{"type":"boolean","title":"Ok"},"basis":{"anyOf":[{"type":"string","const":"network"},{"type":"null"}],"title":"Basis"},"network":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Network"},"network_rate":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Network Rate"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"considered":{"type":"integer","title":"Considered","default":0},"refused_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refused By"},"refusal_reason":{"type":"string","title":"Refusal Reason","default":""}},"type":"object","required":["network_id","ok"],"title":"NetworkQuote"},"cbradio2__NetworkQuoteRequest":{"properties":{"network_id":{"type":"string","title":"Network Id"},"ad_type":{"type":"string","enum":["candidate","issue"],"title":"Ad Type"},"duration_seconds":{"type":"integer","maximum":3600.0,"exclusiveMinimum":0.0,"title":"Duration Seconds"}},"additionalProperties":false,"type":"object","required":["network_id","ad_type","duration_seconds"],"title":"NetworkQuoteRequest","description":"The cell being priced. `ad_type` is the network vocabulary —\n`candidate | issue`, never `any` — because that is what the vendor's\nsheet prices."},"cbradio2__Outstanding":{"properties":{"station_id":{"type":"string","title":"Station Id"},"reason":{"type":"string","enum":["needs_card","price_moved","card_gone","price_conflict"],"title":"Reason"},"detail":{"type":"string","title":"Detail"},"planned":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Planned"},"current":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Current"},"rate_card_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Card Id"},"prices":{"items":{"type":"string"},"type":"array","title":"Prices"}},"type":"object","required":["station_id","reason","detail"],"title":"Outstanding","description":"A line that did NOT make it onto the proposal, and what to do about it.\n\n⚠️ **A refusal here is an ANSWER, not an error.** `promote` returns 201 with\nthese alongside what it carried, because confirming by exception is the\npoint: a caller renders the list and acts on it. A consumer that treats a\nnon-empty `outstanding` as a failed call has misread the endpoint."},"cbradio2__PaymentRequest":{"properties":{"payer":{"type":"string","minLength":1,"title":"Payer"},"received_on":{"type":"string","format":"date","title":"Received On"},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Amount"},"reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"added_by":{"type":"string","minLength":1,"title":"Added By"},"allocations":{"items":{"$ref":"#/components/schemas/cbradio2__Allocation"},"type":"array","minItems":1,"title":"Allocations"}},"additionalProperties":false,"type":"object","required":["payer","received_on","amount","added_by","allocations"],"title":"PaymentRequest"},"cbradio2__PlacementRequest":{"properties":{"added_by":{"type":"string","minLength":1,"title":"Added By"},"nab_agency":{"type":"string","minLength":1,"title":"Nab Agency"},"buyer_agency_commission":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Buyer Agency Commission","default":"0"},"network_agency":{"items":{"$ref":"#/components/schemas/cbradio2__NetworkAgencySlice"},"type":"array","title":"Network Agency"},"client":{"type":"string","minLength":1,"title":"Client"},"cycle":{"anyOf":[{"type":"integer","maximum":2100.0,"minimum":2000.0},{"type":"null"}],"title":"Cycle"},"flight_start":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight End"},"election":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election"},"election_kind":{"anyOf":[{"type":"string","enum":["primary","general","runoff","special"]},{"type":"null"}],"title":"Election Kind"},"splits":{"items":{"$ref":"#/components/schemas/cbradio2__ProposedSplit"},"type":"array","title":"Splits"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"additionalProperties":false,"type":"object","required":["added_by","nab_agency","client"],"title":"PlacementRequest","description":"What placing a buy needs that the proposal does not hold."},"cbradio2__PlanRequest":{"properties":{"name":{"type":"string","minLength":1,"title":"Name"},"station_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Station Ids"},"card_year":{"type":"integer","maximum":2100.0,"minimum":2000.0,"title":"Card Year"},"duration_seconds":{"type":"integer","maximum":3600.0,"exclusiveMinimum":0.0,"title":"Duration Seconds"},"ad_type":{"type":"string","enum":["candidate","issue","any"],"title":"Ad Type"},"days_mask":{"type":"integer","maximum":127.0,"minimum":1.0,"title":"Days Mask"},"daypart_id":{"type":"string","enum":["am_drive","midday","pm_drive","evening","overnight","ros"],"title":"Daypart Id"},"spots_per_week":{"type":"integer","exclusiveMinimum":0.0,"title":"Spots Per Week"},"weeks":{"type":"integer","exclusiveMinimum":0.0,"title":"Weeks"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"cycle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cycle"},"budget":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Budget"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"policy":{"type":"string","title":"Policy","default":"default"}},"additionalProperties":false,"type":"object","required":["name","station_ids","card_year","duration_seconds","ad_type","days_mask","daypart_id","spots_per_week","weeks"],"title":"PlanRequest","description":"A footprint plus the spot being planned across it."},"cbradio2__PlanResult":{"properties":{"plan_id":{"type":"string","title":"Plan Id"},"lines":{"type":"integer","title":"Lines"},"confirmed":{"type":"integer","title":"Confirmed"},"estimated":{"type":"integer","title":"Estimated"},"conflicted":{"type":"integer","title":"Conflicted","default":0},"skipped":{"items":{"$ref":"#/components/schemas/cbradio2__Skipped"},"type":"array","title":"Skipped"}},"type":"object","required":["plan_id","lines","confirmed","estimated"],"title":"PlanResult"},"cbradio2__PolicyDraft":{"properties":{"document":{"type":"string","title":"Document","description":"The policy document, as YAML."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Why this version exists."},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","required":["document"],"title":"PolicyDraft"},"cbradio2__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbradio2__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbradio2__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbradio2__PromoteRequest":{"properties":{"tolerance":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Tolerance","description":"Fractional price move a card line may make and still be carried without review. 0 means confirm every line by hand.","default":"0.10"},"policy":{"type":"string","title":"Policy","default":"default"}},"additionalProperties":false,"type":"object","title":"PromoteRequest"},"cbradio2__PromoteResult":{"properties":{"plan_id":{"type":"string","title":"Plan Id"},"proposal_id":{"type":"string","title":"Proposal Id"},"confirmed":{"type":"integer","title":"Confirmed"},"outstanding":{"items":{"$ref":"#/components/schemas/cbradio2__Outstanding"},"type":"array","title":"Outstanding"},"superseded_proposal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Superseded Proposal Id"}},"type":"object","required":["plan_id","proposal_id","confirmed"],"title":"PromoteResult"},"cbradio2__ProposedSplit":{"properties":{"role":{"type":"string","enum":["salesman","consultant"],"title":"Role"},"party":{"type":"string","minLength":1,"title":"Party"},"share_pct":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Share Pct"},"amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Amount"}},"additionalProperties":false,"type":"object","required":["role","party"],"title":"ProposedSplit","description":"CJ's proposal of who gets paid. `share_pct` is a share of Rural's\ncommission on the buy; `amount` is a stated dollar figure. One or the\nother — a split with both would let them disagree."},"cbradio2__Quote":{"properties":{"station_id":{"type":"string","title":"Station Id"},"ok":{"type":"boolean","title":"Ok"},"basis":{"anyOf":[{"type":"string","enum":["card","benchmark"]},{"type":"null"}],"title":"Basis"},"policy":{"type":"string","title":"Policy"},"rate":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Rate"},"benchmark":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Benchmark"},"decided_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decided By"},"daypart_honoured":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Daypart Honoured"},"refused_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refused By"},"refusal_detail":{"additionalProperties":true,"type":"object","title":"Refusal Detail"},"refusal_reason":{"type":"string","title":"Refusal Reason","default":""},"considered":{"type":"integer","title":"Considered","default":0},"rejected_by_rule":{"additionalProperties":{"type":"integer"},"type":"object","title":"Rejected By Rule"},"conflict":{"anyOf":[{"$ref":"#/components/schemas/cbradio2__RateConflict"},{"type":"null"}]}},"type":"object","required":["station_id","ok","policy"],"title":"Quote"},"cbradio2__QuoteRequest":{"properties":{"station_id":{"type":"string","title":"Station Id"},"card_year":{"type":"integer","maximum":2100.0,"minimum":2000.0,"title":"Card Year"},"duration_seconds":{"type":"integer","maximum":3600.0,"exclusiveMinimum":0.0,"title":"Duration Seconds"},"ad_type":{"type":"string","enum":["candidate","issue","any"],"title":"Ad Type","description":"candidate, issue, or any"},"days_mask":{"type":"integer","maximum":127.0,"minimum":1.0,"title":"Days Mask","description":"7-bit set, bit 0 = Monday. 31 is M-F, 96 the weekend, 127 all week. 0 would ask for a rate covering no days at all."},"daypart_id":{"anyOf":[{"type":"string","enum":["am_drive","midday","pm_drive","evening","overnight","ros"]},{"type":"null"}],"title":"Daypart Id","description":"Which daypart the buy is for. OPTIONAL: absent means the engine picks, which is the pre-1.4.0 behaviour."},"preemption":{"anyOf":[{"type":"string","enum":["non_preemptible","preemptible_with_notice","preemptible"]},{"type":"null"}],"title":"Preemption","description":"Insist the spot is offered on this basis. OPTIONAL: absent means the cheapest tier wins, which is the pre-1.5.0 behaviour. ⚠️ `any` is NOT accepted — it is a card that did not state a tier, not a tier you can buy. An unavailable tier is a refusal, never a substitution."},"policy":{"type":"string","title":"Policy","description":"Policy NAME; the ACTIVE version prices.","default":"default"}},"additionalProperties":false,"type":"object","required":["station_id","card_year","duration_seconds","ad_type","days_mask"],"title":"QuoteRequest","description":"What is being priced. Every field is required on purpose.\n\n`card_year` especially: v1's proposal generator called selection and nothing\nelse, so it would quote a 2020 card at a 2026 campaign without comment. A\ndefault here would put that back by making the wrong year the easy one."},"cbradio2__RateConflict":{"properties":{"checked":{"type":"boolean","title":"Checked","default":true},"blocking":{"type":"boolean","title":"Blocking","default":false},"prices":{"items":{"type":"string"},"type":"array","title":"Prices"},"dropped":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Dropped"},"card_blocking_total":{"type":"integer","title":"Card Blocking Total","default":0}},"type":"object","title":"RateConflict","description":"Does the card state a SECOND price for the slot we just quoted?\n\n🚨 **Present and `blocking: true` means the number above is the winner of a\ntiebreak, not a decision.** CJ's rate-spec item 3 (2026-07-31): *same\nproduct, same date, disagreeing on price — nobody wins. Flag it, quote\nnothing, get a human.* `ingest_reject`'s own reason string says \"this is a\ntiebreak, not a rule\".\n\n⚠️ **WHAT THE CALLER DOES WITH IT DIFFERS BY SURFACE, AND BOTH ANSWERS ARE\nCJ'S** (2026-09-14):\n\n| surface | on a blocking clash |\n|---|---|\n| `/quote` — a price handed to someone | **refuses**, *\"quote nothing\"* |\n| `/plan` — a working document a human reviews | **prices, FLAGGED** |\n\nA plan is reviewed before it becomes a proposal; a spot quote is not. So the\nsame object is the EXPLANATION of a refusal on one surface and a warning\nbeside a real price on the other. It lives here rather than in either router\nfor that reason."},"cbradio2__Reinstatement":{"properties":{"reason":{"type":"string","minLength":8,"title":"Reason"},"added_by":{"type":"string","minLength":1,"title":"Added By"}},"additionalProperties":false,"type":"object","required":["reason","added_by"],"title":"Reinstatement","description":"⚠️ `reason` is required here too. \"Put it back\" with no reason is the\nsame unanswerable state as \"retired\" with no reason, one direction over —\nand before the ledger existed there was nowhere for it to go at all."},"cbradio2__Retirement":{"properties":{"reason":{"type":"string","minLength":8,"title":"Reason"},"added_by":{"type":"string","minLength":1,"title":"Added By"}},"additionalProperties":false,"type":"object","required":["reason","added_by"],"title":"Retirement","description":"Why this row is leaving the book, and who says so."},"cbradio2__Skipped":{"properties":{"station_id":{"type":"string","title":"Station Id"},"reason":{"type":"string","title":"Reason"}},"type":"object","required":["station_id","reason"],"title":"Skipped"},"cbradio2__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbradio2__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbradio2__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbradio2__Withdrawal":{"properties":{"rate_id":{"type":"string","title":"Rate Id"},"station_id":{"type":"string","title":"Station Id"},"gross_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Rate"},"withdrawn_by":{"type":"string","title":"Withdrawn By"},"withdrawn_at":{"type":"string","title":"Withdrawn At"},"was_already_withdrawn":{"type":"boolean","title":"Was Already Withdrawn"}},"type":"object","required":["rate_id","station_id","gross_rate","withdrawn_by","withdrawn_at","was_already_withdrawn"],"title":"Withdrawal","description":"What withdrawing a phoned-in rate did."},"cbradio__AcceptProposal":{"properties":{"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"AcceptProposal","description":"Request to mark proposal as accepted."},"cbradio__AdTypeEnum":{"type":"string","enum":["candidate","issue"],"title":"AdTypeEnum","description":"Ad type classification for radio buys."},"cbradio__AddStationRequest":{"properties":{"callsign":{"type":"string","maxLength":20,"minLength":1,"title":"Callsign"}},"type":"object","required":["callsign"],"title":"AddStationRequest","description":"Request model for adding a station to a recipient."},"cbradio__AffectedSpots":{"properties":{"dates":{"items":{"type":"string","format":"date"},"type":"array","title":"Dates","default":[]},"dayparts":{"items":{"type":"string"},"type":"array","title":"Dayparts","default":[]}},"type":"object","title":"AffectedSpots","description":"Details of affected spots for make-good."},"cbradio__AffiliateScore":{"properties":{"call_sign":{"type":"string","title":"Call Sign"},"matched_station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matched Station Id"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"},"format_score":{"type":"integer","title":"Format Score"},"coverage_score":{"type":"integer","title":"Coverage Score"},"nielsen_score":{"type":"integer","title":"Nielsen Score"},"voter_efficiency_score":{"type":"integer","title":"Voter Efficiency Score"},"total_score":{"type":"integer","title":"Total Score"},"coverage_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Coverage Pct"},"aqh_share":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aqh Share"}},"type":"object","required":["call_sign","format_score","coverage_score","nielsen_score","voter_efficiency_score","total_score"],"title":"AffiliateScore","description":"Per-affiliate scoring result in a network."},"cbradio__AgencyStatus":{"type":"string","enum":["active","inactive"],"title":"AgencyStatus","description":"Buyer agency status."},"cbradio__AggregateScore":{"properties":{"format_score":{"type":"number","title":"Format Score"},"coverage_score":{"type":"number","title":"Coverage Score"},"nielsen_score":{"type":"number","title":"Nielsen Score"},"voter_efficiency_score":{"type":"number","title":"Voter Efficiency Score"},"total_score":{"type":"number","title":"Total Score"}},"type":"object","required":["format_score","coverage_score","nielsen_score","voter_efficiency_score","total_score"],"title":"AggregateScore","description":"Aggregate (mean) scores across a network's scored affiliates."},"cbradio__AgreementCreate":{"properties":{"station_callsign":{"type":"string","maxLength":20,"minLength":1,"title":"Station Callsign"},"agreement_type":{"$ref":"#/components/schemas/cbradio__AgreementType","default":"standard"},"effective_date":{"type":"string","format":"date","title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date","description":"NULL = perpetual"},"auto_renew":{"type":"boolean","title":"Auto Renew","default":false},"commission_rate":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Commission Rate"},"terms_summary":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Terms Summary"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["station_callsign","effective_date"],"title":"AgreementCreate","description":"Request model for creating an agreement."},"cbradio__AgreementDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"agreement_type":{"$ref":"#/components/schemas/cbradio__AgreementType"},"lifecycle_state":{"$ref":"#/components/schemas/cbradio__src__api__models__agreement__LifecycleState"},"effective_date":{"type":"string","format":"date","title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"auto_renew":{"type":"boolean","title":"Auto Renew"},"commission_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Commission Rate"},"terms_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Terms Summary"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_by":{"anyOf":[{"$ref":"#/components/schemas/cbradio__UserSummary"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"signed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Signed At"},"signed_by_station":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed By Station"},"terminated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Terminated At"},"termination_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Termination Reason"},"documents_count":{"type":"integer","title":"Documents Count","default":0},"station":{"anyOf":[{"$ref":"#/components/schemas/cbradio__src__api__models__agreement__StationSummary"},{"type":"null"}]},"documents":{"items":{"$ref":"#/components/schemas/cbradio__AgreementDocumentResponse"},"type":"array","title":"Documents","default":[]},"audit_trail":{"items":{"$ref":"#/components/schemas/cbradio__AuditEntry"},"type":"array","title":"Audit Trail","default":[]}},"type":"object","required":["id","station_callsign","agreement_type","lifecycle_state","effective_date","auto_renew","created_at","updated_at"],"title":"AgreementDetailResponse","description":"Detailed response model for single agreement."},"cbradio__AgreementDocumentResponse":{"properties":{"id":{"type":"string","title":"Id"},"document_type":{"$ref":"#/components/schemas/cbradio__DocumentType"},"file_name":{"type":"string","title":"File Name"},"file_url":{"type":"string","title":"File Url"},"file_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size"},"mime_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime Type"},"uploaded_by":{"anyOf":[{"$ref":"#/components/schemas/cbradio__UserSummary"},{"type":"null"}]},"uploaded_at":{"type":"string","format":"date-time","title":"Uploaded At"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["id","document_type","file_name","file_url","uploaded_at"],"title":"AgreementDocumentResponse","description":"Response model for agreement document."},"cbradio__AgreementListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/cbradio__AgreementResponse"},"type":"array","title":"Data"},"pagination":{"additionalProperties":true,"type":"object","title":"Pagination"}},"type":"object","required":["data","pagination"],"title":"AgreementListResponse","description":"Paginated list of agreements."},"cbradio__AgreementResponse":{"properties":{"id":{"type":"string","title":"Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"agreement_type":{"$ref":"#/components/schemas/cbradio__AgreementType"},"lifecycle_state":{"$ref":"#/components/schemas/cbradio__src__api__models__agreement__LifecycleState"},"effective_date":{"type":"string","format":"date","title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"auto_renew":{"type":"boolean","title":"Auto Renew"},"commission_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Commission Rate"},"terms_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Terms Summary"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_by":{"anyOf":[{"$ref":"#/components/schemas/cbradio__UserSummary"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"signed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Signed At"},"signed_by_station":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed By Station"},"terminated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Terminated At"},"termination_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Termination Reason"},"documents_count":{"type":"integer","title":"Documents Count","default":0}},"type":"object","required":["id","station_callsign","agreement_type","lifecycle_state","effective_date","auto_renew","created_at","updated_at"],"title":"AgreementResponse","description":"Response model for agreement."},"cbradio__AgreementTransition":{"properties":{"target_state":{"$ref":"#/components/schemas/cbradio__src__api__models__agreement__LifecycleState"},"signed_by_station":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Signed By Station","description":"Required when transitioning to signed"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["target_state"],"title":"AgreementTransition","description":"Request model for transitioning agreement state."},"cbradio__AgreementType":{"type":"string","enum":["standard","custom","legacy"],"title":"AgreementType","description":"Type of agreement."},"cbradio__AgreementUpdate":{"properties":{"agreement_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AgreementType"},{"type":"null"}]},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"auto_renew":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Auto Renew"},"commission_rate":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Commission Rate"},"terms_summary":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Terms Summary"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"AgreementUpdate","description":"Request model for updating an agreement."},"cbradio__AlertList":{"properties":{"alerts":{"items":{"$ref":"#/components/schemas/cbradio__SRMAlert"},"type":"array","title":"Alerts"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["alerts","total"],"title":"AlertList","description":"Paginated list of alerts."},"cbradio__AlertType":{"type":"string","enum":["ownership_change","agreement_expiring","no_contact"],"title":"AlertType","description":"System alert types."},"cbradio__ApplyCorrectionRequest":{"properties":{"corrections":{"additionalProperties":true,"type":"object","title":"Corrections","description":"Field corrections to apply"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Reason for correction"}},"type":"object","required":["corrections"],"title":"ApplyCorrectionRequest","description":"Request model for applying corrections to a staged record."},"cbradio__ApprovedDaypartRate":{"properties":{"daypart_id":{"type":"string","title":"Daypart Id"},"daypart_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Daypart Label"},"time_range":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Range"},"gross_rate":{"type":"number","exclusiveMinimum":0.0,"title":"Gross Rate"},"source":{"type":"string","title":"Source","description":"extracted, corrected, or manual","default":"extracted"},"ad_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ad Type","description":"Product this rate prices: candidate, issue, commercial. Omit to inherit from the card's existing rows. Required when the card carries more than one ad_type for the same daypart and duration."},"net_rate":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Net Rate","description":"Net the source document states, if it states one. Omit to derive from gross at 15% agency commission."}},"type":"object","required":["daypart_id","gross_rate"],"title":"ApprovedDaypartRate","description":"A single daypart rate within the QA approval grid (#53)."},"cbradio__ApprovedRateGrid":{"properties":{"spot_length":{"type":"integer","title":"Spot Length","description":"30 or 60"},"daypart_rates":{"items":{"$ref":"#/components/schemas/cbradio__ApprovedDaypartRate"},"type":"array","title":"Daypart Rates"}},"type":"object","required":["spot_length","daypart_rates"],"title":"ApprovedRateGrid","description":"Rate grid grouped by spot length, sent on QA approval (#53)."},"cbradio__AssetType":{"type":"string","enum":["audio_spot","nab_form"],"title":"AssetType","description":"Type of traffic asset."},"cbradio__AuditEntry":{"properties":{"action":{"type":"string","title":"Action"},"performed_by":{"type":"string","title":"Performed By"},"performed_at":{"type":"string","format":"date-time","title":"Performed At"},"old_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Old Value"},"new_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"New Value"}},"type":"object","required":["action","performed_by","performed_at"],"title":"AuditEntry","description":"Single audit log entry."},"cbradio__AuditLogResponse":{"properties":{"events":{"items":{"$ref":"#/components/schemas/cbradio__ConfirmationEvent"},"type":"array","title":"Events"}},"type":"object","required":["events"],"title":"AuditLogResponse","description":"Response for order audit log."},"cbradio__Body_decide_api_v1_year_proposals__document_id__post":{"properties":{"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"reviewer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reviewer"}},"type":"object","title":"Body_decide_api_v1_year_proposals__document_id__post"},"cbradio__Body_login_for_access_token_api_auth_token_post":{"properties":{"grant_type":{"anyOf":[{"type":"string","pattern":"^password$"},{"type":"null"}],"title":"Grant Type"},"username":{"type":"string","title":"Username"},"password":{"type":"string","format":"password","title":"Password"},"scope":{"type":"string","title":"Scope","default":""},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"password","title":"Client Secret"}},"type":"object","required":["username","password"],"title":"Body_login_for_access_token_api_auth_token_post"},"cbradio__BudgetRange":{"type":"string","enum":["$10-25K","$25-50K","$50-100K","$100K+"],"title":"BudgetRange","description":"Budget range tiers."},"cbradio__BulkApproveRequest":{"properties":{"ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Ids","description":"Specific IDs to approve"},"filter":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Filter","description":"Filter criteria for bulk approval"}},"type":"object","title":"BulkApproveRequest","description":"Request model for bulk approving staged records."},"cbradio__BulkCloseRequest":{"properties":{"project":{"type":"string","title":"Project","description":"Project slug"},"issues":{"items":{"type":"integer"},"type":"array","minItems":1,"title":"Issues","description":"Issue numbers"},"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment","description":"Closing comment"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Reason: completed or not_planned"}},"type":"object","required":["project","issues"],"title":"BulkCloseRequest","description":"Request body for bulk close operations.","example":{"issues":[1,2,3],"project":"cbradio","reason":"completed"}},"cbradio__BulkContactImportRequest":{"properties":{"stations":{"items":{"$ref":"#/components/schemas/cbradio__BulkContactItem"},"type":"array","title":"Stations"},"source":{"type":"string","title":"Source","default":"bulk_import"}},"type":"object","required":["stations"],"title":"BulkContactImportRequest","description":"Request body for bulk contact import."},"cbradio__BulkContactItem":{"properties":{"callsign":{"type":"string","title":"Callsign"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"mailing_street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Street"},"mailing_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing City"},"mailing_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing State"},"mailing_zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Zip"}},"type":"object","required":["callsign"],"title":"BulkContactItem","description":"Single item in a bulk contact import."},"cbradio__BulkLabelRequest":{"properties":{"project":{"type":"string","title":"Project","description":"Project slug"},"issues":{"items":{"type":"integer"},"type":"array","minItems":1,"title":"Issues","description":"Issue numbers"},"add_labels":{"items":{"type":"string"},"type":"array","title":"Add Labels","description":"Labels to add"},"remove_labels":{"items":{"type":"string"},"type":"array","title":"Remove Labels","description":"Labels to remove"}},"type":"object","required":["project","issues"],"title":"BulkLabelRequest","description":"Request body for bulk label operations.","example":{"add_labels":["reviewed"],"issues":[1,2,3],"project":"cbradio"}},"cbradio__BulkOperationResponse":{"properties":{"success_count":{"type":"integer","title":"Success Count"},"error_count":{"type":"integer","title":"Error Count"},"results":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Results"}},"type":"object","required":["success_count","error_count","results"],"title":"BulkOperationResponse","description":"Response for bulk operations."},"cbradio__BulkOperationResult":{"properties":{"success_count":{"type":"integer","title":"Success Count","description":"Number of successful operations"},"error_count":{"type":"integer","title":"Error Count","description":"Number of failed operations","default":0},"errors":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Errors","description":"List of errors with id and message"},"created_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Created Ids","description":"IDs of created resources (for bulk create)"}},"type":"object","required":["success_count"],"title":"BulkOperationResult","description":"Result of a bulk operation."},"cbradio__BulkOwnershipImportRequest":{"properties":{"stations":{"items":{"$ref":"#/components/schemas/cbradio__BulkOwnershipItem"},"type":"array","title":"Stations"},"source":{"type":"string","title":"Source","default":"bulk_import"}},"type":"object","required":["stations"],"title":"BulkOwnershipImportRequest","description":"Request body for bulk ownership import."},"cbradio__BulkOwnershipItem":{"properties":{"callsign":{"type":"string","title":"Callsign"},"ownership_group":{"type":"string","title":"Ownership Group"}},"type":"object","required":["callsign","ownership_group"],"title":"BulkOwnershipItem","description":"Single item in a bulk ownership import."},"cbradio__BulkRateCreate":{"properties":{"rates":{"items":{"$ref":"#/components/schemas/cbradio__RateCreateForCard"},"type":"array","minItems":1,"title":"Rates"}},"type":"object","required":["rates"],"title":"BulkRateCreate","description":"Create multiple rates for an existing rate card."},"cbradio__BulkRateDelete":{"properties":{"rate_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Rate Ids"}},"type":"object","required":["rate_ids"],"title":"BulkRateDelete","description":"Delete multiple rates by ID."},"cbradio__BulkRateUpdate":{"properties":{"updates":{"items":{"$ref":"#/components/schemas/cbradio__RateUpdateWithId"},"type":"array","minItems":1,"title":"Updates"}},"type":"object","required":["updates"],"title":"BulkRateUpdate","description":"Update multiple rates by ID."},"cbradio__BulkSendRequest":{"properties":{"order_packet_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Order Packet Ids"},"sender_name":{"type":"string","maxLength":255,"minLength":1,"title":"Sender Name"},"sender_title":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Sender Title"},"custom_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Message","description":"Optional message included in email body"}},"type":"object","required":["order_packet_ids","sender_name"],"title":"BulkSendRequest","description":"Request model for bulk sending orders."},"cbradio__BulkSendResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"sent_count":{"type":"integer","title":"Sent Count"},"failed_count":{"type":"integer","title":"Failed Count"},"results":{"items":{"$ref":"#/components/schemas/cbradio__BulkSendResult"},"type":"array","title":"Results"}},"type":"object","required":["success","sent_count","failed_count","results"],"title":"BulkSendResponse","description":"Response after bulk sending orders."},"cbradio__BulkSendResult":{"properties":{"order_packet_id":{"type":"string","title":"Order Packet Id"},"status":{"type":"string","title":"Status"},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At"},"email_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Id"},"delivered_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivered To"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"}},"type":"object","required":["order_packet_id","status"],"title":"BulkSendResult","description":"Result for single order in bulk send."},"cbradio__BuyerAgency":{"properties":{"name":{"type":"string","title":"Name","description":"Agency name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AgencyStatus"},{"type":"null"}],"description":"Agency status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Agency contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Agency contact phone"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name","id","created_at","updated_at"],"title":"BuyerAgency","description":"Complete buyer agency model with database fields."},"cbradio__BuyerAgencyCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Agency name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AgencyStatus"},{"type":"null"}],"description":"Agency status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Agency contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Agency contact phone"}},"type":"object","required":["name"],"title":"BuyerAgencyCreate","description":"Model for creating a new buyer agency."},"cbradio__BuyerAgencySummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AgencyStatus"},{"type":"null"}]},"total_buys":{"type":"integer","title":"Total Buys","default":0},"total_volume":{"type":"number","title":"Total Volume","default":0.0},"client_count":{"type":"integer","title":"Client Count","default":0},"last_buy_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Last Buy Date"}},"type":"object","required":["id","name"],"title":"BuyerAgencySummary","description":"Lightweight buyer agency summary for list views."},"cbradio__BuyerAgencyUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AgencyStatus"},{"type":"null"}]},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone"}},"type":"object","title":"BuyerAgencyUpdate","description":"Model for updating a buyer agency (all fields optional)."},"cbradio__BuyerAgencyWithRadioBuys":{"properties":{"name":{"type":"string","title":"Name","description":"Agency name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AgencyStatus"},{"type":"null"}],"description":"Agency status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Agency contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Agency contact phone"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"radio_buys":{"items":{"$ref":"#/components/schemas/cbradio__RadioBuySummary"},"type":"array","title":"Radio Buys","default":[]}},"type":"object","required":["name","id","created_at","updated_at"],"title":"BuyerAgencyWithRadioBuys","description":"Buyer agency with associated radio buys."},"cbradio__CampaignType":{"type":"string","enum":["candidate_in_window","candidate_out_window","issue"],"title":"CampaignType","description":"Campaign type for proposal context."},"cbradio__CancelOrderRequest":{"properties":{"reason":{"type":"string","minLength":1,"title":"Reason"},"send_cancellation_notice":{"type":"boolean","title":"Send Cancellation Notice","default":false}},"type":"object","required":["reason"],"title":"CancelOrderRequest","description":"Request model for cancelling an order."},"cbradio__CancelOrderResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"cancelled_at":{"type":"string","format":"date-time","title":"Cancelled At"},"cancellation_email_sent":{"type":"boolean","title":"Cancellation Email Sent"}},"type":"object","required":["success","cancelled_at","cancellation_email_sent"],"title":"CancelOrderResponse","description":"Response after cancelling order."},"cbradio__ClassificationAdjustment":{"properties":{"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__src__api__models__rate__AdType"},{"type":"null"}],"description":"Candidate vs Issue advertising"},"rate_context":{"anyOf":[{"$ref":"#/components/schemas/cbradio__RateContext"},{"type":"null"}],"description":"In-window vs out-of-window"},"preemption":{"anyOf":[{"$ref":"#/components/schemas/cbradio__PreemptionStatus"},{"type":"null"}],"description":"Preemption status"}},"type":"object","title":"ClassificationAdjustment","description":"Adjust rate classification - for AI correction workflow."},"cbradio__ClassificationBreakdown":{"properties":{"count":{"type":"integer","title":"Count","default":0},"gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross","default":"0"},"commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission","default":"0"}},"type":"object","title":"ClassificationBreakdown","description":"Breakdown for a single classification category."},"cbradio__Client":{"properties":{"name":{"type":"string","title":"Name","description":"Client name (candidate or organization)"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL to client logo"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State abbreviation"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office","description":"Office being sought (Governor, US Senate, etc.)"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientType"},{"type":"null"}],"description":"Client type classification"},"party_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Affiliation","description":"Political party affiliation"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientStatus"},{"type":"null"}],"description":"Client status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Primary contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Primary contact phone"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website","description":"Client website URL"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name","id","created_at","updated_at"],"title":"Client","description":"Complete client model with database fields."},"cbradio__ClientApprovalRequest":{"properties":{"approved_by_name":{"type":"string","maxLength":255,"minLength":1,"title":"Approved By Name"},"approved_by_email":{"type":"string","format":"email","title":"Approved By Email"},"approved_by_title":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Approved By Title"},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes"}},"type":"object","required":["approved_by_name","approved_by_email"],"title":"ClientApprovalRequest","description":"Request model for client approval via public link."},"cbradio__ClientApprovalResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"approved_at":{"type":"string","format":"date-time","title":"Approved At"},"message":{"type":"string","title":"Message","default":"Proposal has been approved."},"proposal_number":{"type":"string","title":"Proposal Number"}},"type":"object","required":["success","approved_at","proposal_number"],"title":"ClientApprovalResponse","description":"Response model for client approval."},"cbradio__ClientApprovalStatus":{"type":"string","enum":["none","client_approved_pending_ops","ops_confirmed"],"title":"ClientApprovalStatus","description":"Client approval status for a proposal."},"cbradio__ClientCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Client name (candidate or organization)"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL to client logo"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State abbreviation"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office","description":"Office being sought (Governor, US Senate, etc.)"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientType"},{"type":"null"}],"description":"Client type classification"},"party_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Affiliation","description":"Political party affiliation"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientStatus"},{"type":"null"}],"description":"Client status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Primary contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Primary contact phone"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website","description":"Client website URL"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name"],"title":"ClientCreate","description":"Model for creating a new client."},"cbradio__ClientStatus":{"type":"string","enum":["active","inactive"],"title":"ClientStatus","description":"Client status."},"cbradio__ClientSummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientType"},{"type":"null"}]},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientStatus"},{"type":"null"}]},"total_buys":{"type":"integer","title":"Total Buys","default":0},"total_spend":{"type":"number","title":"Total Spend","default":0.0},"last_buy_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Last Buy Date"}},"type":"object","required":["id","name"],"title":"ClientSummary","description":"Lightweight client summary for list views."},"cbradio__ClientType":{"type":"string","enum":["candidate","pac","issue","other"],"title":"ClientType","description":"Client type classification."},"cbradio__ClientUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientType"},{"type":"null"}]},"party_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Affiliation"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientStatus"},{"type":"null"}]},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"ClientUpdate","description":"Model for updating a client (all fields optional)."},"cbradio__ClientWithRadioBuys":{"properties":{"name":{"type":"string","title":"Name","description":"Client name (candidate or organization)"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"URL to client logo"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State abbreviation"},"office":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Office","description":"Office being sought (Governor, US Senate, etc.)"},"type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientType"},{"type":"null"}],"description":"Client type classification"},"party_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Affiliation","description":"Political party affiliation"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientStatus"},{"type":"null"}],"description":"Client status"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email","description":"Primary contact email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone","description":"Primary contact phone"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website","description":"Client website URL"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"radio_buys":{"items":{"$ref":"#/components/schemas/cbradio__RadioBuySummary"},"type":"array","title":"Radio Buys","default":[]}},"type":"object","required":["name","id","created_at","updated_at"],"title":"ClientWithRadioBuys","description":"Client with associated radio buys."},"cbradio__CommentCreate":{"properties":{"body":{"type":"string","minLength":1,"title":"Body","description":"Comment body (markdown supported)"}},"type":"object","required":["body"],"title":"CommentCreate","description":"Request body for adding a comment.","example":{"body":"Thanks for reporting! I'll look into this."}},"cbradio__CommissionBreakdown":{"properties":{"in_network":{"$ref":"#/components/schemas/cbradio__ClassificationBreakdown"},"salem":{"$ref":"#/components/schemas/cbradio__ClassificationBreakdown"},"out_of_network":{"$ref":"#/components/schemas/cbradio__ClassificationBreakdown"},"unknown":{"$ref":"#/components/schemas/cbradio__ClassificationBreakdown"}},"type":"object","title":"CommissionBreakdown","description":"Commission breakdown by station classification."},"cbradio__CommissionRecipientCreate":{"properties":{"recipient_type":{"$ref":"#/components/schemas/cbradio__RecipientType"},"recipient_name":{"type":"string","maxLength":255,"minLength":1,"title":"Recipient Name"},"split_percentage":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Split Percentage"},"is_paid":{"type":"boolean","title":"Is Paid","default":false},"paid_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid Date"}},"type":"object","required":["recipient_type","recipient_name","split_percentage"],"title":"CommissionRecipientCreate","description":"Create a commission recipient."},"cbradio__CommissionRecipientUpdate":{"properties":{"recipient_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__RecipientType"},{"type":"null"}]},"recipient_name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Recipient Name"},"split_percentage":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Split Percentage"},"is_paid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Paid"},"paid_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid Date"}},"type":"object","title":"CommissionRecipientUpdate","description":"Update a commission recipient (all fields optional)."},"cbradio__CommissionRuleCreate":{"properties":{"recipient_type":{"$ref":"#/components/schemas/cbradio__RecipientType"},"recipient_name":{"type":"string","maxLength":255,"minLength":1,"title":"Recipient Name"},"rate_in_network_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate In Network Pct","default":"0"},"rate_non_rural_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate Non Rural Pct","default":"0"},"rate_salem_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate Salem Pct","default":"0"},"rate_total_gross_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate Total Gross Pct","default":"0"},"rate_rural_gross_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate Rural Gross Pct","default":"0"},"custom_basis_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Custom Basis Amount"},"hold_status":{"type":"string","title":"Hold Status","default":"none"},"hold_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hold Reason"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["recipient_type","recipient_name"],"title":"CommissionRuleCreate","description":"Create a commission rule for a recipient on a buy."},"cbradio__CommissionRuleSummary":{"properties":{"recipient_name":{"type":"string","title":"Recipient Name"},"recipient_type":{"type":"string","title":"Recipient Type"},"computed_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Computed Amount"},"hold_status":{"type":"string","title":"Hold Status","default":"none"}},"type":"object","required":["recipient_name","recipient_type"],"title":"CommissionRuleSummary","description":"Lightweight commission rule for list views."},"cbradio__CommissionRuleUpdate":{"properties":{"recipient_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__RecipientType"},{"type":"null"}]},"recipient_name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Recipient Name"},"rate_in_network_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rate In Network Pct"},"rate_non_rural_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rate Non Rural Pct"},"rate_salem_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rate Salem Pct"},"rate_total_gross_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rate Total Gross Pct"},"rate_rural_gross_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rate Rural Gross Pct"},"custom_basis_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Custom Basis Amount"},"hold_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hold Status"},"hold_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hold Reason"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"CommissionRuleUpdate","description":"Update a commission rule (all fields optional)."},"cbradio__CommissionSplit":{"properties":{"recipient_type":{"type":"string","pattern":"^(agency|consultant|station|other)$","title":"Recipient Type"},"recipient_name":{"type":"string","title":"Recipient Name"},"recipient_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Id"},"percentage":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Percentage"},"amount":{"type":"number","minimum":0.0,"title":"Amount"}},"type":"object","required":["recipient_type","recipient_name","percentage","amount"],"title":"CommissionSplit","description":"A single commission split entry."},"cbradio__CommissionSplitsUpdate":{"properties":{"commission_total":{"type":"number","minimum":0.0,"title":"Commission Total"},"splits":{"items":{"$ref":"#/components/schemas/cbradio__CommissionSplit"},"type":"array","title":"Splits"},"has_buyer_agency":{"type":"boolean","title":"Has Buyer Agency","default":false},"rebate_amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Rebate Amount"}},"type":"object","required":["commission_total"],"title":"CommissionSplitsUpdate","description":"Request model for updating commission splits."},"cbradio__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbradio__ConfirmationEvent":{"properties":{"id":{"type":"string","title":"Id"},"event_type":{"$ref":"#/components/schemas/cbradio__ConfirmationEventType"},"event_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Event Data"},"ip_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip Address"},"user_agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","required":["id","event_type","created_at"],"title":"ConfirmationEvent","description":"Single audit event for order confirmation."},"cbradio__ConfirmationEventType":{"type":"string","enum":["generated","previewed","approved_for_send","email_sent","email_delivered","email_bounced","web_viewed","web_confirmed","email_confirmed","manual_confirmed","make_good_requested","cancelled","modified"],"title":"ConfirmationEventType","description":"Types of confirmation events for audit trail."},"cbradio__ConfirmationVia":{"type":"string","enum":["web","email","manual"],"title":"ConfirmationVia","description":"How the order was confirmed."},"cbradio__Consultant":{"properties":{"name":{"type":"string","title":"Name","description":"Consultant name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ConsultantStatus"},{"type":"null"}],"description":"Consultant status"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Consultant email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Consultant phone"},"specialization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Specialization","description":"Area of specialization"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name","id","created_at","updated_at"],"title":"Consultant","description":"Complete consultant model with database fields."},"cbradio__ConsultantCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Consultant name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ConsultantStatus"},{"type":"null"}],"description":"Consultant status"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Consultant email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Consultant phone"},"specialization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Specialization","description":"Area of specialization"}},"type":"object","required":["name"],"title":"ConsultantCreate","description":"Model for creating a new consultant."},"cbradio__ConsultantStatus":{"type":"string","enum":["active","inactive"],"title":"ConsultantStatus","description":"Consultant status."},"cbradio__ConsultantSummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ConsultantStatus"},{"type":"null"}]}},"type":"object","required":["id","name"],"title":"ConsultantSummary","description":"Lightweight consultant summary for list views."},"cbradio__ConsultantUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ConsultantStatus"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"specialization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Specialization"}},"type":"object","title":"ConsultantUpdate","description":"Model for updating a consultant (all fields optional)."},"cbradio__ConsultantWithRelations":{"properties":{"name":{"type":"string","title":"Name","description":"Consultant name"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ConsultantStatus"},{"type":"null"}],"description":"Consultant status"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Consultant email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Consultant phone"},"specialization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Specialization","description":"Area of specialization"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"clients":{"items":{"$ref":"#/components/schemas/cbradio__ClientSummary"},"type":"array","title":"Clients","default":[]},"radio_buys":{"items":{"$ref":"#/components/schemas/cbradio__RadioBuySummary"},"type":"array","title":"Radio Buys","default":[]}},"type":"object","required":["name","id","created_at","updated_at"],"title":"ConsultantWithRelations","description":"Consultant with associated clients and radio buys."},"cbradio__ContactFormRequest":{"properties":{"name":{"type":"string","title":"Name"},"email":{"type":"string","title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"company":{"type":"string","title":"Company"},"category":{"type":"string","enum":["advertiser","station_owner","consultant","general","partnership","support"],"title":"Category"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","default":"ruralamfm.com"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"}},"type":"object","required":["name","email","company","category"],"title":"ContactFormRequest"},"cbradio__ContactFormResponse":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status","default":"received"},"message":{"type":"string","title":"Message","default":"Thank you for your inquiry. We'll be in touch soon."}},"type":"object","required":["id"],"title":"ContactFormResponse"},"cbradio__ContactLog":{"properties":{"id":{"type":"string","title":"Id","description":"Unique identifier"},"station_callsign":{"type":"string","title":"Station Callsign","description":"Station callsign"},"contact_type":{"$ref":"#/components/schemas/cbradio__ContactType","description":"Type of contact"},"outcome":{"$ref":"#/components/schemas/cbradio__ContactOutcome","description":"Contact outcome"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Contact notes"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name","description":"Person contacted"},"contact_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Role","description":"Their role at station"},"follow_up_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Follow Up Date","description":"Scheduled follow-up"},"logged_by_user_id":{"type":"string","title":"Logged By User Id","description":"User who logged contact"},"logged_by_name":{"type":"string","title":"Logged By Name","description":"User display name"},"contacted_at":{"type":"string","format":"date-time","title":"Contacted At","description":"When contact occurred"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"}},"type":"object","required":["id","station_callsign","contact_type","outcome","logged_by_user_id","logged_by_name","contacted_at","created_at"],"title":"ContactLog","description":"Contact log entry."},"cbradio__ContactLogCreate":{"properties":{"contact_type":{"$ref":"#/components/schemas/cbradio__ContactType","description":"Type of contact"},"outcome":{"$ref":"#/components/schemas/cbradio__ContactOutcome","description":"Contact outcome"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Contact notes"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name","description":"Person contacted"},"contact_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Role","description":"Their role at station"},"follow_up_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Follow Up Date","description":"Scheduled follow-up date"},"contacted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Contacted At","description":"When contact occurred (defaults to now)"}},"type":"object","required":["contact_type","outcome"],"title":"ContactLogCreate","description":"Request model for logging a contact."},"cbradio__ContactLogList":{"properties":{"contacts":{"items":{"$ref":"#/components/schemas/cbradio__ContactLog"},"type":"array","title":"Contacts"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["contacts","total"],"title":"ContactLogList","description":"Paginated list of contact logs."},"cbradio__ContactOutcome":{"type":"string","enum":["interested","meeting_scheduled","agreement_sent","agreement_signed","callback_requested","info_requested","no_answer","left_voicemail","not_interested","bad_timing","competitor","do_not_contact"],"title":"ContactOutcome","description":"Standardized outcomes for contact logging."},"cbradio__ContactType":{"type":"string","enum":["phone","email","in_person","other"],"title":"ContactType","description":"Types of contact interactions."},"cbradio__ContoursBatchRequest":{"properties":{"callsigns":{"items":{"type":"string"},"type":"array","title":"Callsigns","description":"Station callsigns (suffixed, e.g. 'WOOD-AM'). Deduped server-side."},"contour_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contour Type","description":"Contour type: 'day', 'night', or 'service' (AM only). Defaults to 'day' for AM stations; ignored for FM."}},"type":"object","required":["callsigns"],"title":"ContoursBatchRequest","description":"Request body for POST /geo/contours (cbradio-ui#139)."},"cbradio__ConvertProposal":{"properties":{"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"ConvertProposal","description":"Request to convert proposal to radio buy."},"cbradio__CorrectionStatus":{"type":"string","enum":["pending_review","verified","rejected"],"title":"CorrectionStatus","description":"Status of a rate correction in the review workflow."},"cbradio__CountyContribution":{"properties":{"county_fips":{"type":"string","title":"County Fips","description":"5-digit FIPS code.","examples":["01043"]},"county_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County Name","examples":["Cullman"]},"county_in_district_pct":{"type":"number","title":"County In District Pct","description":"What % of this county lies inside the district. Used as the weight on county_voter_data when aggregating district totals.","examples":[99.99]},"registered_contributed":{"type":"integer","title":"Registered Contributed","description":"Registered voters this county contributes to the district (county_voter_data * county_in_district_pct / 100).","examples":[49000]}},"type":"object","required":["county_fips","county_in_district_pct","registered_contributed"],"title":"CountyContribution","description":"Per-county breakdown of a sub-state district's voter aggregation."},"cbradio__CountyCoverage":{"properties":{"county_fips":{"type":"string","title":"County Fips","description":"5-digit FIPS code for the county.","examples":["26065","26045"]},"county_name":{"type":"string","title":"County Name","description":"County name from voter data.","examples":["Ingham","Eaton"]},"state_code":{"type":"string","title":"State Code","description":"Two-letter state code.","examples":["MI","OH"]},"coverage_pct":{"type":"number","title":"Coverage Pct","description":"Percentage of the county covered by the station's contour (0-100).","examples":[100.0,85.5,3.2]},"registered_voters":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Registered Voters","description":"i360 registered voter count for this county. Null if no voter data.","examples":[245000]},"swing_likely_voters":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Swing Likely Voters","description":"Swing voters (affinity 40-55, propensity 1-4) in this county.","examples":[1200]},"base_likely_voters":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Base Likely Voters","description":"Base voters (affinity 65+, propensity 1-4) in this county.","examples":[3400]}},"type":"object","required":["county_fips","county_name","state_code","coverage_pct"],"title":"CountyCoverage","description":"A county within a station's coverage area."},"cbradio__CountyReportRequest":{"properties":{"state":{"type":"string","maxLength":2,"minLength":2,"title":"State","description":"Two-letter US state abbreviation. Case-insensitive (auto-uppercased).","examples":["NH","FL","TX"]},"swing_affinity_min":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Swing Affinity Min","description":"Minimum political affinity score for swing voters. 0=strong opposition, 100=strong support.","default":40,"examples":[35,40,45]},"swing_affinity_max":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Swing Affinity Max","description":"Maximum political affinity score for swing voters.","default":55,"examples":[50,55,60]},"base_affinity_min":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Base Affinity Min","description":"Minimum political affinity score for base voters. Typically 65+ for strong partisans.","default":65,"examples":[60,65,70]},"voting_propensity_min":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Voting Propensity Min","description":"Minimum voting propensity score. 1=most likely to vote, 4=moderately likely.","default":1,"examples":[1,2]},"voting_propensity_max":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Voting Propensity Max","description":"Maximum voting propensity score. Higher values include less frequent voters.","default":4,"examples":[4,5,6]}},"type":"object","required":["state"],"title":"CountyReportRequest","description":"Request parameters for a statewide county-level voter propensity report.\n\nGenerates a CSV-backed report with one row per county showing registered voter\ncounts plus swing and base universe sizes. Defaults match standard campaign\ntargeting criteria (swing: affinity 40-55, base: affinity 65+, propensity 1-4).","example":{"base_affinity_min":65,"state":"NH","swing_affinity_max":55,"swing_affinity_min":40,"voting_propensity_max":4,"voting_propensity_min":1}},"cbradio__CountyReportResponse":{"properties":{"state":{"type":"string","title":"State","description":"Two-letter state code that was queried.","examples":["NH"]},"county_count":{"type":"integer","title":"County Count","description":"Number of counties in the report.","examples":[11,67]},"counties":{"items":{"$ref":"#/components/schemas/cbradio__CountyRow"},"type":"array","title":"Counties","description":"One row per county, ordered as returned by i360."}},"type":"object","required":["state","county_count","counties"],"title":"CountyReportResponse","description":"Statewide county-level voter propensity report."},"cbradio__CountyRow":{"properties":{"county":{"type":"string","title":"County","description":"County name as returned by i360 (e.g. 'ALACHUA', 'Belknap').","examples":["Belknap","ALACHUA","Hillsborough"]},"registered_voters":{"type":"integer","title":"Registered Voters","description":"All individuals in i360 database for this county. Note: this is total i360 records, not official voter registration count.","examples":[62957,371069]},"swing_likely_voters":{"type":"integer","title":"Swing Likely Voters","description":"Individuals matching swing affinity + propensity criteria.","examples":[101,374]},"swing_likely_hhs":{"type":"integer","title":"Swing Likely Hhs","description":"Unique households (residences) for swing voters. Use for direct mail piece counts.","examples":[95,370]},"base_likely_voters":{"type":"integer","title":"Base Likely Voters","description":"Individuals matching base affinity + propensity criteria.","examples":[198,573]},"base_likely_hhs":{"type":"integer","title":"Base Likely Hhs","description":"Unique households for base voters.","examples":[190,563]}},"type":"object","required":["county","registered_voters","swing_likely_voters","swing_likely_hhs","base_likely_voters","base_likely_hhs"],"title":"CountyRow","description":"One county's voter universe data from the i360 report."},"cbradio__CreateBatchRequest":{"properties":{"source_type":{"type":"string","pattern":"^(buy_tracker|payment_sheet)$","title":"Source Type","description":"Source type: buy_tracker or payment_sheet"},"source_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Source Name"},"records":{"items":{"$ref":"#/components/schemas/cbradio__ImportRecord"},"type":"array","minItems":1,"title":"Records"}},"type":"object","required":["source_type","records"],"title":"CreateBatchRequest","description":"Request model for creating an import batch."},"cbradio__CreateShareLinkRequest":{"properties":{"expires_in_days":{"type":"integer","maximum":90.0,"minimum":1.0,"title":"Expires In Days","description":"Number of days until the link expires (1-90)","default":30}},"type":"object","title":"CreateShareLinkRequest","description":"Request model for creating a share link."},"cbradio__CycleCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"start_year":{"type":"integer","maximum":2040.0,"minimum":2020.0,"title":"Start Year"},"end_year":{"type":"integer","maximum":2040.0,"minimum":2020.0,"title":"End Year"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"type":"object","required":["name","start_year","end_year"],"title":"CycleCreate","description":"Create a political cycle."},"cbradio__CycleUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Name"},"start_year":{"anyOf":[{"type":"integer","maximum":2040.0,"minimum":2020.0},{"type":"null"}],"title":"Start Year"},"end_year":{"anyOf":[{"type":"integer","maximum":2040.0,"minimum":2020.0},{"type":"null"}],"title":"End Year"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"CycleUpdate","description":"Update a political cycle (all fields optional)."},"cbradio__DashboardResponse":{"properties":{"summary":{"$ref":"#/components/schemas/cbradio__DashboardSummary"},"overdue_orders":{"items":{"$ref":"#/components/schemas/cbradio__OverdueOrder"},"type":"array","title":"Overdue Orders","default":[]},"pending_make_goods":{"items":{"$ref":"#/components/schemas/cbradio__PendingMakeGood"},"type":"array","title":"Pending Make Goods","default":[]}},"type":"object","required":["summary"],"title":"DashboardResponse","description":"Dashboard summary response."},"cbradio__DashboardSummary":{"properties":{"pending_review":{"type":"integer","title":"Pending Review","default":0},"ready_to_send":{"type":"integer","title":"Ready To Send","default":0},"sent_awaiting_confirmation":{"type":"integer","title":"Sent Awaiting Confirmation","default":0},"confirmed":{"type":"integer","title":"Confirmed","default":0},"make_good_requested":{"type":"integer","title":"Make Good Requested","default":0},"cancelled":{"type":"integer","title":"Cancelled","default":0}},"type":"object","title":"DashboardSummary","description":"Summary counts by status."},"cbradio__DataFreshness":{"properties":{"fetched_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Fetched At","description":"Earliest fetched_at across contributing counties.","examples":["2026-02-17T10:37:24Z"]},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Earliest expires_at across contributing counties.","examples":["2026-05-18T10:37:24Z"]},"is_stale":{"type":"boolean","title":"Is Stale","description":"True when expires_at is in the past — voter data has aged out of its 90-day TTL.","default":false}},"type":"object","title":"DataFreshness","description":"cbradio#252: freshness window for the i360 voter data backing this response.\n\n`fetched_at` / `expires_at` are the OLDEST values across the counties that\ncontributed (most-pessimistic). `is_stale=True` means `expires_at` is in\nthe past — the underlying `county_voter_data` rows are past their 90-day\nTTL and should be re-fetched. cbradio still returns the data (no hard\nfailure) so consumers can decide how to handle staleness."},"cbradio__DayPattern":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun","m-f","sat-sun","m-sat","all"],"title":"DayPattern","description":"Day of week patterns for time slots."},"cbradio__DeleteVersionResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"deleted_version_id":{"type":"string","title":"Deleted Version Id"},"deleted_version_number":{"type":"integer","title":"Deleted Version Number"},"remaining_versions":{"type":"integer","title":"Remaining Versions"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","deleted_version_id","deleted_version_number","remaining_versions","message"],"title":"DeleteVersionResponse","description":"Response for deleting a version."},"cbradio__DistrictVoterResponse":{"properties":{"district_id":{"type":"string","title":"District Id","description":"FIPS code for the district.","examples":["26065"]},"district_type":{"type":"string","title":"District Type","description":"Type of district.","examples":["county"]},"county_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County Name","description":"County name.","examples":["Ingham"]},"state_code":{"type":"string","title":"State Code","description":"Two-letter state code.","examples":["MI"]},"voter_counts":{"anyOf":[{"$ref":"#/components/schemas/cbradio__VoterCounts"},{"type":"null"}],"description":"Raw voter counts for this district (unweighted)."},"data_freshness":{"$ref":"#/components/schemas/cbradio__DataFreshness","description":"Freshness window for the i360 voter data (cbradio#252)."},"station_count":{"type":"integer","title":"Station Count","description":"Number of stations covering this district.","examples":[12]},"stations":{"items":{"$ref":"#/components/schemas/cbradio__StationCoverage"},"type":"array","title":"Stations","description":"Stations covering this district, ordered by coverage_pct DESC."}},"type":"object","required":["district_id","district_type","state_code","station_count","stations"],"title":"DistrictVoterResponse","description":"Voter data for a district (county) with all covering stations."},"cbradio__DistrictVoterTotalsResponse":{"properties":{"district_id":{"type":"string","title":"District Id","examples":["AL-SD-004"]},"district_type":{"type":"string","title":"District Type","description":"One of 'county', 'state_senate', 'state_house', 'congressional'.","examples":["state_senate"]},"state_code":{"type":"string","title":"State Code","examples":["AL"]},"total_registered_voters":{"type":"integer","title":"Total Registered Voters","examples":[137000]},"swing_likely_voters":{"type":"integer","title":"Swing Likely Voters","examples":[6800]},"base_likely_voters":{"type":"integer","title":"Base Likely Voters","examples":[32000]},"estimated_listeners":{"type":"integer","title":"Estimated Listeners","description":"total_registered_voters * 0.15 (industry-standard radio penetration rate).","examples":[20550]},"county_count":{"type":"integer","title":"County Count","description":"Number of constituent counties contributing to totals."},"data_freshness":{"$ref":"#/components/schemas/cbradio__DataFreshness","description":"Freshness window for the i360 voter data (cbradio#252)."},"components":{"items":{"$ref":"#/components/schemas/cbradio__CountyContribution"},"type":"array","title":"Components","description":"Per-county breakdown ordered by intersection_pct DESC."}},"type":"object","required":["district_id","district_type","state_code","total_registered_voters","swing_likely_voters","base_likely_voters","estimated_listeners","county_count"],"title":"DistrictVoterTotalsResponse","description":"Aggregated voter totals across a district's constituent counties.\n\nPowers the in-district CPE denominator (cbradio#228). For ``county`` type,\nreturns the row from ``county_voter_data`` directly. For state-leg and\ncongressional types, sums county_voter_data weighted by what fraction of\neach county lies inside the district (precomputed in\ncbdistricts.intersections.duckdb)."},"cbradio__DocumentCreate":{"properties":{"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"callsign":{"type":"string","title":"Callsign"},"document_type":{"type":"string","title":"Document Type","default":"rate_card"},"document_format":{"type":"string","title":"Document Format"},"rate_card_type":{"type":"string","title":"Rate Card Type","default":"political"},"year":{"type":"integer","title":"Year","default":2026},"filename":{"type":"string","title":"Filename"},"file_path":{"type":"string","title":"File Path"},"file_size_bytes":{"type":"integer","title":"File Size Bytes"},"source":{"type":"string","title":"Source","default":"airtable_2026"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"}},"type":"object","required":["callsign","document_format","filename","file_path","file_size_bytes"],"title":"DocumentCreate","description":"A single document to create."},"cbradio__DocumentCreateRequest":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/cbradio__DocumentCreate"},"type":"array","title":"Documents"}},"type":"object","required":["documents"],"title":"DocumentCreateRequest","description":"Batch document creation request."},"cbradio__DocumentCreateResponse":{"properties":{"created":{"type":"integer","title":"Created"},"ids":{"items":{"type":"string"},"type":"array","title":"Ids"}},"type":"object","required":["created","ids"],"title":"DocumentCreateResponse","description":"Response for batch document creation."},"cbradio__DocumentType":{"type":"string","enum":["agreement_pdf","rate_card","addendum","correspondence","other"],"title":"DocumentType","description":"Type of agreement document."},"cbradio__DocumentUploadResponse":{"properties":{"id":{"type":"string","title":"Id"},"document_type":{"$ref":"#/components/schemas/cbradio__DocumentType"},"file_name":{"type":"string","title":"File Name"},"file_url":{"type":"string","title":"File Url"},"file_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size"},"mime_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime Type"},"uploaded_at":{"type":"string","format":"date-time","title":"Uploaded At"}},"type":"object","required":["id","document_type","file_name","file_url","uploaded_at"],"title":"DocumentUploadResponse","description":"Response after uploading a document."},"cbradio__ElectionType":{"type":"string","enum":["primary","general"],"title":"ElectionType","description":"Election type for radio buys."},"cbradio__EstimatedListeners":{"properties":{"swing":{"type":"integer","title":"Swing","description":"Estimated swing voter listeners (voters * 0.15 penetration rate).","examples":[675]},"base":{"type":"integer","title":"Base","description":"Estimated base voter listeners.","examples":[1335]},"total":{"type":"integer","title":"Total","description":"Estimated total registered voter listeners.","examples":[37500]}},"type":"object","required":["swing","base","total"],"title":"EstimatedListeners","description":"Estimated radio listeners derived from voter counts."},"cbradio__ExitReason":{"type":"string","enum":["non_performance","station_request","rate_dispute","coverage_change","compliance_issue","other"],"title":"ExitReason","description":"Reason for network exit request."},"cbradio__ExitRequestCreate":{"properties":{"station_callsign":{"type":"string","maxLength":20,"minLength":1,"title":"Station Callsign"},"reason":{"$ref":"#/components/schemas/cbradio__ExitReason"},"reason_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason Details","description":"Required if reason is 'other'"},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"}},"type":"object","required":["station_callsign","reason"],"title":"ExitRequestCreate","description":"Request model for creating exit request."},"cbradio__ExitRequestListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/cbradio__ExitRequestResponse"},"type":"array","title":"Data"},"pagination":{"additionalProperties":true,"type":"object","title":"Pagination"}},"type":"object","required":["data","pagination"],"title":"ExitRequestListResponse","description":"Paginated list of exit requests."},"cbradio__ExitRequestResponse":{"properties":{"id":{"type":"string","title":"Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"station":{"anyOf":[{"$ref":"#/components/schemas/cbradio__src__api__models__agreement__StationSummary"},{"type":"null"}]},"agreement_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agreement Id"},"requested_by":{"anyOf":[{"$ref":"#/components/schemas/cbradio__UserSummary"},{"type":"null"}]},"requested_at":{"type":"string","format":"date-time","title":"Requested At"},"reason":{"$ref":"#/components/schemas/cbradio__ExitReason"},"reason_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason Details"},"status":{"$ref":"#/components/schemas/cbradio__ExitStatus"},"reviewed_by":{"anyOf":[{"$ref":"#/components/schemas/cbradio__UserSummary"},{"type":"null"}]},"reviewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reviewed At"},"review_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Notes"},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"days_pending":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Pending"}},"type":"object","required":["id","station_callsign","requested_at","reason","status"],"title":"ExitRequestResponse","description":"Response model for exit request."},"cbradio__ExitRequestReview":{"properties":{"decision":{"type":"string","pattern":"^(approved|rejected)$","title":"Decision"},"review_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Notes"},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date","description":"Override effective date if approved"}},"type":"object","required":["decision"],"title":"ExitRequestReview","description":"Request model for reviewing exit request."},"cbradio__ExitStatus":{"type":"string","enum":["pending","approved","rejected","cancelled"],"title":"ExitStatus","description":"Status of exit request."},"cbradio__ExportFormat":{"type":"string","enum":["csv","xlsx"],"title":"ExportFormat","description":"Export file format."},"cbradio__ExtractionResultRequest":{"properties":{"ocr_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ocr Text"},"status":{"type":"string","title":"Status"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"},"rates":{"items":{"$ref":"#/components/schemas/cbradio__RateData"},"type":"array","title":"Rates"},"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"callsign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callsign"},"station_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station State"},"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"processing_time_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Processing Time Seconds"},"model_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Used"}},"type":"object","required":["status"],"title":"ExtractionResultRequest","description":"Compound extraction result — OCR text + rates + status in one call."},"cbradio__ExtractionResultResponse":{"properties":{"status":{"type":"string","title":"Status"},"rates_stored":{"type":"integer","title":"Rates Stored"}},"type":"object","required":["status","rates_stored"],"title":"ExtractionResultResponse","description":"Response for extraction result submission."},"cbradio__FCCStation":{"properties":{"facility_id":{"type":"string","title":"Facility Id"},"callsign":{"type":"string","title":"Callsign"},"normalized_callsign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Normalized Callsign"},"service_code":{"type":"string","title":"Service Code"},"frequency":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Frequency"},"channel":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Channel"},"facility_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Facility Status"},"community_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Community City"},"community_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Community State"},"network_affiliation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Network Affiliation"},"nielsen_dma_rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Nielsen Dma Rank"},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"},"licensee_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Licensee Name"},"our_station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Our Station Id"},"is_matched":{"type":"boolean","title":"Is Matched","default":false}},"type":"object","required":["facility_id","callsign","service_code"],"title":"FCCStation","description":"FCC Station from LMS database.","example":{"callsign":"WABC","community_city":"NEW YORK","community_state":"NY","facility_id":"12345","frequency":770.0,"is_matched":true,"latitude":40.7128,"longitude":-74.006,"service_code":"AM"}},"cbradio__FCCStatsResponse":{"properties":{"total_fcc_stations":{"type":"integer","title":"Total Fcc Stations"},"am_stations":{"type":"integer","title":"Am Stations"},"fm_stations":{"type":"integer","title":"Fm Stations"},"matched_to_ours":{"type":"integer","title":"Matched To Ours"},"unmatched_potential_partners":{"type":"integer","title":"Unmatched Potential Partners"},"our_stations_with_fcc_id":{"type":"integer","title":"Our Stations With Fcc Id"},"our_stations_total":{"type":"integer","title":"Our Stations Total"},"fcc_coverage_percent":{"type":"number","title":"Fcc Coverage Percent"}},"type":"object","required":["total_fcc_stations","am_stations","fm_stations","matched_to_ours","unmatched_potential_partners","our_stations_with_fcc_id","our_stations_total","fcc_coverage_percent"],"title":"FCCStatsResponse","description":"FCC import statistics."},"cbradio__FormatSummary":{"properties":{"nielsen_format":{"type":"string","title":"Nielsen Format","description":"Nielsen format name.","examples":["Country","AC","News/Talk"]},"station_count":{"type":"integer","title":"Station Count","description":"Number of distinct stations with this format.","examples":[453,236]},"market_count":{"type":"integer","title":"Market Count","description":"Number of distinct markets where this format appears.","examples":[233,191]},"avg_latest_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Latest Rating","description":"Average of rating_4 across all stations with this format. Null if no ratings.","examples":[4.6,6.4]}},"type":"object","required":["nielsen_format","station_count","market_count"],"title":"FormatSummary","description":"Aggregate statistics for a single Nielsen format across all markets."},"cbradio__GenerateDefaultsResponse":{"properties":{"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"placements_created":{"type":"integer","title":"Placements Created"},"source":{"type":"string","title":"Source","description":"Source of placement data: 'proposal_line_items' or 'radio_buy_stations'"}},"type":"object","required":["radio_buy_id","placements_created","source"],"title":"GenerateDefaultsResponse","description":"Response from generating default placements."},"cbradio__GeneratePDFResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"pdf_url":{"type":"string","title":"Pdf Url"}},"type":"object","required":["success","pdf_url"],"title":"GeneratePDFResponse","description":"Response after generating PDF."},"cbradio__GenerateProposalRequest":{"properties":{"name":{"type":"string","minLength":1,"title":"Name","description":"Proposal name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"target_states":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Target States","description":"States to target (2-letter codes)"},"target_dmas":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Dmas","description":"Optional DMA filters"},"ad_type":{"$ref":"#/components/schemas/cbradio__src__api__models__proposal__AdType","description":"Type of advertising","default":"candidate"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date","description":"Election day. With `election_type`, determines the lowest-unit-charge window (47 U.S.C. 315(b)): 45 days before a primary, 60 before a general or special. Omit and rate selection will not rank on the window at all."},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type","description":"primary | general | special — sets the LUR window length"},"rate_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Context","description":"in_window | out_of_window — the caller's own statement of which side of the lowest-unit-charge boundary this buy sits on. Wins over anything derived from `election_date`: a buyer who picks 'candidate, in window' has told us directly, and that is better evidence than arithmetic on a date. Omit both and rate selection will not rank on the window at all (cbradio#376)."},"spot_duration":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Duration","description":"Spot length in seconds","default":60},"preemption":{"$ref":"#/components/schemas/cbradio__PreemptionRequest","description":"Preemption preference","default":"non_preemptible"},"spots_per_week_per_station":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Spots Per Week Per Station","description":"Spots per week per station","default":10},"max_budget":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Max Budget","description":"Maximum total gross budget"},"max_stations":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Max Stations","description":"Maximum stations to include","default":50},"max_rate_per_spot":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Max Rate Per Spot","description":"Max rate per spot"},"prefer_lowest_rates":{"type":"boolean","title":"Prefer Lowest Rates","description":"Sort by lowest rates first","default":true},"rural_only":{"type":"boolean","title":"Rural Only","description":"Only include rural stations","default":false},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name","target_states","start_date","end_date"],"title":"GenerateProposalRequest","description":"Request to auto-generate a proposal based on criteria."},"cbradio__GenerateProposalResponse":{"properties":{"proposal_id":{"type":"string","title":"Proposal Id"},"proposal_number":{"type":"string","title":"Proposal Number"},"stations_matched":{"type":"integer","title":"Stations Matched"},"stations_included":{"type":"integer","title":"Stations Included"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Net"},"total_spots":{"type":"integer","title":"Total Spots"},"budget_remaining":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Budget Remaining"},"message":{"type":"string","title":"Message"},"window_split":{"anyOf":[{"$ref":"#/components/schemas/cbradio__WindowSplitOffer"},{"type":"null"}],"description":"Present only when the flight crosses the lowest-unit-charge boundary. The proposal was generated WITHOUT ranking the window; each segment should be generated separately to price correctly."}},"type":"object","required":["proposal_id","proposal_number","stations_matched","stations_included","total_gross","total_net","total_spots","message"],"title":"GenerateProposalResponse","description":"Response from proposal generation."},"cbradio__GenerateResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"order_packets":{"items":{"$ref":"#/components/schemas/cbradio__OrderPacketResponse"},"type":"array","title":"Order Packets"},"total_packets":{"type":"integer","title":"Total Packets"}},"type":"object","required":["success","order_packets","total_packets"],"title":"GenerateResponse","description":"Response after generating order packets."},"cbradio__GeographyDiscoveryRequest":{"properties":{"states":{"items":{"type":"string"},"type":"array","minItems":1,"title":"States","description":"Target 2-letter state codes (e.g. ['MO']).","examples":[["MO"]]},"include_border":{"type":"boolean","title":"Include Border","description":"Include border-market stations by DMA or signal contour, not just licensed state. False reproduces today's licensed-state-only behavior.","default":true},"min_contour_pct":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Min Contour Pct","description":"Minimum % of a station's signal contour that must fall inside a target state for in_contour inclusion (cbdistricts pct_of_a).","default":25.0},"exclude_non_commercial":{"type":"boolean","title":"Exclude Non Commercial","description":"Exclude non-commercial (LPFM/NCE) stations.","default":true}},"type":"object","required":["states"],"title":"GeographyDiscoveryRequest","description":"Geography-aware station discovery request (cbradio#290)."},"cbradio__GeographyResponse":{"properties":{"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State filter applied.","examples":["MI","NH"]},"market":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market","description":"Market filter applied.","examples":["Detroit MI","Manchester"]},"total_markets":{"type":"integer","title":"Total Markets","description":"Distinct markets in the result.","examples":[8,1]},"total_stations":{"type":"integer","title":"Total Stations","description":"Total stations returned.","examples":[142,24]},"stations":{"items":{"$ref":"#/components/schemas/cbradio__NielsenRating"},"type":"array","title":"Stations","description":"Station ratings ordered by market rank then station rank."}},"type":"object","required":["total_markets","total_stations","stations"],"title":"GeographyResponse","description":"Bulk Nielsen ratings for stations in a state or market."},"cbradio__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbradio__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbradio__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbradio__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbradio__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbradio__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbradio__ImportRecord":{"properties":{"source_row":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Source Row"},"raw_data":{"additionalProperties":true,"type":"object","title":"Raw Data"}},"type":"object","required":["raw_data"],"title":"ImportRecord","description":"A single record to import."},"cbradio__Issue":{"properties":{"number":{"type":"integer","title":"Number"},"title":{"type":"string","title":"Title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"state":{"type":"string","title":"State"},"stateReason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Statereason"},"author":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Author"},"assignees":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Assignees"},"labels":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Labels"},"milestone":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Milestone"},"isPinned":{"type":"boolean","title":"Ispinned","default":false},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat"},"closedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Closedat"},"closed":{"type":"boolean","title":"Closed","default":false},"comments":{"type":"integer","title":"Comments","default":0},"url":{"type":"string","title":"Url"},"project":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project"}},"type":"object","required":["number","title","state","url"],"title":"Issue","description":"GitHub issue response."},"cbradio__IssueClose":{"properties":{"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment","description":"Closing comment"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Reason: completed or not_planned"}},"type":"object","title":"IssueClose","description":"Request body for closing an issue.","example":{"comment":"Fixed in commit abc123","reason":"completed"}},"cbradio__IssueCreate":{"properties":{"title":{"type":"string","minLength":1,"title":"Title","description":"Issue title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Issue body (markdown supported)"},"labels":{"items":{"type":"string"},"type":"array","title":"Labels","description":"Labels to apply"},"assignees":{"items":{"type":"string"},"type":"array","title":"Assignees","description":"Assignee logins (use @me for self)"},"milestone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Milestone","description":"Milestone name"}},"type":"object","required":["title"],"title":"IssueCreate","description":"Request body for creating an issue.","example":{"assignees":["isenbek"],"body":"## Description\nDetailed bug report...","labels":["bug","priority:high"],"milestone":"v1.5.0","title":"Bug: Rate extraction fails on PDF"}},"cbradio__IssueListResponse":{"properties":{"project":{"type":"string","title":"Project"},"total":{"type":"integer","title":"Total"},"issues":{"items":{"$ref":"#/components/schemas/cbradio__Issue"},"type":"array","title":"Issues"}},"type":"object","required":["project","total","issues"],"title":"IssueListResponse","description":"Response for listing issues."},"cbradio__IssueLock":{"properties":{"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Lock reason: off_topic, resolved, spam, too_heated"}},"type":"object","title":"IssueLock","description":"Request body for locking an issue.","example":{"reason":"resolved"}},"cbradio__IssueReopen":{"properties":{"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment","description":"Reopening comment"}},"type":"object","title":"IssueReopen","description":"Request body for reopening an issue.","example":{"comment":"Reopening - regression found in production"}},"cbradio__IssueUpdate":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"New title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"New body"},"add_labels":{"items":{"type":"string"},"type":"array","title":"Add Labels","description":"Labels to add"},"remove_labels":{"items":{"type":"string"},"type":"array","title":"Remove Labels","description":"Labels to remove"},"add_assignees":{"items":{"type":"string"},"type":"array","title":"Add Assignees","description":"Assignees to add"},"remove_assignees":{"items":{"type":"string"},"type":"array","title":"Remove Assignees","description":"Assignees to remove"},"milestone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Milestone","description":"Milestone name to set"},"remove_milestone":{"type":"boolean","title":"Remove Milestone","description":"Remove milestone association","default":false}},"type":"object","title":"IssueUpdate","description":"Request body for updating an issue.","example":{"add_assignees":["@me"],"add_labels":["needs-review"],"remove_labels":["wip"],"title":"Updated title"}},"cbradio__LegacyBuyImport":{"properties":{"radio_buy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"},"callsign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callsign"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"client_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Name"},"gross_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount"},"net_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount"},"ad_production":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Ad Production"},"is_rural":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Rural"},"rural_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Amount"},"non_rural_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Amount"},"is_paid":{"type":"boolean","title":"Is Paid","default":false},"paid_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid Date"},"political_cycle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Political Cycle"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"commission_recipients":{"items":{"$ref":"#/components/schemas/cbradio__CommissionRecipientCreate"},"type":"array","title":"Commission Recipients"}},"type":"object","title":"LegacyBuyImport","description":"A single historical buy for legacy import."},"cbradio__LineItem":{"properties":{"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"spots_per_week":{"type":"integer","minimum":1.0,"title":"Spots Per Week"},"weeks":{"type":"integer","minimum":1.0,"title":"Weeks"},"gross_rate_per_spot":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Rate Per Spot"},"net_rate_per_spot":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate Per Spot"},"rate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id"},"proposal_id":{"type":"string","title":"Proposal Id"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Net"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"item_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Item Source","default":"manual"},"ai_suggestion_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Suggestion Id"},"action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action"},"modifications":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Modifications"},"decision_timestamp":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Decision Timestamp"},"ai_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ai Confidence"},"ai_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Reason"},"classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification"},"classification_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification Source"}},"type":"object","required":["station_callsign","spots_per_week","weeks","gross_rate_per_spot","id","proposal_id","total_gross","total_net","created_at","updated_at"],"title":"LineItem","description":"Full line item with computed fields."},"cbradio__LineItemCreate":{"properties":{"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"spots_per_week":{"type":"integer","minimum":1.0,"title":"Spots Per Week"},"weeks":{"type":"integer","minimum":1.0,"title":"Weeks"},"gross_rate_per_spot":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Gross Rate Per Spot"},"net_rate_per_spot":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate Per Spot"},"rate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"item_source":{"$ref":"#/components/schemas/cbradio__LineItemSource","default":"manual"},"ai_suggestion_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Suggestion Id"},"action":{"anyOf":[{"$ref":"#/components/schemas/cbradio__SuggestionAction"},{"type":"null"}]},"modifications":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Modifications"},"ai_confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Ai Confidence"},"ai_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Reason"},"classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification"},"classification_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification Source"}},"type":"object","required":["station_callsign","spots_per_week","weeks","gross_rate_per_spot"],"title":"LineItemCreate","description":"Fields for creating a new line item."},"cbradio__LineItemResponse":{"properties":{"station_callsign":{"type":"string","maxLength":20,"title":"Station Callsign"},"station_format":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Station Format"},"dma":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Dma"},"days":{"type":"string","maxLength":20,"title":"Days","description":"Days of week (MTWRF, Sat, Sun)"},"daypart":{"type":"string","maxLength":20,"title":"Daypart","description":"Time slot (6A-10A, etc.)"},"duration_seconds":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Duration Seconds"},"gross_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Rate","description":"Rate per spot"},"spot_schedule":{"additionalProperties":true,"type":"object","title":"Spot Schedule","description":"Weekly/daily spot counts"},"total_spots":{"type":"integer","minimum":0.0,"title":"Total Spots"},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount"},"net_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Net Amount"},"id":{"type":"string","title":"Id"},"sort_order":{"type":"integer","title":"Sort Order","default":0}},"type":"object","required":["station_callsign","days","daypart","duration_seconds","gross_rate","spot_schedule","total_spots","gross_amount","net_amount","id"],"title":"LineItemResponse","description":"Response model for line item."},"cbradio__LineItemSource":{"type":"string","enum":["ai","manual"],"title":"LineItemSource","description":"How a line item was added."},"cbradio__LineItemUpdate":{"properties":{"spots_per_week":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Spots Per Week"},"weeks":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Weeks"},"gross_rate_per_spot":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate Per Spot"},"net_rate_per_spot":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate Per Spot"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"LineItemUpdate","description":"Fields for updating a line item (all optional)."},"cbradio__LockedSlotRequest":{"properties":{"daypart_id":{"type":"string","title":"Daypart Id"},"slot_days":{"type":"string","title":"Slot Days","description":"m-f | sat | sun | sat-sun | m-sat | all","default":"all"},"spots_per_week":{"type":"integer","maximum":50.0,"minimum":0.0,"title":"Spots Per Week"}},"type":"object","required":["daypart_id","spots_per_week"],"title":"LockedSlotRequest","description":"One (station, daypart, days) held at a weight.\n\n``slot_days`` is required rather than optional because a station can sell\nthe same daypart on different days at different prices, and the solver\noffers those as separate options (cbradio#369). A lock naming only the\ndaypart would be ambiguous between them."},"cbradio__MailingAddress":{"properties":{"street":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Street"},"street2":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Street2"},"city":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"City"},"state":{"anyOf":[{"type":"string","maxLength":2},{"type":"null"}],"title":"State"},"zip":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Zip"}},"type":"object","title":"MailingAddress","description":"Structured mailing address."},"cbradio__MakeGoodListResponse":{"properties":{"make_good_requests":{"items":{"$ref":"#/components/schemas/cbradio__MakeGoodRequestResponse"},"type":"array","title":"Make Good Requests"}},"type":"object","required":["make_good_requests"],"title":"MakeGoodListResponse","description":"Response for listing make-good requests."},"cbradio__MakeGoodReason":{"type":"string","enum":["preemption","technical_issue","scheduling_error","audio_issue","other"],"title":"MakeGoodReason","description":"Reason categories for make-good requests."},"cbradio__MakeGoodRequestCreate":{"properties":{"reason_category":{"$ref":"#/components/schemas/cbradio__MakeGoodReason"},"reason_details":{"type":"string","minLength":1,"title":"Reason Details"},"affected_spots":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AffectedSpots"},{"type":"null"}]},"proposed_resolution":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proposed Resolution"},"contact_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Email"}},"type":"object","required":["reason_category","reason_details"],"title":"MakeGoodRequestCreate","description":"Request model for creating make-good request."},"cbradio__MakeGoodRequestResolve":{"properties":{"status":{"$ref":"#/components/schemas/cbradio__MakeGoodStatus","description":"New status (approved, completed, rejected)"},"resolution_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Notes"}},"type":"object","required":["status"],"title":"MakeGoodRequestResolve","description":"Request model for resolving make-good request."},"cbradio__MakeGoodRequestResponse":{"properties":{"id":{"type":"string","title":"Id"},"order_packet_id":{"type":"string","title":"Order Packet Id"},"estimate_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Estimate Number"},"station_group_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Group Name"},"reason_category":{"$ref":"#/components/schemas/cbradio__MakeGoodReason"},"reason_details":{"type":"string","title":"Reason Details"},"affected_spots":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AffectedSpots"},{"type":"null"}]},"proposed_resolution":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proposed Resolution"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"status":{"$ref":"#/components/schemas/cbradio__MakeGoodStatus"},"resolution_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Notes"},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At"},"resolved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved By"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","order_packet_id","reason_category","reason_details","status","created_at"],"title":"MakeGoodRequestResponse","description":"Response model for make-good request."},"cbradio__MakeGoodStatus":{"type":"string","enum":["pending","approved","completed","rejected"],"title":"MakeGoodStatus","description":"Status of make-good request."},"cbradio__ManualConfirmRequest":{"properties":{"confirmed_by":{"type":"string","maxLength":255,"minLength":1,"title":"Confirmed By"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["confirmed_by"],"title":"ManualConfirmRequest","description":"Request model for manual confirmation."},"cbradio__ManualRateWriteRequest":{"properties":{"rates":{"items":{"$ref":"#/components/schemas/cbradio__RateWriteItem"},"type":"array","title":"Rates"},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"ad_type":{"$ref":"#/components/schemas/cbradio__src__api__models__rate__AdType","default":"all"},"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"source":{"type":"string","title":"Source","description":"manual_entry or qa_correction","default":"manual_entry"},"entered_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entered By"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["rates"],"title":"ManualRateWriteRequest","description":"Request body for POST /stations/{callsign}/rates (#54)."},"cbradio__MarketDetail":{"properties":{"market_rank":{"type":"integer","title":"Market Rank","description":"Nielsen market rank.","examples":[1]},"market_name":{"type":"string","title":"Market Name","description":"Market name.","examples":["New York"]},"market_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market State","description":"State code(s).","examples":["NH",null]},"population":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Population","description":"Metro population.","examples":[16563900]},"pop_black":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Black","description":"Black population.","examples":[2696600]},"pop_hispanic":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Hispanic","description":"Hispanic population.","examples":[4228100]},"station_count":{"type":"integer","title":"Station Count","description":"Number of stations returned (may be less than total if filtered).","examples":[53,4]},"stations":{"items":{"$ref":"#/components/schemas/cbradio__NielsenRating"},"type":"array","title":"Stations","description":"Station ratings sorted by rating_4 descending (strongest first)."}},"type":"object","required":["market_rank","market_name","station_count","stations"],"title":"MarketDetail","description":"Full market detail including all station ratings, sorted by latest rating descending."},"cbradio__MarketSummary":{"properties":{"market_rank":{"type":"integer","title":"Market Rank","description":"Nielsen market rank (1=largest).","examples":[1,50]},"market_name":{"type":"string","title":"Market Name","description":"Market name.","examples":["New York","Manchester"]},"market_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market State","description":"State code(s).","examples":["NH","TX",null]},"population":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Population","description":"Metro population.","examples":[16563900,425000]},"pop_black":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Black","description":"Black population.","examples":[2696600]},"pop_hispanic":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Hispanic","description":"Hispanic population.","examples":[4228100]},"station_count":{"type":"integer","title":"Station Count","description":"Number of distinct stations with ratings in this market.","examples":[53,15]}},"type":"object","required":["market_rank","market_name","station_count"],"title":"MarketSummary","description":"Summary of a Nielsen market with station count and demographics."},"cbradio__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbradio__NetworkBuyCreate":{"properties":{"network_id":{"type":"string","title":"Network Id"},"rate_type":{"type":"string","title":"Rate Type"},"spot_length":{"type":"string","title":"Spot Length"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"num_weeks":{"type":"integer","title":"Num Weeks"},"flight_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flight End"},"proposal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proposal Id"},"radio_buy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"}},"type":"object","required":["network_id","rate_type","spot_length","spots_per_week","num_weeks"],"title":"NetworkBuyCreate","description":"Fields required to create a StateNets network buy."},"cbradio__NetworkBuyUpdate":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"flight_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flight End"}},"type":"object","title":"NetworkBuyUpdate","description":"Fields that can be updated on a StateNets network buy."},"cbradio__NetworkItemIn":{"properties":{"network_id":{"type":"string","title":"Network Id"},"network_name":{"type":"string","title":"Network Name"},"network_state":{"type":"string","title":"Network State"},"wired_network_type":{"type":"string","enum":["statenets","westwood_one_wire","premiere_networks_wire"],"title":"Wired Network Type"},"product_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Type"},"station_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Station Count"},"rate_type":{"type":"string","title":"Rate Type"},"spot_length":{"type":"string","title":"Spot Length"},"rate_per_spot":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Rate Per Spot"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"num_weeks":{"type":"integer","title":"Num Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"total_gross":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Total Gross"},"ruralam_commission":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Ruralam Commission"},"statenets_commission":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Statenets Commission"},"buyer_agency_commission":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Buyer Agency Commission"},"station_share":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Station Share"},"flight_start":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight End"},"status":{"type":"string","title":"Status","default":"draft"}},"type":"object","required":["network_id","network_name","network_state","wired_network_type","rate_type","spot_length","rate_per_spot","spots_per_week","num_weeks","total_spots","total_gross","ruralam_commission","statenets_commission","buyer_agency_commission","station_share"],"title":"NetworkItemIn","description":"Inbound network-item row pushed from cbradio-ui on radio-buy sync."},"cbradio__NetworkItemOut":{"properties":{"network_id":{"type":"string","title":"Network Id"},"network_name":{"type":"string","title":"Network Name"},"network_state":{"type":"string","title":"Network State"},"wired_network_type":{"type":"string","enum":["statenets","westwood_one_wire","premiere_networks_wire"],"title":"Wired Network Type"},"product_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Type"},"station_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Station Count"},"rate_type":{"type":"string","title":"Rate Type"},"spot_length":{"type":"string","title":"Spot Length"},"rate_per_spot":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Rate Per Spot"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"num_weeks":{"type":"integer","title":"Num Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"ruralam_commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Ruralam Commission"},"statenets_commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Statenets Commission"},"buyer_agency_commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Buyer Agency Commission"},"station_share":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Share"},"flight_start":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight End"},"status":{"type":"string","title":"Status","default":"draft"},"id":{"type":"string","title":"Id"},"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["network_id","network_name","network_state","wired_network_type","rate_type","spot_length","rate_per_spot","spots_per_week","num_weeks","total_spots","total_gross","ruralam_commission","statenets_commission","buyer_agency_commission","station_share","id","radio_buy_id","created_at","updated_at"],"title":"NetworkItemOut","description":"Network-item row as stored — includes id and timestamps."},"cbradio__NetworkItemsReplaceRequest":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbradio__NetworkItemIn"},"type":"array","title":"Items"}},"type":"object","title":"NetworkItemsReplaceRequest","description":"Replace-all payload for PUT /radio-buys/{id}/network-items."},"cbradio__NetworkItemsReplaceResponse":{"properties":{"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"inserted":{"type":"integer","title":"Inserted"},"deleted":{"type":"integer","title":"Deleted"},"items":{"items":{"$ref":"#/components/schemas/cbradio__NetworkItemOut"},"type":"array","title":"Items"}},"type":"object","required":["radio_buy_id","inserted","deleted","items"],"title":"NetworkItemsReplaceResponse","description":"Response from a replace-all sync."},"cbradio__NetworkLineItemResponse":{"properties":{"id":{"type":"string","title":"Id"},"network_id":{"type":"string","title":"Network Id"},"network_name":{"type":"string","title":"Network Name"},"network_state":{"type":"string","title":"Network State"},"wired_network_type":{"type":"string","title":"Wired Network Type"},"rate_type":{"type":"string","title":"Rate Type"},"spot_length":{"type":"string","title":"Spot Length"},"rate_per_spot":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Rate Per Spot"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"num_weeks":{"type":"integer","title":"Num Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount"},"net_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Net Amount"},"flight_start":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight Start"},"flight_end":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Flight End"}},"type":"object","required":["id","network_id","network_name","network_state","wired_network_type","rate_type","spot_length","rate_per_spot","spots_per_week","num_weeks","total_spots","gross_amount","net_amount"],"title":"NetworkLineItemResponse","description":"Response model for a wired-network line item (#201 Phase 3).\n\nOne row per (network, rate_type, spot_length) on a network-aggregator\norder packet. Distinct from station line items — no callsign, per-network\ntotals instead of per-station."},"cbradio__NetworkScoresResponse":{"properties":{"network_id":{"type":"string","title":"Network Id"},"network_name":{"type":"string","title":"Network Name"},"affiliate_count":{"type":"integer","title":"Affiliate Count"},"scored_affiliate_count":{"type":"integer","title":"Scored Affiliate Count"},"aggregate_score":{"$ref":"#/components/schemas/cbradio__AggregateScore"},"affiliates":{"items":{"$ref":"#/components/schemas/cbradio__AffiliateScore"},"type":"array","title":"Affiliates"}},"type":"object","required":["network_id","network_name","affiliate_count","scored_affiliate_count","aggregate_score","affiliates"],"title":"NetworkScoresResponse","description":"Response for GET /networks/{network_id}/scores."},"cbradio__NetworkUpdate":{"properties":{"candidate_rate_60":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Candidate Rate 60"},"candidate_rate_30":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Candidate Rate 30"},"issue_rate_60":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Issue Rate 60"},"issue_rate_30":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Issue Rate 30"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"daypart":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Daypart"}},"type":"object","title":"NetworkUpdate","description":"Fields that can be updated on a StateNets network."},"cbradio__NielsenRating":{"properties":{"id":{"type":"integer","title":"Id","description":"Internal row ID."},"callsign":{"type":"string","title":"Callsign","description":"Station callsign including band suffix.","examples":["WLTW-FM","WINS-AM","WSKQ-FM"]},"market_rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Market Rank","description":"Nielsen market rank, 1-246. #1 = New York, #2 = Los Angeles, etc.","examples":[1,50,239]},"market_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Name","description":"Market name as listed by Nielsen.","examples":["New York","Manchester","Dallas-Fort Worth"]},"market_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market State","description":"State code(s). Multi-state markets hyphenated (e.g. 'IA-IL'). Null for major metros.","examples":["NH","TX","IA-IL",null]},"population":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Population","description":"Metro area population (persons 12+).","examples":[16563900,425000]},"pop_black":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Black","description":"Black population in metro area.","examples":[2696600,12000]},"pop_hispanic":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pop Hispanic","description":"Hispanic population in metro area.","examples":[4228100,18000]},"survey_period":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Survey Period","description":"Survey period label (e.g. 'Holiday 25', 'Fall 2025').","examples":["Holiday 25","Fall 2025","December 2025"]},"methodology":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Methodology","description":"Measurement method. 'PPM 6+' = electronic (top ~50 markets), '12+' = diary (smaller markets).","examples":["PPM 6+","12+"]},"nielsen_format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Format","description":"Station format per Nielsen (67 known formats). Different from station.format (our normalized version).","examples":["AC","Classic Rock","News/Talk","Country","CHR"]},"nielsen_owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Owner","description":"Station owner as listed by Nielsen.","examples":["iHeartMedia","Audacy","Townsquare","Spanish Broadcasting System"]},"rating_period_1":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating Period 1","description":"Label for rating period 1 (oldest).","examples":["Oct 25","Spr 24"]},"rating_1":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating 1","description":"AQH Share for period 1. Null if station not measured.","examples":[7.4,3.8]},"rating_period_2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating Period 2","description":"Label for rating period 2.","examples":["Nov 25","Fall 24"]},"rating_2":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating 2","description":"AQH Share for period 2.","examples":[7.4,4.1]},"rating_period_3":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating Period 3","description":"Label for rating period 3.","examples":["Dec 25","Spr 25"]},"rating_3":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating 3","description":"AQH Share for period 3.","examples":[9.9,3.5]},"rating_period_4":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rating Period 4","description":"Label for rating period 4 (most recent). Use this for 'current' rating.","examples":["Hol 25","Fall 25"]},"rating_4":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating 4","description":"AQH Share for period 4 — the most recent rating. >5.0 is strong, >10.0 is dominant.","examples":[12.5,5.6,3.9]},"source_file":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source File","description":"Source data file that this rating was parsed from.","examples":["arb001.txt","arb050.txt"]},"market_station_rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Market Station Rank","description":"Station's rank within its market (1 = highest AQH share). Computed from rating_4 DESC.","examples":[1,5,15]},"market_station_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Market Station Count","description":"Total rated stations in this market. Use with rank to compute percentile.","examples":[53,24,12]},"market_percentile":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Market Percentile","description":"Normalized 0-1 score. 1.0 = top station, 0.0 = bottom. Formula: 1 - (rank-1)/count.","examples":[1.0,0.87,0.5]},"is_top_quartile":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Top Quartile","description":"True if station is in top 25% of its market by AQH share."},"ratings_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ratings Score","description":"0-4 score for station scoring. 4=top 25%, 3=25-50%, 2=50-75%, 1=bottom 25%, 0=no data.","examples":[4,3,2,1]}},"type":"object","required":["id","callsign"],"title":"NielsenRating","description":"A single Nielsen rating entry for a station in a market.\n\nEach row represents one station's performance in one market during one survey period.\nStations can appear in multiple markets (e.g. WLTW-FM appears in New York, Nassau-Suffolk,\nHudson Valley, etc.). Ratings are AQH Share (Average Quarter-Hour percentage of listeners)."},"cbradio__NielsenStatsResponse":{"properties":{"total_ratings":{"type":"integer","title":"Total Ratings","description":"Total number of rating rows in the database.","examples":[5151]},"unique_callsigns":{"type":"integer","title":"Unique Callsigns","description":"Number of distinct station callsigns.","examples":[4638]},"unique_markets":{"type":"integer","title":"Unique Markets","description":"Number of distinct Nielsen markets.","examples":[243]},"unique_formats":{"type":"integer","title":"Unique Formats","description":"Number of distinct Nielsen format values.","examples":[66]}},"type":"object","required":["total_ratings","unique_callsigns","unique_markets","unique_formats"],"title":"NielsenStatsResponse","description":"High-level statistics about the Nielsen ratings dataset."},"cbradio__OrderPacketDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"estimate_number":{"type":"string","title":"Estimate Number"},"payee_group_key":{"type":"string","title":"Payee Group Key"},"station_group_name":{"type":"string","title":"Station Group Name"},"contact_email":{"type":"string","title":"Contact Email"},"cc_emails":{"items":{"type":"string"},"type":"array","title":"Cc Emails","default":[]},"status":{"$ref":"#/components/schemas/cbradio__OrderPacketStatus"},"confirmation_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Token"},"token_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Token Expires At"},"total_spots":{"type":"integer","title":"Total Spots"},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount"},"net_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Net Amount"},"station_payment":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Payment"},"total_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Total Gross"},"total_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Total Net"},"spot_breakdown_type":{"$ref":"#/components/schemas/cbradio__SpotBreakdownType"},"pdf_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pdf Url"},"audio_spot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Spot Url"},"nab_form_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nab Form Url"},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At"},"confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmed At"},"confirmed_via":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ConfirmationVia"},{"type":"null"}]},"confirmed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmed By"},"cancelled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cancelled At"},"cancellation_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cancellation Reason"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"stations":{"items":{"type":"string"},"type":"array","title":"Stations","default":[]},"confirmation_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Url"},"payee_type":{"type":"string","title":"Payee Type","default":"station_rep"},"line_items":{"items":{"$ref":"#/components/schemas/cbradio__LineItemResponse"},"type":"array","title":"Line Items","default":[]},"network_line_items":{"items":{"$ref":"#/components/schemas/cbradio__NetworkLineItemResponse"},"type":"array","title":"Network Line Items","default":[]}},"type":"object","required":["id","radio_buy_id","estimate_number","payee_group_key","station_group_name","contact_email","status","total_spots","gross_amount","net_amount","station_payment","spot_breakdown_type","created_at","updated_at"],"title":"OrderPacketDetailResponse","description":"Detailed response with line items.\n\nFor network-aggregator packets (`payee_type='network_aggregator'`),\n`line_items` is empty and `network_line_items` is populated.\nFor station packets, `network_line_items` is empty and `line_items`\ncarries per-station detail."},"cbradio__OrderPacketGenerate":{"properties":{"spot_breakdown_type":{"$ref":"#/components/schemas/cbradio__SpotBreakdownType","default":"weekly"},"audio_spot_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Audio Spot Url"},"include_nab_form":{"type":"boolean","title":"Include Nab Form","default":true}},"type":"object","title":"OrderPacketGenerate","description":"Request model for generating order packets."},"cbradio__OrderPacketListResponse":{"properties":{"order_packets":{"items":{"$ref":"#/components/schemas/cbradio__OrderPacketDetailResponse"},"type":"array","title":"Order Packets"}},"type":"object","required":["order_packets"],"title":"OrderPacketListResponse","description":"Response for listing order packets."},"cbradio__OrderPacketResponse":{"properties":{"id":{"type":"string","title":"Id"},"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"estimate_number":{"type":"string","title":"Estimate Number"},"payee_group_key":{"type":"string","title":"Payee Group Key"},"station_group_name":{"type":"string","title":"Station Group Name"},"contact_email":{"type":"string","title":"Contact Email"},"cc_emails":{"items":{"type":"string"},"type":"array","title":"Cc Emails","default":[]},"status":{"$ref":"#/components/schemas/cbradio__OrderPacketStatus"},"confirmation_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Token"},"token_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Token Expires At"},"total_spots":{"type":"integer","title":"Total Spots"},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount"},"net_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Net Amount"},"station_payment":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Payment"},"total_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Total Gross"},"total_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Total Net"},"spot_breakdown_type":{"$ref":"#/components/schemas/cbradio__SpotBreakdownType"},"pdf_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pdf Url"},"audio_spot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audio Spot Url"},"nab_form_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nab Form Url"},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At"},"confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmed At"},"confirmed_via":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ConfirmationVia"},{"type":"null"}]},"confirmed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmed By"},"cancelled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cancelled At"},"cancellation_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cancellation Reason"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"stations":{"items":{"type":"string"},"type":"array","title":"Stations","default":[]},"confirmation_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Url"},"payee_type":{"type":"string","title":"Payee Type","default":"station_rep"}},"type":"object","required":["id","radio_buy_id","estimate_number","payee_group_key","station_group_name","contact_email","status","total_spots","gross_amount","net_amount","station_payment","spot_breakdown_type","created_at","updated_at"],"title":"OrderPacketResponse","description":"Response model for order packet."},"cbradio__OrderPacketStatus":{"type":"string","enum":["pending_review","ready_to_send","sent","confirmed","make_good_requested","cancelled"],"title":"OrderPacketStatus","description":"Status of an order packet."},"cbradio__OrderPacketUpdate":{"properties":{"contact_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Email"},"cc_emails":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Cc Emails"},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__OrderPacketStatus"},{"type":"null"}]},"audio_spot_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Audio Spot Url"},"nab_form_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Nab Form Url"}},"type":"object","title":"OrderPacketUpdate","description":"Request model for updating an order packet."},"cbradio__OverdueOrder":{"properties":{"id":{"type":"string","title":"Id"},"estimate_number":{"type":"string","title":"Estimate Number"},"station_group_name":{"type":"string","title":"Station Group Name"},"sent_at":{"type":"string","format":"date-time","title":"Sent At"},"days_since_sent":{"type":"integer","title":"Days Since Sent"}},"type":"object","required":["id","estimate_number","station_group_name","sent_at","days_since_sent"],"title":"OverdueOrder","description":"Order that hasn't been confirmed."},"cbradio__OwnershipGroupCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Display name"},"legal_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Legal Name","description":"Full legal entity name for documents"},"payment_method":{"type":"string","pattern":"^(ach|portal|check|wire)$","title":"Payment Method","description":"Payment method: ach, portal, check, or wire","default":"check"},"stations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Stations","description":"List of station callsigns"},"contact_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Contact Phone"},"mailing_address":{"anyOf":[{"$ref":"#/components/schemas/cbradio__MailingAddress"},{"type":"null"}],"description":"Structured mailing address"},"memo_template":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Memo Template","description":"Default memo line for checks"},"portal_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Portal Url"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name"],"title":"OwnershipGroupCreate","description":"Request model for creating an ownership group (payment recipient)."},"cbradio__OwnershipGroupResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"legal_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legal Name"},"payment_method":{"type":"string","title":"Payment Method"},"stations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Stations"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Phone"},"mailing_address":{"anyOf":[{"$ref":"#/components/schemas/cbradio__MailingAddress"},{"type":"null"}]},"mailing_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mailing Lat"},"mailing_lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mailing Lon"},"memo_template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Memo Template"},"portal_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Portal Url"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","payment_method","is_active","created_at","updated_at"],"title":"OwnershipGroupResponse","description":"Response model for an ownership group (payment recipient)."},"cbradio__OwnershipGroupUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"legal_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Legal Name"},"payment_method":{"anyOf":[{"type":"string","pattern":"^(ach|portal|check|wire)$"},{"type":"null"}],"title":"Payment Method"},"stations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Stations"},"contact_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact Email"},"contact_phone":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Contact Phone"},"mailing_address":{"anyOf":[{"$ref":"#/components/schemas/cbradio__MailingAddress"},{"type":"null"}]},"memo_template":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Memo Template"},"portal_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Portal Url"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"OwnershipGroupUpdate","description":"Request model for updating an ownership group."},"cbradio__PayeeGroupDelta":{"properties":{"group_key":{"type":"string","title":"Group Key"},"pay_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pay To"},"change_type":{"type":"string","title":"Change Type"},"old_values":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Old Values"},"new_values":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"New Values"}},"type":"object","required":["group_key","change_type"],"title":"PayeeGroupDelta","description":"Change summary for a payee group."},"cbradio__PayeeGroupWithStatus":{"properties":{"group_key":{"type":"string","title":"Group Key"},"pay_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pay To"},"payment_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Method"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"station_count":{"type":"integer","title":"Station Count","default":0},"spot_count":{"type":"integer","title":"Spot Count","default":0},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount","default":"0"},"station_payments":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Payments","default":"0"},"commission_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission Amount","default":"0"},"station_callsigns":{"items":{"type":"string"},"type":"array","title":"Station Callsigns"},"has_provisional":{"type":"boolean","title":"Has Provisional","default":false},"payment_status":{"$ref":"#/components/schemas/cbradio__PaymentStatus"}},"type":"object","required":["group_key","payment_status"],"title":"PayeeGroupWithStatus","description":"Payee group with payment status info."},"cbradio__PaymentHistoryResponse":{"properties":{"version_id":{"type":"string","title":"Version Id"},"version_number":{"type":"integer","title":"Version Number"},"total_changes":{"type":"integer","title":"Total Changes"},"history":{"items":{"$ref":"#/components/schemas/cbradio__PaymentStatusHistory"},"type":"array","title":"History"}},"type":"object","required":["version_id","version_number","total_changes","history"],"title":"PaymentHistoryResponse","description":"Response for payment status history."},"cbradio__PaymentStatus":{"properties":{"status":{"$ref":"#/components/schemas/cbradio__PaymentStatusEnum","default":"not_started"},"paid_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Paid Date"},"reference_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference Number"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id"},"version_id":{"type":"string","title":"Version Id"},"group_key":{"type":"string","title":"Group Key"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"}},"type":"object","required":["id","version_id","group_key"],"title":"PaymentStatus","description":"Full payment status record."},"cbradio__PaymentStatusEnum":{"type":"string","enum":["not_started","in_progress","paid","issue"],"title":"PaymentStatusEnum","description":"Payment status for payee groups."},"cbradio__PaymentStatusHistory":{"properties":{"id":{"type":"string","title":"Id"},"payment_status_id":{"type":"string","title":"Payment Status Id"},"version_id":{"type":"string","title":"Version Id"},"group_key":{"type":"string","title":"Group Key"},"previous_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous Status"},"new_status":{"type":"string","title":"New Status"},"previous_paid_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Previous Paid Date"},"new_paid_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"New Paid Date"},"previous_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous Reference"},"new_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"New Reference"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"changed_at":{"type":"string","format":"date-time","title":"Changed At"},"changed_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Changed By"}},"type":"object","required":["id","payment_status_id","version_id","group_key","new_status","changed_at"],"title":"PaymentStatusHistory","description":"Payment status change history record."},"cbradio__PendingMakeGood":{"properties":{"id":{"type":"string","title":"Id"},"estimate_number":{"type":"string","title":"Estimate Number"},"station_group_name":{"type":"string","title":"Station Group Name"},"reason_category":{"$ref":"#/components/schemas/cbradio__MakeGoodReason"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","estimate_number","station_group_name","reason_category","created_at"],"title":"PendingMakeGood","description":"Pending make-good for dashboard."},"cbradio__PlacementCreate":{"properties":{"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id","description":"Station ID"},"station_callsign":{"type":"string","title":"Station Callsign","description":"Station callsign (e.g., WABC-AM)"},"daypart":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Daypart","description":"Time slot/daypart name"},"spots_per_week":{"type":"integer","minimum":1.0,"title":"Spots Per Week","description":"Number of spots per week"},"rate":{"type":"number","minimum":0.0,"title":"Rate","description":"Rate per spot"},"weeks":{"items":{"type":"integer"},"type":"array","title":"Weeks","description":"ISO week numbers in flight. If empty, backend computes from the parent radio_buy's start_date/end_date (cbradio#251)."},"source_rate_card_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Rate Card Id","description":"The rate_card.id whose rates this placement was priced from. Snapshot for audit + the three-axis gates (cbradio#251). Backend resolves from station_id+year if not provided."},"simulcast_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulcast Group Id","description":"Simulcast group ID if this station is part of a simulcast group"},"simulcast_callsigns":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Simulcast Callsigns","description":"All callsigns in the simulcast group (e.g., ['WABC-AM', 'WABC-FM', 'W201AB'])"}},"type":"object","required":["station_callsign","spots_per_week","rate"],"title":"PlacementCreate","description":"Model for creating a placement on a radio buy."},"cbradio__PlanFlightShape":{"type":"string","enum":["even","front_loaded","back_loaded","closing_surge"],"title":"PlanFlightShape","description":"How a station's spots distribute across the weeks of the flight."},"cbradio__PlanLineResponse":{"properties":{"callsign":{"type":"string","title":"Callsign"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"daypart_id":{"type":"string","title":"Daypart Id"},"slot_days":{"type":"string","title":"Slot Days"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"weeks":{"type":"integer","title":"Weeks"},"weekly_spots":{"items":{"type":"integer"},"type":"array","title":"Weekly Spots"},"total_spots":{"type":"integer","title":"Total Spots"},"gross_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Rate"},"net_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"basis":{"type":"string","title":"Basis","description":"card | benchmark"},"basis_label":{"type":"string","title":"Basis Label"},"verification":{"type":"string","title":"Verification","description":"How much scrutiny this price has had: human_reviewed | auto_approved | unreviewed. 94% of live rates are auto_approved — a structured feed approved on arrival because it parses cleanly, with nobody having checked the number itself.","default":""},"priced_from":{"type":"string","title":"Priced From","description":"Which daypart slug on the card supplied this price. Equal to daypart_id when the card priced the slot being bought; 'ros', 'evening' and so on when a rate is standing in. We buy only the three standard dayparts (6a-10a, 10a-3p, 3p-7p), so a borrowed price is routine — and must be visible on the quote.","default":""},"card_is_current":{"type":"boolean","title":"Card Is Current"},"classification":{"type":"string","title":"Classification"},"score":{"type":"integer","title":"Score"},"rationale":{"type":"string","title":"Rationale"},"rate_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Id"}},"type":"object","required":["callsign","state","daypart_id","slot_days","spots_per_week","weeks","weekly_spots","total_spots","gross_rate","net_rate","total_gross","basis","basis_label","card_is_current","classification","score","rationale","rate_id"],"title":"PlanLineResponse","description":"One station-daypart buy — the grain buys are actually placed at."},"cbradio__PlanPostureRequest":{"properties":{"flight_shape":{"$ref":"#/components/schemas/cbradio__PlanFlightShape","default":"even"},"min_spots_per_week":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Min Spots Per Week","description":"Station-level floor, summed across dayparts","default":8},"max_spots_per_week":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Max Spots Per Week","description":"Station-level cap; 50 is the hard rail","default":40},"max_stations":{"anyOf":[{"type":"integer","maximum":1000.0,"minimum":1.0},{"type":"null"}],"title":"Max Stations"},"min_score":{"type":"integer","maximum":16.0,"minimum":0.0,"title":"Min Score","description":"Relevance floor. Statewide queries cap at 8 of 16 (coverage needs district geography, CPE needs voter data), so a floor above 8 will empty a statewide plan. District targeting activates coverage and raises the ceiling to 12.","default":0},"network_preference":{"type":"number","maximum":2.0,"minimum":0.0,"title":"Network Preference","description":"Margin lever. 0 = pure client outcome (the default, per CJ's 2026-08-15 ruling). Its position is echoed back on the plan.","default":0.0},"reach_bias":{"type":"number","maximum":10.0,"exclusiveMinimum":0.0,"title":"Reach Bias","description":">1 more stations, <1 fewer and harder","default":1.0},"require_card_rates":{"type":"boolean","title":"Require Card Rates","description":"Refuse benchmark-estimated prices","default":false},"exclude_dayparts":{"items":{"type":"string"},"type":"array","title":"Exclude Dayparts"},"locked_stations":{"additionalProperties":{"items":{"$ref":"#/components/schemas/cbradio__LockedSlotRequest"},"type":"array"},"type":"object","title":"Locked Stations","description":"Stations the buyer has committed to: callsign -> list of locked slots. A non-empty list locks exact weekly spot counts and the solver leaves that station alone in every daypart; an empty list pins the station into the plan and lets the solver choose the weight. Locking a plan's own output and re-solving reproduces it exactly, which is what makes lock-and-adjust safe. A lock overrides min_score — naming a station is the relevance judgment that floor approximates — but not require_card_rates."},"excluded_stations":{"items":{"type":"string"},"type":"array","maxItems":500,"title":"Excluded Stations","description":"Callsigns never to buy. Wins over a lock on the same station."}},"type":"object","title":"PlanPostureRequest","description":"The strategy knobs — shape decisions, never prices.\n\nAn LLM may fill these in and a human confirms them before the solve. None of\nit can produce a wrong rate: a bad value here yields a differently *shaped*\nplan, which is the whole point of keeping the model on this side of the line."},"cbradio__PlanRequest":{"properties":{"target_states":{"items":{"type":"string"},"type":"array","maxItems":20,"title":"Target States"},"target_districts":{"items":{"type":"string"},"type":"array","maxItems":200,"title":"Target Districts","description":"District ids, e.g. ['MI-SD-020','MI-SD-019']"},"district_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"District Type","description":"congressional | state_senate | state_house | county"},"budget":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Budget"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type","description":"primary | general | special — sets the audience profile, and with `election_date` the length of the LUR window (cbradio#376)"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date","description":"Election day. With `election_type`, determines the lowest-unit-charge window (47 U.S.C. 315(b)): 45 days before a primary, 60 before a general or special. Omit and the solver will not rank on the window — it will not guess one."},"rate_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Context","description":"in_window | out_of_window — the caller's own statement of which side of the lowest-unit-charge boundary this buy sits on. Wins over anything derived from `election_date`: a buyer who picks 'candidate, in window' has told us directly, and that is better evidence than arithmetic on a date. Omit both and rate selection will not rank on the window at all (cbradio#376)."},"target_audience":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Audience","description":"republican_primary | democratic_primary | swing_general. Wins over election_type when given. A caller that already knows the audience should say so: election_type cannot express partisan lean, so a Democratic primary reaching this endpoint as 'primary' would be scored against a Republican format profile."},"ad_type":{"$ref":"#/components/schemas/cbradio__src__api__models__proposal__AdType","default":"candidate"},"spot_duration":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Duration","default":60},"preemption":{"anyOf":[{"$ref":"#/components/schemas/cbradio__PreemptionRequest"},{"type":"null"}]},"station_pool":{"$ref":"#/components/schemas/cbradio__StationPool","default":"all"},"election_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Election Year","description":"Freshness bar for rate cards. Defaults to the current year."},"posture":{"$ref":"#/components/schemas/cbradio__PlanPostureRequest"}},"type":"object","required":["budget","start_date","end_date"],"title":"PlanRequest","description":"Campaign parameters plus a posture. Writes nothing.\n\nTarget **either** whole states or a set of districts. Districts win when\nboth are supplied, and they are the better mode: sub-state targeting\nactivates the coverage dimension (geo-fit — district reach blended with the\nshare of the station's own signal that lands inside it), where a statewide\nquery has no fit dimension to blend and scores cap at 8 of 16."},"cbradio__PlanResponse":{"properties":{"lines":{"items":{"$ref":"#/components/schemas/cbradio__PlanLineResponse"},"type":"array","title":"Lines"},"budget":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Budget"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_spots":{"type":"integer","title":"Total Spots"},"weeks":{"type":"integer","title":"Weeks"},"budget_used_pct":{"type":"number","title":"Budget Used Pct"},"budget_remaining":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Budget Remaining"},"stations_included":{"type":"integer","title":"Stations Included"},"stations_estimated":{"type":"integer","title":"Stations Estimated","description":"Stations priced from a benchmark rather than a card"},"station_spots_per_week":{"additionalProperties":{"type":"integer"},"type":"object","title":"Station Spots Per Week"},"targeting":{"type":"string","title":"Targeting","description":"statewide | district"},"districts_targeted":{"items":{"type":"string"},"type":"array","title":"Districts Targeted"},"stations_considered":{"type":"integer","title":"Stations Considered"},"stations_priced_from_card":{"type":"integer","title":"Stations Priced From Card"},"stations_priced_from_benchmark":{"type":"integer","title":"Stations Priced From Benchmark"},"stations_unpriced":{"type":"integer","title":"Stations Unpriced"},"candidates_eligible":{"type":"integer","title":"Candidates Eligible"},"audience":{"type":"string","title":"Audience"},"posture":{"$ref":"#/components/schemas/cbradio__PlanPostureRequest"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"},"window_split":{"anyOf":[{"$ref":"#/components/schemas/cbradio__WindowSplitOffer"},{"type":"null"}],"description":"Present only when the flight crosses the lowest-unit-charge boundary. The plan above is priced WITHOUT ranking the window (there is no single correct side), so treat this as an offer to re-request each segment separately — see cbradio#376."}},"type":"object","required":["lines","budget","total_gross","total_spots","weeks","budget_used_pct","budget_remaining","stations_included","stations_estimated","station_spots_per_week","targeting","stations_considered","stations_priced_from_card","stations_priced_from_benchmark","stations_unpriced","candidates_eligible","audience","posture","warnings"],"title":"PlanResponse","description":"A complete, priced, flighted plan and an account of how it got there."},"cbradio__PlanningApprovalBody":{"properties":{"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","title":"PlanningApprovalBody","description":"Request body for POST /rate-cards/{id}/approve-planning (#205).\n\nOptional note — e.g. \"Confirmed with Angie Martin at iHeart 2026-04-22,\nrate card is accurate for this flight window\"."},"cbradio__PlanningApprovalResponse":{"properties":{"rate_card_id":{"type":"string","title":"Rate Card Id"},"lifecycle":{"type":"string","title":"Lifecycle"},"planning_approved_by":{"type":"string","title":"Planning Approved By"},"planning_approved_at":{"type":"string","format":"date-time","title":"Planning Approved At"},"planning_approval_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Planning Approval Note"}},"type":"object","required":["rate_card_id","lifecycle","planning_approved_by","planning_approved_at"],"title":"PlanningApprovalResponse","description":"Response for POST /rate-cards/{id}/approve-planning."},"cbradio__PrecisionTrakImportItem":{"properties":{"callsign":{"type":"string","title":"Callsign"},"band":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Band"},"national_rep_firm":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"National Rep Firm"},"is_commercial":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Commercial"},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County"},"simulcast_group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulcast Group"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"mailing_street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Street"},"mailing_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing City"},"mailing_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing State"},"mailing_zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Zip"}},"type":"object","required":["callsign"],"title":"PrecisionTrakImportItem","description":"Single item in a PrecisionTrak enrichment import."},"cbradio__PrecisionTrakImportRequest":{"properties":{"stations":{"items":{"$ref":"#/components/schemas/cbradio__PrecisionTrakImportItem"},"type":"array","title":"Stations"}},"type":"object","required":["stations"],"title":"PrecisionTrakImportRequest","description":"Request body for PrecisionTrak CSV import."},"cbradio__PreemptionRequest":{"type":"string","enum":["non_preemptible","preemptible_with_notice","preemptible"],"title":"PreemptionRequest","description":"What a buyer can ask for — the guarantee, not the row's status.\n\ncbradio#353. This used to be a second class named ``PreemptionStatus``\ncarrying two of the four members its namesake in ``models/rate.py`` has, so\n``preemptible_with_notice`` was unrequestable and **2,077 stations holding\n71,159 rows could not be reached by any request**. CJ's ruling, 2026-08-04:\nthree rungs, because the middle one is a distinct promise and is priced like\none.\n\n| what the buyer gets       | median gross | stations |\n|---------------------------|--------------|----------|\n| ``non_preemptible``       | ``$35.00``   | 4,100    |\n| ``preemptible_with_notice`` | ``$20.00`` | 2,077    |\n| ``preemptible``           | ``$15.00``   | 2,941    |\n\nWith-notice is 33% above plain preemptible, so folding it into the\n``preemptible`` search would answer a request for a ``$15`` rate with a\n``$20`` one carrying a different guarantee.\n\n``PreemptionStatus.ALL`` is deliberately absent: ``all`` describes a rate row\n(\"this price applies whatever the preemption status\"), not something a buyer\nasks for, and it already reaches every request through the\n``OR r.preemption = 'all'`` branch in ``generate_proposal``. Every member here\nmust be a valid ``PreemptionStatus`` — the canonical vocabulary lives there."},"cbradio__PreemptionStatus":{"type":"string","enum":["non_preemptible","preemptible","preemptible_with_notice","all"],"title":"PreemptionStatus","description":"Whether a spot can be preempted by higher-paying advertisers."},"cbradio__PricingAdjustment":{"properties":{"gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate","description":"Gross rate (before commission)"},"net_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate","description":"Net rate (after commission)"}},"type":"object","title":"PricingAdjustment","description":"Adjust pricing fields only - for AI correction workflow.\n\nIf only gross_rate provided, net_rate is auto-calculated as gross * 0.85.\nIf only net_rate provided, gross_rate is auto-calculated as net / 0.85."},"cbradio__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbradio__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbradio__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbradio__Project":{"properties":{"slug":{"type":"string","title":"Slug","description":"Project slug (e.g., cbradio)"},"repo":{"type":"string","title":"Repo","description":"GitHub repo path (e.g., isenbek/cbradio)"},"description":{"type":"string","title":"Description","description":"Project description"}},"type":"object","required":["slug","repo","description"],"title":"Project","description":"CB* project metadata."},"cbradio__ProposalClassEnum":{"type":"string","enum":["candidate_in_window","candidate_out_of_window","issue"],"title":"ProposalClassEnum","description":"Political-window classification for a radio buy (cbradio#241 / #249).\n\nDrives the `window_mismatch` gate in order-packet generation and the\n`?proposal_class=` filter on `/stations/{cs}/rates`. Maps via\n`PROPOSAL_CLASS_MAP` in `src/lib/rate_filters.py` to the allowed\n(ad_type, rate_context) tuples for the buy's placements."},"cbradio__ProposalCreate":{"properties":{"name":{"type":"string","minLength":1,"title":"Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"consultant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consultant Id"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"},"ad_type":{"$ref":"#/components/schemas/cbradio__src__api__models__proposal__AdType","default":"candidate"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date"},"spot_duration":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Duration","default":60},"preemption":{"$ref":"#/components/schemas/cbradio__PreemptionRequest","default":"non_preemptible"},"time_slot_preference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Slot Preference"},"target_states":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target States"},"target_dmas":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Dmas"},"rural_only":{"type":"boolean","title":"Rural Only","default":false},"station_pool":{"$ref":"#/components/schemas/cbradio__StationPool","description":"Station pool: 'all' = full FCC universe, 'network_only' = In-Network + Salem only.","default":"all"},"commission_rate":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Commission Rate","default":"0.15"},"target_geography":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Target Geography","description":"Structured geography: {states, districts, DMAs, counties}"},"budget_range":{"anyOf":[{"$ref":"#/components/schemas/cbradio__BudgetRange"},{"type":"null"}]},"exact_budget":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Exact Budget"},"campaign_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__CampaignType"},{"type":"null"}]},"brief_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Notes"},"brief_attachment_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Attachment Url"},"proposal_source":{"$ref":"#/components/schemas/cbradio__ProposalSource","default":"direct"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"line_items":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbradio__LineItemCreate"},"type":"array"},{"type":"null"}],"title":"Line Items"}},"type":"object","required":["name","start_date","end_date"],"title":"ProposalCreate","description":"Fields for creating a new proposal."},"cbradio__ProposalFinancialSummary":{"properties":{"proposal_id":{"type":"string","title":"Proposal Id"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Net"},"total_spots":{"type":"integer","title":"Total Spots"},"commission_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission Amount"},"line_item_count":{"type":"integer","title":"Line Item Count"},"stations":{"items":{"type":"string"},"type":"array","title":"Stations"}},"type":"object","required":["proposal_id","total_gross","total_net","total_spots","commission_amount","line_item_count","stations"],"title":"ProposalFinancialSummary","description":"Financial summary of a proposal."},"cbradio__ProposalSource":{"type":"string","enum":["brief","direct"],"title":"ProposalSource","description":"How the proposal originated."},"cbradio__ProposalStatus":{"type":"string","enum":["draft","pending_review","sent","accepted","rejected","expired","converted"],"title":"ProposalStatus","description":"Status of a proposal in the workflow."},"cbradio__ProposalUpdate":{"properties":{"name":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"consultant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consultant Id"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__src__api__models__proposal__AdType"},{"type":"null"}]},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date"},"spot_duration":{"anyOf":[{"type":"integer","maximum":120.0,"minimum":15.0},{"type":"null"}],"title":"Spot Duration"},"preemption":{"anyOf":[{"$ref":"#/components/schemas/cbradio__PreemptionRequest"},{"type":"null"}]},"time_slot_preference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Slot Preference"},"target_states":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target States"},"target_dmas":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Dmas"},"rural_only":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rural Only"},"station_pool":{"anyOf":[{"$ref":"#/components/schemas/cbradio__StationPool"},{"type":"null"}]},"commission_rate":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Commission Rate"},"target_geography":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Target Geography"},"budget_range":{"anyOf":[{"$ref":"#/components/schemas/cbradio__BudgetRange"},{"type":"null"}]},"exact_budget":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Exact Budget"},"campaign_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__CampaignType"},{"type":"null"}]},"brief_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Notes"},"brief_attachment_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Attachment Url"},"proposal_source":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ProposalSource"},{"type":"null"}]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"ProposalUpdate","description":"Fields for updating a proposal (all optional)."},"cbradio__ProposalWithLineItems":{"properties":{"name":{"type":"string","minLength":1,"title":"Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"consultant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consultant Id"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"},"ad_type":{"$ref":"#/components/schemas/cbradio__src__api__models__proposal__AdType","default":"candidate"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date"},"spot_duration":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Duration","default":60},"preemption":{"$ref":"#/components/schemas/cbradio__PreemptionRequest","default":"non_preemptible"},"time_slot_preference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Slot Preference"},"target_states":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target States"},"target_dmas":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Target Dmas"},"rural_only":{"type":"boolean","title":"Rural Only","default":false},"station_pool":{"$ref":"#/components/schemas/cbradio__StationPool","description":"Station pool: 'all' = full FCC universe, 'network_only' = In-Network + Salem only.","default":"all"},"commission_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission Rate","default":"0.15"},"target_geography":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Target Geography","description":"Structured geography: {states, districts, DMAs, counties}"},"budget_range":{"anyOf":[{"$ref":"#/components/schemas/cbradio__BudgetRange"},{"type":"null"}]},"exact_budget":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Exact Budget"},"campaign_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__CampaignType"},{"type":"null"}]},"brief_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Notes"},"brief_attachment_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Attachment Url"},"proposal_source":{"$ref":"#/components/schemas/cbradio__ProposalSource","default":"direct"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id"},"proposal_number":{"type":"string","title":"Proposal Number"},"status":{"$ref":"#/components/schemas/cbradio__ProposalStatus","default":"draft"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accepted At"},"rejected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Rejected At"},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rejection Reason"},"converted_to_buy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Converted To Buy Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"line_items":{"items":{"$ref":"#/components/schemas/cbradio__LineItem"},"type":"array","title":"Line Items","default":[]},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross","default":"0"},"total_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Net","default":"0"},"total_spots":{"type":"integer","title":"Total Spots","default":0}},"type":"object","required":["name","start_date","end_date","id","proposal_number","created_at","updated_at"],"title":"ProposalWithLineItems","description":"Proposal with embedded line items."},"cbradio__PublicConfirmRequest":{"properties":{"confirmed_by_name":{"type":"string","maxLength":255,"minLength":1,"title":"Confirmed By Name"},"confirmed_by_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Confirmed By Email"}},"type":"object","required":["confirmed_by_name"],"title":"PublicConfirmRequest","description":"Request model for public order confirmation."},"cbradio__PublicConfirmResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"confirmed_at":{"type":"string","format":"date-time","title":"Confirmed At"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","confirmed_at","message"],"title":"PublicConfirmResponse","description":"Response after public confirmation."},"cbradio__PublicLineItem":{"properties":{"station_callsign":{"type":"string","title":"Station Callsign"},"station_format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Format"},"dma":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dma"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"weeks":{"type":"integer","title":"Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"gross_rate_per_spot":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate Per Spot"}},"type":"object","required":["station_callsign","spots_per_week","weeks","total_spots"],"title":"PublicLineItem","description":"Public-safe line item data."},"cbradio__PublicMakeGoodResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"make_good_request_id":{"type":"string","title":"Make Good Request Id"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","make_good_request_id","message"],"title":"PublicMakeGoodResponse","description":"Response after submitting make-good via public endpoint."},"cbradio__PublicNetworkLineItem":{"properties":{"network_name":{"type":"string","title":"Network Name"},"network_state":{"type":"string","title":"Network State"},"rate_type":{"type":"string","title":"Rate Type"},"spot_length":{"type":"string","title":"Spot Length"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"num_weeks":{"type":"integer","title":"Num Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"rate_per_spot":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Rate Per Spot"},"gross_amount":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Amount"}},"type":"object","required":["network_name","network_state","rate_type","spot_length","spots_per_week","num_weeks","total_spots","rate_per_spot","gross_amount"],"title":"PublicNetworkLineItem","description":"Slim network line item for public confirmation page (#201 Phase 3)."},"cbradio__PublicOrderResponse":{"properties":{"estimate_number":{"type":"string","title":"Estimate Number"},"client_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Name"},"campaign_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Name"},"station_group_name":{"type":"string","title":"Station Group Name"},"run_dates":{"additionalProperties":true,"type":"object","title":"Run Dates"},"ad_length_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ad Length Seconds"},"status":{"$ref":"#/components/schemas/cbradio__OrderPacketStatus"},"confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmed At"},"total_spots":{"type":"integer","title":"Total Spots"},"station_payment":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Payment"},"stations":{"items":{"$ref":"#/components/schemas/cbradio__src__api__models__order_packet__StationSummary"},"type":"array","title":"Stations","default":[]},"network_line_items":{"items":{"$ref":"#/components/schemas/cbradio__PublicNetworkLineItem"},"type":"array","title":"Network Line Items","default":[]},"payee_type":{"type":"string","title":"Payee Type","default":"station_rep"},"files":{"additionalProperties":true,"type":"object","title":"Files"},"can_confirm":{"type":"boolean","title":"Can Confirm"},"can_request_make_good":{"type":"boolean","title":"Can Request Make Good"}},"type":"object","required":["estimate_number","station_group_name","run_dates","status","total_spots","station_payment","files","can_confirm","can_request_make_good"],"title":"PublicOrderResponse","description":"Public view of order for confirmation.\n\nFor station packets (`payee_type='station_rep'`), `stations` carries the\nper-callsign list; `network_line_items` is empty.\n\nFor network-aggregator packets (`payee_type='network_aggregator'`, #201\nPhase 3), `network_line_items` carries the per-network detail and\n`stations` is empty."},"cbradio__PublicProposalResponse":{"properties":{"proposal_number":{"type":"string","title":"Proposal Number"},"name":{"type":"string","title":"Name"},"client_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Name"},"status":{"type":"string","title":"Status"},"ad_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ad Type"},"ad_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ad Length"},"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"election_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Date"},"target_states":{"items":{"type":"string"},"type":"array","title":"Target States","default":[]},"target_dmas":{"items":{"type":"string"},"type":"array","title":"Target Dmas","default":[]},"line_items":{"items":{"$ref":"#/components/schemas/cbradio__PublicLineItem"},"type":"array","title":"Line Items","default":[]},"total_spots":{"type":"integer","title":"Total Spots","default":0},"total_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Total Gross"},"station_count":{"type":"integer","title":"Station Count","default":0},"share_expires_at":{"type":"string","format":"date-time","title":"Share Expires At"},"client_approval_status":{"$ref":"#/components/schemas/cbradio__ClientApprovalStatus","default":"none"},"client_approved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Client Approved At"},"can_approve":{"type":"boolean","title":"Can Approve","default":true}},"type":"object","required":["proposal_number","name","status","share_expires_at"],"title":"PublicProposalResponse","description":"Response model for public proposal view."},"cbradio__RadioBuy":{"properties":{"name":{"type":"string","title":"Name","description":"Campaign/buy name"},"client_id":{"type":"string","title":"Client Id","description":"Reference to client"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id","description":"Reference to buyer agency"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AdTypeEnum"},{"type":"null"}],"description":"candidate or issue"},"election_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ElectionType"},{"type":"null"}],"description":"primary or general"},"proposal_class":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ProposalClassEnum"},{"type":"null"}],"description":"Political-window classification: candidate_in_window / candidate_out_of_window / issue. NULL passes the window_mismatch gate (legacy backwards compat); set explicitly to enforce."},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Campaign start date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Campaign end date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date","description":"Election date"},"project_lead":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Lead","description":"Project lead name"},"project_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Team","description":"Comma-separated team member names"},"is_complete":{"type":"boolean","title":"Is Complete","description":"Whether the buy is complete","default":false},"gross_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount","description":"Total gross amount"},"net_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount","description":"Total net amount"},"rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Gross","description":"Rural gross amount"},"rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Net","description":"Rural net amount"},"non_rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Gross","description":"Non-rural gross amount"},"non_rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Net","description":"Non-rural net amount"},"buyer_agency_commission":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Commission","description":"Agency commission"},"payments_to_stations":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Payments To Stations","description":"Total station payments"},"buyer_agency_rebate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Rebate","description":"Agency rebate"},"consultant_rebate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Consultant Rebate","description":"Consultant rebate"},"profit":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Profit","description":"Profit (can be negative)"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name","client_id","id","created_at","updated_at"],"title":"RadioBuy","description":"Complete radio buy model with database fields."},"cbradio__RadioBuyCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Campaign/buy name"},"client_id":{"type":"string","title":"Client Id","description":"Reference to client"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id","description":"Reference to buyer agency"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AdTypeEnum"},{"type":"null"}],"description":"candidate or issue"},"election_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ElectionType"},{"type":"null"}],"description":"primary or general"},"proposal_class":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ProposalClassEnum"},{"type":"null"}],"description":"Political-window classification: candidate_in_window / candidate_out_of_window / issue. NULL passes the window_mismatch gate (legacy backwards compat); set explicitly to enforce."},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Campaign start date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Campaign end date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date","description":"Election date"},"project_lead":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Lead","description":"Project lead name"},"project_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Team","description":"Comma-separated team member names"},"is_complete":{"type":"boolean","title":"Is Complete","description":"Whether the buy is complete","default":false},"gross_amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount","description":"Total gross amount"},"net_amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount","description":"Total net amount"},"rural_gross":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Gross","description":"Rural gross amount"},"rural_net":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Net","description":"Rural net amount"},"non_rural_gross":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Gross","description":"Non-rural gross amount"},"non_rural_net":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Net","description":"Non-rural net amount"},"buyer_agency_commission":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Commission","description":"Agency commission"},"payments_to_stations":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Payments To Stations","description":"Total station payments"},"buyer_agency_rebate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Rebate","description":"Agency rebate"},"consultant_rebate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Consultant Rebate","description":"Consultant rebate"},"profit":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Profit","description":"Profit (can be negative)"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"consultant_ids":{"items":{"type":"string"},"type":"array","title":"Consultant Ids","description":"List of consultant IDs"}},"type":"object","required":["name","client_id"],"title":"RadioBuyCreate","description":"Model for creating a new radio buy."},"cbradio__RadioBuyFinancialSummary":{"properties":{"total_buys":{"type":"integer","title":"Total Buys"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross"},"total_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Net"},"total_rural_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Rural Gross"},"total_rural_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Rural Net"},"total_non_rural_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Non Rural Gross"},"total_non_rural_net":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Non Rural Net"},"total_profit":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Profit"},"completed_buys":{"type":"integer","title":"Completed Buys"},"active_buys":{"type":"integer","title":"Active Buys"}},"type":"object","required":["total_buys","total_gross","total_net","total_rural_gross","total_rural_net","total_non_rural_gross","total_non_rural_net","total_profit","completed_buys","active_buys"],"title":"RadioBuyFinancialSummary","description":"Aggregated financial summary for radio buys."},"cbradio__RadioBuyPlacement":{"properties":{"id":{"type":"string","title":"Id"},"radio_buy_id":{"type":"string","title":"Radio Buy Id"},"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"station_callsign":{"type":"string","title":"Station Callsign"},"daypart":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Daypart"},"spots_per_week":{"type":"integer","title":"Spots Per Week"},"rate":{"type":"number","title":"Rate"},"weeks":{"items":{"type":"integer"},"type":"array","title":"Weeks"},"total_spots":{"type":"integer","title":"Total Spots"},"total_cost":{"type":"number","title":"Total Cost"},"source_rate_card_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Rate Card Id"},"simulcast_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulcast Group Id"},"simulcast_callsigns":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Simulcast Callsigns"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["id","radio_buy_id","station_callsign","spots_per_week","rate","total_spots","total_cost"],"title":"RadioBuyPlacement","description":"Radio buy placement (station-level line item)."},"cbradio__RadioBuySummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AdTypeEnum"},{"type":"null"}]},"election_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ElectionType"},{"type":"null"}]},"proposal_class":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ProposalClassEnum"},{"type":"null"}]},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"is_complete":{"type":"boolean","title":"Is Complete","default":false},"gross_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount"},"net_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount"},"rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Gross"},"rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Net"},"non_rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Gross"},"non_rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Net"},"buyer_agency_commission":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Commission"},"payments_to_stations":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Payments To Stations"},"consultant_rebate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Consultant Rebate"},"profit":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Profit"},"is_paid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Paid"},"paid_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid Date"},"amount_received":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Amount Received"},"cycle_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cycle Id"},"total_commissions":{"type":"number","title":"Total Commissions","default":0},"held_amount":{"type":"number","title":"Held Amount","default":0},"commission_rules":{"items":{"$ref":"#/components/schemas/cbradio__CommissionRuleSummary"},"type":"array","title":"Commission Rules","default":[]}},"type":"object","required":["id","name"],"title":"RadioBuySummary","description":"Radio buy summary for list views — includes financial fields."},"cbradio__RadioBuyUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AdTypeEnum"},{"type":"null"}]},"election_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ElectionType"},{"type":"null"}]},"proposal_class":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ProposalClassEnum"},{"type":"null"}]},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date"},"project_lead":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Lead"},"project_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Team"},"is_complete":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Complete"},"gross_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount"},"net_amount":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount"},"rural_gross":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Gross"},"rural_net":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Net"},"non_rural_gross":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Gross"},"non_rural_net":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Net"},"buyer_agency_commission":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Commission"},"payments_to_stations":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Payments To Stations"},"buyer_agency_rebate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Rebate"},"consultant_rebate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Consultant Rebate"},"profit":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Profit"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"consultant_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Consultant Ids"}},"type":"object","title":"RadioBuyUpdate","description":"Model for updating a radio buy (all fields optional)."},"cbradio__RadioBuyVersion":{"properties":{"change_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Summary"},"id":{"type":"string","title":"Id"},"buy_id":{"type":"string","title":"Buy Id"},"version_number":{"type":"integer","title":"Version Number"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross","default":"0"},"total_station_payments":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Station Payments","default":"0"},"total_commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Commission","default":"0"},"total_spots":{"type":"integer","title":"Total Spots","default":0},"station_count":{"type":"integer","title":"Station Count","default":0},"has_provisional":{"type":"boolean","title":"Has Provisional","default":false}},"type":"object","required":["id","buy_id","version_number","created_at"],"title":"RadioBuyVersion","description":"Radio buy version summary."},"cbradio__RadioBuyVersionCreate":{"properties":{"change_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Summary"}},"type":"object","title":"RadioBuyVersionCreate","description":"Fields for creating a new version."},"cbradio__RadioBuyVersionDetail":{"properties":{"change_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Summary"},"id":{"type":"string","title":"Id"},"buy_id":{"type":"string","title":"Buy Id"},"version_number":{"type":"integer","title":"Version Number"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"total_gross":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Gross","default":"0"},"total_station_payments":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Station Payments","default":"0"},"total_commission":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Total Commission","default":"0"},"total_spots":{"type":"integer","title":"Total Spots","default":0},"station_count":{"type":"integer","title":"Station Count","default":0},"has_provisional":{"type":"boolean","title":"Has Provisional","default":false},"commission_breakdown":{"$ref":"#/components/schemas/cbradio__CommissionBreakdown"},"payee_groups":{"items":{"$ref":"#/components/schemas/cbradio__PayeeGroupWithStatus"},"type":"array","title":"Payee Groups"}},"type":"object","required":["id","buy_id","version_number","created_at"],"title":"RadioBuyVersionDetail","description":"Full radio buy version with payee groups and commission breakdown."},"cbradio__RadioBuyWithRelations":{"properties":{"name":{"type":"string","title":"Name","description":"Campaign/buy name"},"client_id":{"type":"string","title":"Client Id","description":"Reference to client"},"buyer_agency_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Agency Id","description":"Reference to buyer agency"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__AdTypeEnum"},{"type":"null"}],"description":"candidate or issue"},"election_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ElectionType"},{"type":"null"}],"description":"primary or general"},"proposal_class":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ProposalClassEnum"},{"type":"null"}],"description":"Political-window classification: candidate_in_window / candidate_out_of_window / issue. NULL passes the window_mismatch gate (legacy backwards compat); set explicitly to enforce."},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Campaign start date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Campaign end date"},"election_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Election Date","description":"Election date"},"project_lead":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Lead","description":"Project lead name"},"project_team":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Team","description":"Comma-separated team member names"},"is_complete":{"type":"boolean","title":"Is Complete","description":"Whether the buy is complete","default":false},"gross_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Amount","description":"Total gross amount"},"net_amount":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Amount","description":"Total net amount"},"rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Gross","description":"Rural gross amount"},"rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Rural Net","description":"Rural net amount"},"non_rural_gross":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Gross","description":"Non-rural gross amount"},"non_rural_net":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Non Rural Net","description":"Non-rural net amount"},"buyer_agency_commission":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Commission","description":"Agency commission"},"payments_to_stations":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Payments To Stations","description":"Total station payments"},"buyer_agency_rebate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Buyer Agency Rebate","description":"Agency rebate"},"consultant_rebate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Consultant Rebate","description":"Consultant rebate"},"profit":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Profit","description":"Profit (can be negative)"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"airtable_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Name","description":"Original Airtable name for reference"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"client":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ClientSummary"},{"type":"null"}]},"buyer_agency":{"anyOf":[{"$ref":"#/components/schemas/cbradio__BuyerAgencySummary"},{"type":"null"}]},"consultants":{"items":{"$ref":"#/components/schemas/cbradio__ConsultantSummary"},"type":"array","title":"Consultants","default":[]}},"type":"object","required":["name","client_id","id","created_at","updated_at"],"title":"RadioBuyWithRelations","description":"Radio buy with full related entities."},"cbradio__Rate":{"properties":{"station_id":{"type":"string","title":"Station Id","description":"Reference to station"},"effective_date":{"type":"string","format":"date","title":"Effective Date","description":"When this rate becomes effective"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date","description":"When this rate expires"},"ad_type":{"$ref":"#/components/schemas/cbradio__src__api__models__rate__AdType","description":"Candidate vs Issue advertising"},"rate_context":{"$ref":"#/components/schemas/cbradio__RateContext","description":"In-window vs out-of-window","default":"all"},"preemption":{"$ref":"#/components/schemas/cbradio__PreemptionStatus","description":"Preemption status","default":"non_preemptible"},"slot_days":{"$ref":"#/components/schemas/cbradio__DayPattern","description":"Days pattern","default":"all"},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time","description":"Time slot start"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time","description":"Time slot end"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name","description":"Named time slot"},"duration_seconds":{"$ref":"#/components/schemas/cbradio__SpotDuration","description":"Spot duration"},"gross_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate","description":"Gross rate (before commission)"},"net_rate":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate","description":"Net rate (after commission)"},"rate_card_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Card Year","description":"Year of rate card"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id","description":"Source document reference"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_lur":{"type":"boolean","title":"Is Lur","description":"Is this a Lowest Unit Rate","default":false},"id":{"type":"string","title":"Id","description":"Unique identifier"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"station_callsign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Callsign"},"station_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station State"},"rate_card_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Card Id","description":"Associated rate card ID"},"rate_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Source","description":"Origin of rate data"},"rate_card_review_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Card Review Status","description":"QA verification status"},"rate_last_verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Rate Last Verified At","description":"When rate was last verified"},"rate_last_verified_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Last Verified By","description":"User who verified the rate"}},"type":"object","required":["station_id","effective_date","ad_type","duration_seconds","id"],"title":"Rate","description":"Complete rate model with database fields."},"cbradio__RateCardBackfillRequest":{"properties":{"station_id":{"type":"string","title":"Station Id"},"callsign":{"type":"string","title":"Callsign"},"source":{"type":"string","title":"Source","default":"airtable_2026"},"year":{"type":"integer","title":"Year","default":2026}},"type":"object","required":["station_id","callsign"],"title":"RateCardBackfillRequest","description":"Request to create a rate_card and link orphaned rates."},"cbradio__RateCardBackfillResponse":{"properties":{"rate_card_id":{"type":"string","title":"Rate Card Id"},"created":{"type":"boolean","title":"Created"},"rates_linked":{"type":"integer","title":"Rates Linked"}},"type":"object","required":["rate_card_id","created","rates_linked"],"title":"RateCardBackfillResponse","description":"Response for rate card backfill."},"cbradio__RateCardCreateWithRates":{"properties":{"station_id":{"type":"string","title":"Station Id"},"year":{"type":"integer","title":"Year"},"rate_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Type","description":"e.g., 'Political', 'Gross', 'Net'"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"source":{"type":"string","title":"Source","description":"Ingestion source (manual, katz, iheart, airtable_2026, amfm)","default":"manual"},"rates":{"items":{"$ref":"#/components/schemas/cbradio__RateCreateForCard"},"type":"array","title":"Rates"}},"type":"object","required":["station_id","year"],"title":"RateCardCreateWithRates","description":"Create rate card with nested rates in one operation."},"cbradio__RateCardUpdate":{"properties":{"year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"},"rate_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Type"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"RateCardUpdate","description":"Model for partial rate card updates."},"cbradio__RateCardWithRates":{"properties":{"station_id":{"type":"string","title":"Station Id"},"year":{"type":"integer","title":"Year"},"rate_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate Type","description":"e.g., 'Political', 'Gross', 'Net'"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"source":{"type":"string","title":"Source","description":"Ingestion source (manual, katz, iheart, airtable_2026, amfm)","default":"manual"},"id":{"type":"string","title":"Id"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"rate_count":{"type":"integer","title":"Rate Count","default":0},"rates":{"items":{"$ref":"#/components/schemas/cbradio__Rate"},"type":"array","title":"Rates"},"station_callsign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Callsign"}},"type":"object","required":["station_id","year","id"],"title":"RateCardWithRates","description":"Rate card with embedded rates for read operations."},"cbradio__RateContext":{"type":"string","enum":["in_window","out_of_window","all"],"title":"RateContext","description":"Context/window for the rate."},"cbradio__RateCorrection":{"properties":{"station_callsign":{"type":"string","title":"Station Callsign","description":"Station callsign"},"facility_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Facility Id","description":"FCC facility ID for disambiguation"},"daypart":{"type":"string","title":"Daypart","description":"Daypart being corrected (e.g., 'Morning Drive')"},"spot_length_seconds":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Length Seconds","description":"Spot duration"},"original_gross_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Original Gross Rate","description":"Rate before correction"},"corrected_gross_rate":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Corrected Gross Rate","description":"New corrected rate"},"effective_start_date":{"type":"string","format":"date","title":"Effective Start Date","description":"When correction takes effect"},"effective_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective End Date","description":"When correction expires"},"reason":{"type":"string","minLength":1,"title":"Reason","description":"Why the correction is needed"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Additional context"},"status":{"$ref":"#/components/schemas/cbradio__CorrectionStatus","description":"Correction status","default":"pending_review"},"id":{"type":"string","title":"Id","description":"Unique identifier"},"created_by":{"type":"string","title":"Created By","description":"User who created the correction"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When correction was created"},"verified_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verified By","description":"Admin who verified"},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"When verified"},"verification_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verification Notes","description":"Verification notes"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["station_callsign","daypart","spot_length_seconds","original_gross_rate","corrected_gross_rate","effective_start_date","reason","id","created_by","created_at"],"title":"RateCorrection","description":"Complete rate correction with database fields."},"cbradio__RateCorrectionCreate":{"properties":{"station_callsign":{"type":"string","title":"Station Callsign","description":"Station callsign"},"facility_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Facility Id","description":"FCC facility ID for disambiguation"},"daypart":{"type":"string","title":"Daypart","description":"Daypart being corrected (e.g., 'Morning Drive')"},"spot_length_seconds":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Length Seconds","description":"Spot duration"},"original_gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Original Gross Rate","description":"Rate before correction"},"corrected_gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Corrected Gross Rate","description":"New corrected rate"},"effective_start_date":{"type":"string","format":"date","title":"Effective Start Date","description":"When correction takes effect"},"effective_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective End Date","description":"When correction expires"},"reason":{"type":"string","minLength":1,"title":"Reason","description":"Why the correction is needed"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Additional context"},"status":{"$ref":"#/components/schemas/cbradio__CorrectionStatus","description":"Correction status","default":"pending_review"}},"type":"object","required":["station_callsign","daypart","spot_length_seconds","original_gross_rate","corrected_gross_rate","effective_start_date","reason"],"title":"RateCorrectionCreate","description":"Model for creating a rate correction."},"cbradio__RateCorrectionVerify":{"properties":{"verified":{"type":"boolean","title":"Verified","description":"True to verify, False to reject"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Verification notes"}},"type":"object","required":["verified"],"title":"RateCorrectionVerify","description":"Request to verify or reject a rate correction."},"cbradio__RateCreate":{"properties":{"station_id":{"type":"string","title":"Station Id","description":"Reference to station"},"effective_date":{"type":"string","format":"date","title":"Effective Date","description":"When this rate becomes effective"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date","description":"When this rate expires"},"ad_type":{"$ref":"#/components/schemas/cbradio__src__api__models__rate__AdType","description":"Candidate vs Issue advertising"},"rate_context":{"$ref":"#/components/schemas/cbradio__RateContext","description":"In-window vs out-of-window","default":"all"},"preemption":{"$ref":"#/components/schemas/cbradio__PreemptionStatus","description":"Preemption status","default":"non_preemptible"},"slot_days":{"$ref":"#/components/schemas/cbradio__DayPattern","description":"Days pattern","default":"all"},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time","description":"Time slot start"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time","description":"Time slot end"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name","description":"Named time slot"},"duration_seconds":{"$ref":"#/components/schemas/cbradio__SpotDuration","description":"Spot duration"},"gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate","description":"Gross rate (before commission)"},"net_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate","description":"Net rate (after commission)"},"rate_card_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Card Year","description":"Year of rate card"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id","description":"Source document reference"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_lur":{"type":"boolean","title":"Is Lur","description":"Is this a Lowest Unit Rate","default":false}},"type":"object","required":["station_id","effective_date","ad_type","duration_seconds"],"title":"RateCreate","description":"Model for creating a new rate."},"cbradio__RateCreateForCard":{"properties":{"effective_date":{"type":"string","format":"date","title":"Effective Date","description":"When this rate becomes effective"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"ad_type":{"$ref":"#/components/schemas/cbradio__src__api__models__rate__AdType","description":"Candidate vs Issue advertising"},"rate_context":{"$ref":"#/components/schemas/cbradio__RateContext","description":"In-window vs out-of-window","default":"all"},"preemption":{"$ref":"#/components/schemas/cbradio__PreemptionStatus","description":"Preemption status","default":"non_preemptible"},"slot_days":{"$ref":"#/components/schemas/cbradio__DayPattern","description":"Days pattern","default":"all"},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time","description":"Time slot start"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time","description":"Time slot end"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name","description":"Named time slot"},"duration_seconds":{"$ref":"#/components/schemas/cbradio__SpotDuration","description":"Spot duration in seconds"},"gross_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate","description":"Gross rate (before commission)"},"net_rate":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate","description":"Net rate (after commission)"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id","description":"Source document reference"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_lur":{"type":"boolean","title":"Is Lur","description":"Is this a Lowest Unit Rate","default":false}},"type":"object","required":["effective_date","ad_type","duration_seconds"],"title":"RateCreateForCard","description":"Rate creation model when nested inside a rate card.\n\nOmits station_id and rate_card_id (inferred from parent)."},"cbradio__RateData":{"properties":{"ad_type":{"type":"string","title":"Ad Type"},"rate_context":{"type":"string","title":"Rate Context","default":"all"},"preemption":{"type":"string","title":"Preemption"},"slot_days":{"type":"string","title":"Slot Days"},"slot_start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Start Time"},"slot_end_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot End Time"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name"},"duration_seconds":{"type":"integer","title":"Duration Seconds"},"gross_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gross Rate"},"net_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Net Rate"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["ad_type","preemption","slot_days","duration_seconds"],"title":"RateData","description":"A single extracted rate."},"cbradio__RateUpdate":{"properties":{"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__src__api__models__rate__AdType"},{"type":"null"}]},"rate_context":{"anyOf":[{"$ref":"#/components/schemas/cbradio__RateContext"},{"type":"null"}]},"preemption":{"anyOf":[{"$ref":"#/components/schemas/cbradio__PreemptionStatus"},{"type":"null"}]},"slot_days":{"anyOf":[{"$ref":"#/components/schemas/cbradio__DayPattern"},{"type":"null"}]},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name"},"duration_seconds":{"anyOf":[{"$ref":"#/components/schemas/cbradio__SpotDuration"},{"type":"null"}]},"gross_rate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate"},"net_rate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate"},"rate_card_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Card Year"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_lur":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Lur"}},"type":"object","title":"RateUpdate","description":"Model for updating a rate (all fields optional)."},"cbradio__RateUpdateWithId":{"properties":{"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date"},"expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expiration Date"},"ad_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__src__api__models__rate__AdType"},{"type":"null"}]},"rate_context":{"anyOf":[{"$ref":"#/components/schemas/cbradio__RateContext"},{"type":"null"}]},"preemption":{"anyOf":[{"$ref":"#/components/schemas/cbradio__PreemptionStatus"},{"type":"null"}]},"slot_days":{"anyOf":[{"$ref":"#/components/schemas/cbradio__DayPattern"},{"type":"null"}]},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name"},"duration_seconds":{"anyOf":[{"$ref":"#/components/schemas/cbradio__SpotDuration"},{"type":"null"}]},"gross_rate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gross Rate"},"net_rate":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Net Rate"},"rate_card_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Card Year"},"source_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Document Id"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_lur":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Lur"},"id":{"type":"string","title":"Id","description":"Rate ID to update"}},"type":"object","required":["id"],"title":"RateUpdateWithId","description":"Rate update with explicit ID for bulk operations."},"cbradio__RateWriteItem":{"properties":{"daypart_id":{"type":"string","title":"Daypart Id","description":"One of 13 standard daypart IDs"},"spot_length":{"type":"integer","title":"Spot Length","description":"Spot duration: 30 or 60 seconds"},"gross_rate":{"type":"number","exclusiveMinimum":0.0,"title":"Gross Rate","description":"Gross rate in dollars"},"source":{"type":"string","title":"Source","description":"Rate provenance: extracted, corrected, manual","default":"manual"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["daypart_id","spot_length","gross_rate"],"title":"RateWriteItem","description":"A single rate to write (used by both #53 and #54)."},"cbradio__RecipientType":{"type":"string","enum":["salesman","agency","consultant","station","other"],"title":"RecipientType","description":"Commission recipient role."},"cbradio__ReextractGapsResponse":{"properties":{"total":{"type":"integer","title":"Total","description":"Gap documents attempted in this call"},"succeeded":{"type":"integer","title":"Succeeded","description":"Successfully reprocessed"},"failed":{"type":"integer","title":"Failed","description":"Failed to reprocess"},"remaining":{"type":"integer","title":"Remaining","description":"Gap documents still waiting after this call. Each one is a synchronous LLM round trip, so a run is bounded and an operator works the queue down across several calls.","default":0},"errors":{"items":{"type":"string"},"type":"array","title":"Errors","description":"Error messages"}},"type":"object","required":["total","succeeded","failed"],"title":"ReextractGapsResponse","description":"Response from reprocessing gap documents."},"cbradio__RejectProposal":{"properties":{"reason":{"type":"string","minLength":1,"title":"Reason"}},"type":"object","required":["reason"],"title":"RejectProposal","description":"Request to mark proposal as rejected."},"cbradio__RejectRequest":{"properties":{"reason":{"type":"string","minLength":1,"title":"Reason","description":"Reason for rejection"}},"type":"object","required":["reason"],"title":"RejectRequest","description":"Request model for rejecting a staged record."},"cbradio__ReorderItem":{"properties":{"id":{"type":"string","title":"Id"},"rotation_order":{"type":"integer","minimum":1.0,"title":"Rotation Order"}},"type":"object","required":["id","rotation_order"],"title":"ReorderItem","description":"Single item in reorder request."},"cbradio__ReorderRequest":{"properties":{"order":{"items":{"$ref":"#/components/schemas/cbradio__ReorderItem"},"type":"array","title":"Order"}},"type":"object","required":["order"],"title":"ReorderRequest","description":"Request model for reordering audio spots."},"cbradio__ReviewApprovalBody":{"properties":{"corrections":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Corrections"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"approved_rates":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbradio__ApprovedRateGrid"},"type":"array"},{"type":"null"}],"title":"Approved Rates"}},"type":"object","title":"ReviewApprovalBody","description":"Request body for POST /rate-cards/{id}/review when decision=approved."},"cbradio__SRMAlert":{"properties":{"id":{"type":"string","title":"Id","description":"Unique identifier"},"type":{"$ref":"#/components/schemas/cbradio__AlertType","description":"Alert type"},"station_callsign":{"type":"string","title":"Station Callsign","description":"Related station"},"title":{"type":"string","title":"Title","description":"Alert title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Alert details"},"dismissed":{"type":"boolean","title":"Dismissed","description":"Whether dismissed","default":false},"dismissed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Dismissed At","description":"When dismissed"},"dismissed_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dismissed By User Id","description":"Who dismissed"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"}},"type":"object","required":["id","type","station_callsign","title","created_at"],"title":"SRMAlert","description":"System-generated alert."},"cbradio__SRMTask":{"properties":{"id":{"type":"string","title":"Id","description":"Unique identifier"},"station_callsign":{"type":"string","title":"Station Callsign","description":"Station callsign"},"task_type":{"$ref":"#/components/schemas/cbradio__TaskType","description":"Type of task"},"title":{"type":"string","title":"Title","description":"Task title"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why task was generated"},"priority":{"type":"integer","maximum":3.0,"minimum":0.0,"title":"Priority","description":"Priority (0=highest, 3=lowest)"},"status":{"$ref":"#/components/schemas/cbradio__TaskStatus","description":"Task status"},"owner_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner User Id","description":"Assigned user ID"},"owner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Name","description":"Assigned user name"},"due_date":{"type":"string","format":"date","title":"Due Date","description":"Task due date"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"When completed"},"completed_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed By User Id","description":"Who completed"},"completion_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completion Notes","description":"Completion notes"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"},"station":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Station","description":"Station summary"}},"type":"object","required":["id","station_callsign","task_type","title","priority","status","due_date","created_at","updated_at"],"title":"SRMTask","description":"SRM task for sales rep outreach."},"cbradio__ScoreRequest":{"properties":{"callsigns":{"items":{"type":"string"},"type":"array","maxItems":500,"minItems":1,"title":"Callsigns","description":"Station callsigns to score"},"audience":{"type":"string","title":"Audience","description":"Target audience: republican_primary, democratic_primary, swing_general","default":"republican_primary"},"states":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"States","description":"Target states for coverage scoring (e.g., ['TN', 'MI'])"}},"type":"object","required":["callsigns"],"title":"ScoreRequest","description":"Request to score stations for a target audience/geography."},"cbradio__SearchCountsRequest":{"properties":{"state":{"type":"string","maxLength":2,"minLength":2,"title":"State","description":"Two-letter US state abbreviation. Case-insensitive.","examples":["NH","FL"]},"county":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"County","description":"Numeric i360 county ID. Omit for statewide totals. Get IDs from i360 county list.","examples":[306,307]},"affinity_min":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Affinity Min","description":"Minimum political affinity score. Must pair with affinity_max or API returns zeros.","examples":[40,65]},"affinity_max":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Affinity Max","description":"Maximum political affinity score. Must pair with affinity_min.","examples":[55,100]},"propensity_min":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Propensity Min","description":"Minimum voting propensity score (1=most likely). Must pair with propensity_max.","examples":[1]},"propensity_max":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Propensity Max","description":"Maximum voting propensity score. Must pair with propensity_min.","examples":[4,6]}},"type":"object","required":["state"],"title":"SearchCountsRequest","description":"Request parameters for a single voter universe count query.\n\nReturns aggregate counts for a state or state+county combination,\noptionally filtered by political affinity and voting propensity ranges.\nMuch faster than county-report (single API call vs N*3 calls).","example":{"affinity_max":55,"affinity_min":40,"county":306,"propensity_max":4,"propensity_min":1,"state":"FL"}},"cbradio__SearchCountsResponse":{"properties":{"state":{"type":"string","title":"State","description":"State that was queried.","examples":["FL"]},"county":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"County","description":"County ID if a county filter was applied, null for statewide.","examples":[306,null]},"individuals":{"type":"integer","title":"Individuals","description":"Total individuals matching query. This is the primary 'universe size' number.","examples":[4048,1309995]},"residences":{"type":"integer","title":"Residences","description":"Unique residential addresses. Always <= individuals. Use for household-level targeting.","examples":[3855,564277]},"apartments":{"type":"integer","title":"Apartments","description":"Subset of residences that are apartment units.","examples":[1447,200569]},"mailing_addresses":{"type":"integer","title":"Mailing Addresses","description":"Deliverable postal addresses. Use for direct mail piece counts.","examples":[3867,578094]},"landlines":{"type":"integer","title":"Landlines","description":"Available landline phone numbers. Use for phone banking estimates.","examples":[609,204792]},"mobiles":{"type":"integer","title":"Mobiles","description":"Available mobile phone numbers. Use for P2P texting estimates.","examples":[2187,628521]}},"type":"object","required":["state","individuals","residences","apartments","mailing_addresses","landlines","mobiles"],"title":"SearchCountsResponse","description":"Aggregate voter universe counts from i360."},"cbradio__SearchResponse":{"properties":{"query":{"type":"string","title":"Query"},"total":{"type":"integer","title":"Total"},"issues":{"items":{"$ref":"#/components/schemas/cbradio__Issue"},"type":"array","title":"Issues"}},"type":"object","required":["query","total","issues"],"title":"SearchResponse","description":"Response for cross-project search."},"cbradio__SendOrderRequest":{"properties":{"sender_name":{"type":"string","maxLength":255,"minLength":1,"title":"Sender Name"},"sender_title":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Sender Title"},"additional_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Additional Message"}},"type":"object","required":["sender_name"],"title":"SendOrderRequest","description":"Request model for sending order packet email."},"cbradio__SendOrderResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"sent_at":{"type":"string","format":"date-time","title":"Sent At"},"email_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Id"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"}},"type":"object","required":["success","sent_at"],"title":"SendOrderResponse","description":"Response after sending order."},"cbradio__SendProposal":{"properties":{"expires_in_days":{"type":"integer","maximum":90.0,"minimum":1.0,"title":"Expires In Days","default":30},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"SendProposal","description":"Request to send a proposal to client."},"cbradio__ShareLinkResponse":{"properties":{"share_url":{"type":"string","title":"Share Url","description":"The shareable short URL"},"slug":{"type":"string","title":"Slug","description":"The slug portion of the URL"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["share_url","slug","expires_at","created_at"],"title":"ShareLinkResponse","description":"Response model for share link creation."},"cbradio__ShareLinkStatusResponse":{"properties":{"share_url":{"type":"string","title":"Share Url"},"slug":{"type":"string","title":"Slug"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"created_by":{"type":"string","title":"Created By"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"is_expired":{"type":"boolean","title":"Is Expired"},"is_revoked":{"type":"boolean","title":"Is Revoked"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"},"revoked_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked By"},"view_count":{"type":"integer","title":"View Count","default":0},"unique_view_count":{"type":"integer","title":"Unique View Count","default":0},"last_viewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Viewed At"},"cblinks_stats":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Cblinks Stats","description":"Analytics from CBLinks service"}},"type":"object","required":["share_url","slug","created_at","created_by","expires_at","is_expired","is_revoked"],"title":"ShareLinkStatusResponse","description":"Response model for share link status."},"cbradio__SimulcastGroupCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"primary_callsign":{"type":"string","maxLength":20,"minLength":1,"title":"Primary Callsign"},"callsigns":{"items":{"type":"string"},"type":"array","minItems":2,"title":"Callsigns"},"rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name","primary_callsign","callsigns"],"title":"SimulcastGroupCreate","description":"Request model for creating a simulcast group."},"cbradio__SimulcastGroupResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"primary_callsign":{"type":"string","title":"Primary Callsign"},"callsigns":{"items":{"type":"string"},"type":"array","title":"Callsigns"},"rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","primary_callsign","callsigns","created_at","updated_at"],"title":"SimulcastGroupResponse","description":"Response model for a simulcast group."},"cbradio__SimulcastGroupUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"primary_callsign":{"anyOf":[{"type":"string","maxLength":20,"minLength":1},{"type":"null"}],"title":"Primary Callsign"},"callsigns":{"anyOf":[{"items":{"type":"string"},"type":"array","minItems":2},{"type":"null"}],"title":"Callsigns"},"rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"SimulcastGroupUpdate","description":"Request model for updating a simulcast group."},"cbradio__SpotBreakdownType":{"type":"string","enum":["weekly","daily","flight"],"title":"SpotBreakdownType","description":"How spots are broken down in the order."},"cbradio__SpotDuration":{"type":"integer","enum":[15,30,60,90,120],"title":"SpotDuration","description":"Standard spot durations in seconds."},"cbradio__StationContact":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"}},"type":"object","title":"StationContact","description":"Contact information for a station."},"cbradio__StationCoverage":{"properties":{"callsign":{"type":"string","title":"Callsign","description":"Station callsign.","examples":["WITL-FM"]},"facility_id":{"type":"string","title":"Facility Id","description":"FCC facility ID.","examples":["46706"]},"coverage_pct":{"type":"number","title":"Coverage Pct","description":"Percentage of the district covered by this station (0-100).","examples":[100.0,85.5]},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format","description":"Station format.","examples":["Country"]},"nielsen_dma":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Dma","description":"Nielsen DMA market.","examples":["Lansing, MI"]},"is_in_network":{"type":"boolean","title":"Is In Network","description":"Whether the station is in-network.","default":false},"simulcast_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulcast With","description":"Simulcast partner callsign, if any.","examples":["WCAB-FM"]}},"type":"object","required":["callsign","facility_id","coverage_pct"],"title":"StationCoverage","description":"A station covering a given district."},"cbradio__StationDelta":{"properties":{"station_callsign":{"type":"string","title":"Station Callsign"},"station_id":{"type":"string","title":"Station Id"},"change_type":{"type":"string","title":"Change Type"},"old_values":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Old Values"},"new_values":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"New Values"}},"type":"object","required":["station_callsign","station_id","change_type"],"title":"StationDelta","description":"Change summary for a single station."},"cbradio__StationFormat":{"type":"string","enum":["talk","news_talk","classic_hits","classic_rock","country","hot_ac","adult_contemporary","regional_mexican","southern_gospel","sports","religious","other"],"title":"StationFormat","description":"Radio station programming formats."},"cbradio__StationPool":{"type":"string","enum":["all","network_only"],"title":"StationPool","description":"Station pool selection for proposals."},"cbradio__StationRatingsResponse":{"properties":{"callsign":{"type":"string","title":"Callsign","description":"Station callsign (uppercased).","examples":["WLTW-FM"]},"ratings":{"items":{"$ref":"#/components/schemas/cbradio__NielsenRating"},"type":"array","title":"Ratings","description":"All rating entries for this callsign, ordered by market_rank then most recent first."}},"type":"object","required":["callsign","ratings"],"title":"StationRatingsResponse","description":"All Nielsen ratings for a specific station across markets and survey periods."},"cbradio__StationSRM":{"properties":{"callsign":{"type":"string","title":"Callsign","description":"Station callsign (FK to station)"},"lifecycle_state":{"$ref":"#/components/schemas/cbradio__src__api__models__srm__LifecycleState","description":"Current lifecycle state","default":"prospect"},"lifecycle_state_changed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lifecycle State Changed At","description":"When lifecycle state last changed"},"last_contacted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Contacted At","description":"Most recent contact timestamp"},"last_contact_outcome":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ContactOutcome"},{"type":"null"}],"description":"Outcome of last contact"},"last_contact_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Contact By User Id","description":"User who made last contact"},"agreement_start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Agreement Start Date","description":"Current agreement start date"},"agreement_expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Agreement Expiration Date","description":"Current agreement expiration date"},"previous_owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous Owner","description":"Previous owner if changed"},"ownership_changed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ownership Changed At","description":"When ownership changed"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"General notes"},"outreach_priority_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Outreach Priority Score","description":"Outreach priority score (0-100), higher = more urgent"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["callsign","created_at","updated_at"],"title":"StationSRM","description":"SRM data for a station.","example":{"agreement_expiration_date":"2026-05-31","agreement_start_date":"2025-06-01","callsign":"WAAA-FM","last_contact_outcome":"meeting_scheduled","last_contacted_at":"2026-01-10T14:30:00Z","lifecycle_state":"active","notes":"Key account, quarterly check-ins"}},"cbradio__StationSRMUpdate":{"properties":{"lifecycle_state":{"anyOf":[{"$ref":"#/components/schemas/cbradio__src__api__models__srm__LifecycleState"},{"type":"null"}],"description":"New lifecycle state"},"agreement_start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Agreement Start Date","description":"Agreement start date"},"agreement_expiration_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Agreement Expiration Date","description":"Agreement expiration date"},"previous_owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous Owner","description":"Previous owner"},"ownership_changed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ownership Changed At","description":"When ownership changed"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"General notes"}},"type":"object","title":"StationSRMUpdate","description":"Request model for updating SRM data."},"cbradio__StationStatus":{"type":"string","enum":["signed","pending","inactive","prospect","exclusive"],"title":"StationStatus","description":"Station network status."},"cbradio__StationStatusUpdate":{"properties":{"status":{"type":"string","title":"Status","description":"New status for the station: signed, pending, inactive, prospect","examples":["inactive"]}},"type":"object","required":["status"],"title":"StationStatusUpdate","description":"Request model for updating station status."},"cbradio__StationUpdate":{"properties":{"frequency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Frequency"},"format":{"anyOf":[{"$ref":"#/components/schemas/cbradio__StationFormat"},{"type":"null"}]},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"state_full":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State Full"},"nielsen_dma":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Dma"},"nielsen_msa":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nielsen Msa"},"mailing_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Address"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"},"ownership_group":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ownership Group"},"ownership_group_override":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ownership Group Override"},"contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Email"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name"},"mailing_street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Street"},"mailing_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing City"},"mailing_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing State"},"mailing_zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mailing Zip"},"primary_contact":{"anyOf":[{"$ref":"#/components/schemas/cbradio__StationContact"},{"type":"null"}]},"general_manager":{"anyOf":[{"$ref":"#/components/schemas/cbradio__StationContact"},{"type":"null"}]},"sales_manager":{"anyOf":[{"$ref":"#/components/schemas/cbradio__StationContact"},{"type":"null"}]},"program_director":{"anyOf":[{"$ref":"#/components/schemas/cbradio__StationContact"},{"type":"null"}]},"billing_manager":{"anyOf":[{"$ref":"#/components/schemas/cbradio__StationContact"},{"type":"null"}]},"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__StationStatus"},{"type":"null"}]},"is_in_network":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is In Network"},"commission_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Commission Rate"},"status_tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Status Tags"},"status_tags_scope":{"anyOf":[{"type":"string","const":"airtable"},{"type":"null"}],"title":"Status Tags Scope"},"simulcast_with":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulcast With"},"multicast_stations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Multicast Stations"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"airtable_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Id"},"airtable_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Status"},"airtable_synced_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Airtable Synced At"}},"type":"object","title":"StationUpdate","description":"Model for updating a station (all fields optional)."},"cbradio__StationVoterResponse":{"properties":{"callsign":{"type":"string","title":"Callsign","description":"Station callsign.","examples":["WITL-FM"]},"facility_id":{"type":"string","title":"Facility Id","description":"FCC facility ID.","examples":["46706"]},"simulcast_partners":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Simulcast Partners","description":"Simulcast partner callsigns whose coverage was unioned. Null if no partners.","examples":[["WCAB-FM"]]},"coverage_county_count":{"type":"integer","title":"Coverage County Count","description":"Number of counties in the station's coverage area.","examples":[14]},"counties_with_voter_data":{"type":"integer","title":"Counties With Voter Data","description":"Counties that have i360 voter data.","examples":[12]},"voter_counts":{"$ref":"#/components/schemas/cbradio__VoterCounts"},"estimated_listeners":{"$ref":"#/components/schemas/cbradio__EstimatedListeners"},"data_freshness":{"$ref":"#/components/schemas/cbradio__DataFreshness","description":"Freshness window for the i360 voter data (cbradio#252)."},"coverage_counties":{"items":{"$ref":"#/components/schemas/cbradio__CountyCoverage"},"type":"array","title":"Coverage Counties","description":"Per-county breakdown ordered by coverage percentage descending."}},"type":"object","required":["callsign","facility_id","coverage_county_count","counties_with_voter_data","voter_counts","estimated_listeners","coverage_counties"],"title":"StationVoterResponse","description":"Complete voter data for a station's coverage area."},"cbradio__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbradio__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbradio__SuggestStationsRequest":{"properties":{"target_geography":{"additionalProperties":true,"type":"object","title":"Target Geography","description":"e.g. {states: ['TX'], districts: ['TX-SD3']}"},"budget":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Budget"},"campaign_type":{"$ref":"#/components/schemas/cbradio__CampaignType","default":"candidate_in_window"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"station_pool":{"$ref":"#/components/schemas/cbradio__StationPool","default":"all"},"spot_duration":{"type":"integer","maximum":120.0,"minimum":15.0,"title":"Spot Duration","default":60},"spots_per_week":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Spots Per Week","default":10},"max_stations":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Max Stations","default":50}},"type":"object","required":["target_geography","budget","start_date","end_date"],"title":"SuggestStationsRequest","description":"Request for station suggestions (non-committing)."},"cbradio__SuggestionAction":{"type":"string","enum":["accepted","rejected","modified","ignored"],"title":"SuggestionAction","description":"User action on an AI suggestion."},"cbradio__SyncStatusResponse":{"properties":{"status":{"type":"string","title":"Status"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"},"pid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pid"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"last_run_summary":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Last Run Summary"},"log_tail":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Log Tail"}},"type":"object","required":["status"],"title":"SyncStatusResponse","description":"Current or last sync pipeline status."},"cbradio__SyncTriggerRequest":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run","description":"Preview mode — no DB writes","default":false},"skip_extraction":{"type":"boolean","title":"Skip Extraction","description":"Skip OCR/extraction (download + document records only)","default":false},"limit":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Limit","description":"Max documents to process in extraction step","default":50}},"type":"object","title":"SyncTriggerRequest","description":"Request body for triggering the sync pipeline."},"cbradio__SyncTriggerResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"log_file":{"type":"string","title":"Log File"}},"type":"object","required":["job_id","status","log_file"],"title":"SyncTriggerResponse","description":"Response from triggering the sync pipeline."},"cbradio__TaskCompleteRequest":{"properties":{"outcome":{"$ref":"#/components/schemas/cbradio__ContactOutcome","description":"Contact outcome"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Completion notes"},"contact_type":{"anyOf":[{"$ref":"#/components/schemas/cbradio__ContactType"},{"type":"null"}],"description":"Type of contact","default":"phone"},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name","description":"Person contacted"}},"type":"object","required":["outcome"],"title":"TaskCompleteRequest","description":"Request model for completing a task."},"cbradio__TaskList":{"properties":{"tasks":{"items":{"$ref":"#/components/schemas/cbradio__SRMTask"},"type":"array","title":"Tasks"},"total":{"type":"integer","title":"Total"},"due_today":{"type":"integer","title":"Due Today","default":0},"overdue":{"type":"integer","title":"Overdue","default":0}},"type":"object","required":["tasks","total"],"title":"TaskList","description":"Paginated list of tasks with summary stats."},"cbradio__TaskStatus":{"type":"string","enum":["pending","in_progress","completed","cancelled"],"title":"TaskStatus","description":"Task workflow states."},"cbradio__TaskType":{"type":"string","enum":["initial_outreach","follow_up","relationship_maintenance","agreement_renewal","win_back","ownership_change"],"title":"TaskType","description":"Types of SRM tasks."},"cbradio__TimeSlotAdjustment":{"properties":{"slot_days":{"anyOf":[{"$ref":"#/components/schemas/cbradio__DayPattern"},{"type":"null"}],"description":"Days pattern"},"slot_start_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot Start Time","description":"Time slot start"},"slot_end_time":{"anyOf":[{"type":"string","format":"time"},{"type":"null"}],"title":"Slot End Time","description":"Time slot end"},"slot_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slot Name","description":"Named time slot"}},"type":"object","title":"TimeSlotAdjustment","description":"Adjust time slot fields only - for AI correction workflow."},"cbradio__Token":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["access_token"],"title":"Token","description":"Token model for JWT authentication."},"cbradio__TrafficAssetCreate":{"properties":{"asset_type":{"$ref":"#/components/schemas/cbradio__AssetType"},"file_url":{"type":"string","maxLength":512,"title":"File Url"},"file_name":{"type":"string","maxLength":255,"title":"File Name"},"file_size":{"type":"integer","exclusiveMinimum":0.0,"title":"File Size"},"mime_type":{"type":"string","maxLength":100,"title":"Mime Type"},"label":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Label"},"duration_seconds":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Duration Seconds"},"rotation_order":{"type":"integer","minimum":1.0,"title":"Rotation Order","default":1},"rotation_weight":{"type":"integer","minimum":1.0,"title":"Rotation Weight","default":1}},"type":"object","required":["asset_type","file_url","file_name","file_size","mime_type"],"title":"TrafficAssetCreate","description":"Request model for creating a traffic asset."},"cbradio__TrafficAssetResponse":{"properties":{"id":{"type":"string","title":"Id"},"radio_buy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Radio Buy Id"},"asset_type":{"$ref":"#/components/schemas/cbradio__AssetType"},"file_url":{"type":"string","title":"File Url"},"file_name":{"type":"string","title":"File Name"},"file_size":{"type":"integer","title":"File Size"},"mime_type":{"type":"string","title":"Mime Type"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds"},"rotation_order":{"type":"integer","title":"Rotation Order"},"rotation_weight":{"type":"integer","title":"Rotation Weight"},"created_by":{"type":"string","title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","asset_type","file_url","file_name","file_size","mime_type","rotation_order","rotation_weight","created_by","created_at"],"title":"TrafficAssetResponse","description":"Response model for a traffic asset."},"cbradio__TrafficAssetUpdate":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Label"},"rotation_order":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Rotation Order"},"rotation_weight":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Rotation Weight"}},"type":"object","title":"TrafficAssetUpdate","description":"Request model for updating a traffic asset."},"cbradio__TrafficAssetsResponse":{"properties":{"audio_spots":{"items":{"$ref":"#/components/schemas/cbradio__TrafficAssetResponse"},"type":"array","title":"Audio Spots","default":[]},"nab_form":{"anyOf":[{"$ref":"#/components/schemas/cbradio__TrafficAssetResponse"},{"type":"null"}]}},"type":"object","title":"TrafficAssetsResponse","description":"Response model for listing traffic assets."},"cbradio__UploadUrlRequest":{"properties":{"file_name":{"type":"string","maxLength":255,"title":"File Name"},"mime_type":{"type":"string","maxLength":100,"title":"Mime Type"},"file_size":{"type":"integer","exclusiveMinimum":0.0,"title":"File Size"}},"type":"object","required":["file_name","mime_type","file_size"],"title":"UploadUrlRequest","description":"Request model for getting presigned upload URL."},"cbradio__UploadUrlResponse":{"properties":{"upload_url":{"type":"string","title":"Upload Url"},"file_key":{"type":"string","title":"File Key"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["upload_url","file_key","expires_at"],"title":"UploadUrlResponse","description":"Response model for presigned upload URL."},"cbradio__User":{"properties":{"username":{"type":"string","title":"Username"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"role":{"type":"string","title":"Role","default":"admin"},"id":{"type":"string","title":"Id"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["username","first_name","last_name","id","is_active","created_at","updated_at"],"title":"User","description":"Complete user model with all data."},"cbradio__UserSummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"UserSummary","description":"Embedded user summary."},"cbradio__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbradio__VerifyRateCardBody":{"properties":{"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","title":"VerifyRateCardBody","description":"Request body for POST /rate-cards/{id}/verify (cbradio#240).\n\nOptional note — e.g. \"Re-confirmed with iHeart 2026-05-08, all rates\naccurate for the next political flight window\"."},"cbradio__VerifyRateCardResponse":{"properties":{"rate_card_id":{"type":"string","title":"Rate Card Id"},"last_verified_by":{"type":"string","title":"Last Verified By"},"last_verified_at":{"type":"string","format":"date-time","title":"Last Verified At"},"last_verified_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Verified Note"},"is_stale":{"type":"boolean","title":"Is Stale"}},"type":"object","required":["rate_card_id","last_verified_by","last_verified_at","is_stale"],"title":"VerifyRateCardResponse","description":"Response for POST /rate-cards/{id}/verify (cbradio#240)."},"cbradio__VersionDelta":{"properties":{"from_version_id":{"type":"string","title":"From Version Id"},"from_version_number":{"type":"integer","title":"From Version Number"},"to_version_id":{"type":"string","title":"To Version Id"},"to_version_number":{"type":"integer","title":"To Version Number"},"stations_added":{"type":"integer","title":"Stations Added","default":0},"stations_removed":{"type":"integer","title":"Stations Removed","default":0},"stations_modified":{"type":"integer","title":"Stations Modified","default":0},"payee_groups_added":{"type":"integer","title":"Payee Groups Added","default":0},"payee_groups_removed":{"type":"integer","title":"Payee Groups Removed","default":0},"payee_groups_modified":{"type":"integer","title":"Payee Groups Modified","default":0},"gross_delta":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Gross Delta","default":"0"},"commission_delta":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Commission Delta","default":"0"},"station_payments_delta":{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$","title":"Station Payments Delta","default":"0"},"station_changes":{"items":{"$ref":"#/components/schemas/cbradio__StationDelta"},"type":"array","title":"Station Changes"},"payee_group_changes":{"items":{"$ref":"#/components/schemas/cbradio__PayeeGroupDelta"},"type":"array","title":"Payee Group Changes"}},"type":"object","required":["from_version_id","from_version_number","to_version_id","to_version_number"],"title":"VersionDelta","description":"Delta comparison between two versions."},"cbradio__VersionListResponse":{"properties":{"buy_id":{"type":"string","title":"Buy Id"},"total_versions":{"type":"integer","title":"Total Versions"},"versions":{"items":{"$ref":"#/components/schemas/cbradio__RadioBuyVersion"},"type":"array","title":"Versions"}},"type":"object","required":["buy_id","total_versions","versions"],"title":"VersionListResponse","description":"Response for listing versions of a radio buy."},"cbradio__VoterCounts":{"properties":{"swing_likely_voters":{"type":"integer","title":"Swing Likely Voters","description":"Coverage-weighted swing voters across all counties.","examples":[4500]},"swing_likely_hhs":{"type":"integer","title":"Swing Likely Hhs","description":"Coverage-weighted swing households.","examples":[4200]},"base_likely_voters":{"type":"integer","title":"Base Likely Voters","description":"Coverage-weighted base voters across all counties.","examples":[8900]},"base_likely_hhs":{"type":"integer","title":"Base Likely Hhs","description":"Coverage-weighted base households.","examples":[8400]},"total_registered":{"type":"integer","title":"Total Registered","description":"Coverage-weighted registered voters across all counties.","examples":[250000]}},"type":"object","required":["swing_likely_voters","swing_likely_hhs","base_likely_voters","base_likely_hhs","total_registered"],"title":"VoterCounts","description":"Aggregated voter counts across a station's coverage area."},"cbradio__WindowSplitOffer":{"properties":{"boundary":{"type":"string","format":"date","title":"Boundary","description":"First day of the LUR window"},"election_date":{"type":"string","format":"date","title":"Election Date"},"election_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Election Type"},"window_days":{"type":"integer","title":"Window Days","description":"45 for a primary, 60 for a general or special"},"segments":{"items":{"$ref":"#/components/schemas/cbradio__WindowSplitSegment"},"type":"array","title":"Segments"}},"type":"object","required":["boundary","election_date","window_days","segments"],"title":"WindowSplitOffer","description":"A straddling flight, and the split that would price it correctly.\n\n47 U.S.C. §315(b) obliges a station to give a candidate its lowest unit\ncharge inside the window and lets it charge comparable commercial rates\noutside. A flight crossing that boundary has **no single correct rate**, so\nthe quote it produces is not the one the buy will settle at.\n\nOffered, never applied. Dividing a budget between the segments is a buying\ndecision — a candidate may well want the money weighted toward the\nregulated window rather than spread evenly — and a flight quietly cut in two\nis worse than one that says it should be. Re-request each segment with its\nown dates and budget to be priced correctly on both."},"cbradio__WindowSplitSegment":{"properties":{"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"rate_context":{"type":"string","title":"Rate Context","description":"in_window | out_of_window"},"weeks":{"type":"integer","title":"Weeks","description":"Calendar weeks this segment spans"}},"type":"object","required":["start_date","end_date","rate_context","weeks"],"title":"WindowSplitSegment","description":"One side of a flight that crosses the lowest-unit-charge boundary."},"cbradio__src__api__models__agreement__LifecycleState":{"type":"string","enum":["draft","pending_signature","signed","expired","terminated"],"title":"LifecycleState","description":"Agreement lifecycle state."},"cbradio__src__api__models__agreement__StationSummary":{"properties":{"callsign":{"type":"string","title":"Callsign"},"market":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"}},"type":"object","required":["callsign"],"title":"StationSummary","description":"Embedded station summary."},"cbradio__src__api__models__order_packet__StationSummary":{"properties":{"callsign":{"type":"string","title":"Callsign"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"},"dma":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dma"}},"type":"object","required":["callsign"],"title":"StationSummary","description":"Minimal station info for order responses."},"cbradio__src__api__models__proposal__AdType":{"type":"string","enum":["candidate","issue","all"],"title":"AdType","description":"Type of political advertising."},"cbradio__src__api__models__rate__AdType":{"type":"string","enum":["candidate","issue","all"],"title":"AdType","description":"Type of political advertisement."},"cbradio__src__api__models__srm__LifecycleState":{"type":"string","enum":["prospect","contacted","negotiating","active","at_risk","churned","inactive"],"title":"LifecycleState","description":"Station lifecycle states for SRM tracking."},"cbradio__src__api__models__version__PaymentStatusUpdate":{"properties":{"status":{"anyOf":[{"$ref":"#/components/schemas/cbradio__PaymentStatusEnum"},{"type":"null"}]},"paid_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Paid Date"},"reference_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference Number"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"PaymentStatusUpdate","description":"Fields for updating payment status."},"cbradio__src__api__routes__radio_buys__PaymentStatusUpdate":{"properties":{"is_paid":{"type":"boolean","title":"Is Paid"}},"type":"object","required":["is_paid"],"title":"PaymentStatusUpdate","description":"Request model for updating payment status."},"cbrouter__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbrouter__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbrouter__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbrouter__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbrouter__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbrouter__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbrouter__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbrouter__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbrouter__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbrouter__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbrouter__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbrouter__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbrouter__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbrouter__UCIAddRequest":{"properties":{"type":{"type":"string","title":"Type"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"values":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Values"}},"type":"object","required":["type"],"title":"UCIAddRequest","description":"Request body for adding a new UCI section."},"cbrouter__UCISetRequest":{"properties":{"section":{"type":"string","title":"Section"},"values":{"additionalProperties":true,"type":"object","title":"Values"}},"type":"object","required":["section","values"],"title":"UCISetRequest","description":"Request body for setting UCI config values."},"cbrouter__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbrouter__WriteFileRequest":{"properties":{"content":{"type":"string","title":"Content"}},"type":"object","required":["content"],"title":"WriteFileRequest","description":"Request body for writing file content to the router."},"cbscout__AddItemRequest":{"properties":{"entity_id":{"type":"string","maxLength":128,"minLength":1,"title":"Entity Id","description":"ID of the entity"},"entity_type":{"type":"string","maxLength":64,"minLength":1,"title":"Entity Type","description":"Type of entity"},"item_type":{"type":"string","title":"Item Type","description":"Type of fact (use ItemType values)"},"category":{"type":"string","title":"Category","description":"Category for the item"},"content":{"type":"string","minLength":1,"title":"Content","description":"The atomic fact content"},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence","description":"Confidence score","default":1.0},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["entity_id","entity_type","item_type","category","content"],"title":"AddItemRequest","description":"Request to add a single item directly (without resource extraction)."},"cbscout__AddItemResponse":{"properties":{"item_id":{"type":"string","format":"uuid","title":"Item Id","description":"ID of the created item"},"superseded_item_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Superseded Item Id","description":"ID of item that was superseded (if conflict resolved)"},"category_evolved":{"type":"boolean","title":"Category Evolved","description":"Whether the category summary was evolved"}},"type":"object","required":["item_id","category_evolved"],"title":"AddItemResponse","description":"Response after adding an item."},"cbscout__Category":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the record was created","examples":["2024-11-15T10:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Timestamp when the record was last updated","examples":["2024-11-15T14:45:00Z"]},"campaign_id":{"type":"string","maxLength":64,"minLength":1,"title":"Campaign Id","description":"Campaign identifier for tenant isolation","examples":["camp_2024_senate_tx"]},"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name","description":"Category name (use CategoryName values for standard categories)","examples":["issues","demographics","contact_history"]},"entity_id":{"type":"string","maxLength":128,"minLength":1,"title":"Entity Id","description":"ID of the entity this category belongs to","examples":["voter_12345"]},"entity_type":{"type":"string","maxLength":64,"minLength":1,"title":"Entity Type","description":"Type of entity","examples":["voter","household"]},"summary":{"type":"string","title":"Summary","description":"Markdown summary of all items in this category","examples":["## Issues\n\n- **Education**: Strongly supports increased funding\n- **Healthcare**: Favors ACA expansion"]},"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for the category","examples":["880e8400-e29b-41d4-a716-446655440003"]},"item_count":{"type":"integer","minimum":0.0,"title":"Item Count","description":"Number of current items in this category","default":0},"last_evolved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Evolved At","description":"Timestamp when summary was last evolved/regenerated","examples":["2024-11-15T10:30:00Z"]},"evolution_count":{"type":"integer","minimum":0.0,"title":"Evolution Count","description":"Number of times this category has been evolved","default":0}},"type":"object","required":["created_at","updated_at","campaign_id","name","entity_id","entity_type","summary","id"],"title":"Category","description":"Complete Category model with all fields.\n\nCategories evolve as new Items are added. The summary is\nregenerated periodically or on significant changes."},"cbscout__CategoryProfile":{"properties":{"category":{"$ref":"#/components/schemas/cbscout__CategorySummary","description":"Category metadata"},"summary":{"type":"string","title":"Summary","description":"Markdown summary of the category"},"items":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbscout__ItemSummary"},"type":"array"},{"type":"null"}],"title":"Items","description":"Items in this category (if requested)"}},"type":"object","required":["category","summary"],"title":"CategoryProfile","description":"A category with its summary and optionally items."},"cbscout__CategorySummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Category identifier"},"name":{"type":"string","title":"Name","description":"Category name"},"item_count":{"type":"integer","title":"Item Count","description":"Number of current items"},"updated_at":{"type":"string","title":"Updated At","description":"Last update timestamp as ISO string"}},"type":"object","required":["id","name","item_count","updated_at"],"title":"CategorySummary","description":"Lightweight category info for listing endpoints."},"cbscout__Checkpoint":{"properties":{"author":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","title":"Author","description":"Author slug (mirror's first path component). Drives the cbscout.read.checkpoints.<author> permission check.","examples":["cj-galdes"]},"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Stable identifier within the author scope (filename stem)","examples":["project_checkpoint_2026_05_05_session2"]},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Title parsed from frontmatter","examples":["HR onboarding wrap-up"]},"date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Date","description":"Calendar date the checkpoint refers to","examples":["2026-05-05"]},"type":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Type","description":"Checkpoint type tag from frontmatter","examples":["session","milestone","decision"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Short description from frontmatter"},"project_tag":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Project Tag","description":"Project tag for filtering (e.g. cbscout, cbea)","examples":["cbscout"]},"path":{"type":"string","minLength":1,"title":"Path","description":"Path within the checkpoint mirror, relative to mirror root","examples":["project_checkpoint_2026_05_05_session2.md"]},"content":{"type":"string","title":"Content","description":"Full markdown content (used for FTS + read endpoint)","default":""},"content_sha256":{"type":"string","maxLength":64,"minLength":64,"title":"Content Sha256","description":"SHA-256 of file bytes — used to skip unchanged files"},"indexed_at":{"type":"string","format":"date-time","title":"Indexed At","description":"When this checkpoint was last (re)indexed","examples":["2026-05-06T14:00:00Z"]}},"type":"object","required":["author","name","path","content_sha256","indexed_at"],"title":"Checkpoint","description":"Indexed checkpoint as stored in cbscout."},"cbscout__CheckpointHit":{"properties":{"author":{"type":"string","title":"Author","description":"Author slug"},"name":{"type":"string","title":"Name","description":"Stable identifier within author scope"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Title from frontmatter"},"date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Date","description":"Calendar date"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"Checkpoint type tag"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Short description"},"project_tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Tag","description":"Project tag"},"snippet":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snippet","description":"Excerpt of matching content for context"},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score","description":"BM25 relevance score (higher = better match)"}},"type":"object","required":["author","name"],"title":"CheckpointHit","description":"Search hit: summary fields plus FTS score and snippet."},"cbscout__CheckpointSummary":{"properties":{"author":{"type":"string","title":"Author","description":"Author slug"},"name":{"type":"string","title":"Name","description":"Stable identifier within author scope"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Title from frontmatter"},"date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Date","description":"Calendar date"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"Checkpoint type tag"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Short description"},"project_tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Tag","description":"Project tag"}},"type":"object","required":["author","name"],"title":"CheckpointSummary","description":"Lightweight checkpoint reference for list responses."},"cbscout__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbscout__DecayStatusResponse":{"properties":{"campaign_id":{"type":"string","title":"Campaign Id","description":"Campaign identifier the status applies to","examples":["camp_2024_senate_tx"]},"stale_30_days":{"type":"integer","minimum":0.0,"title":"Stale 30 Days","description":"Items not accessed in the last 30 days","examples":[42]},"stale_90_days":{"type":"integer","minimum":0.0,"title":"Stale 90 Days","description":"Items not accessed in the last 90 days","examples":[15]},"stale_180_days":{"type":"integer","minimum":0.0,"title":"Stale 180 Days","description":"Items not accessed in the last 180 days","examples":[3]},"policies":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Policies","description":"Active per-category decay/retention policies","examples":[[{"category":"contact_history","retention_days":1460}]]}},"type":"object","required":["campaign_id","stale_30_days","stale_90_days","stale_180_days","policies"],"title":"DecayStatusResponse","description":"Decay/staleness snapshot for a campaign.\n\nReports how many items have not been accessed within rolling\nwindows, alongside the active per-category retention policies.\nUsed to monitor memory health and plan maintenance windows."},"cbscout__EntityProfile":{"properties":{"entity_id":{"type":"string","title":"Entity Id","description":"Entity identifier"},"entity_type":{"type":"string","title":"Entity Type","description":"Type of entity"},"categories":{"items":{"$ref":"#/components/schemas/cbscout__CategoryProfile"},"type":"array","title":"Categories","description":"Categories with summaries"},"total_items":{"type":"integer","title":"Total Items","description":"Total number of current items across all categories"},"resources":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbscout__ResourceSummary"},"type":"array"},{"type":"null"}],"title":"Resources","description":"Source resources (if requested)"},"queried_at":{"type":"string","format":"date-time","title":"Queried At","description":"Timestamp of the query"}},"type":"object","required":["entity_id","entity_type","categories","total_items","queried_at"],"title":"EntityProfile","description":"Complete memory profile for an entity.\n\nThis is the primary read response, providing token-efficient\ncategory summaries with optional drill-down to Items/Resources."},"cbscout__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbscout__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbscout__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbscout__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbscout__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbscout__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbscout__Item":{"properties":{"valid_from":{"type":"string","format":"date-time","title":"Valid From","description":"Timestamp when this fact became valid","examples":["2024-01-01T00:00:00Z"]},"valid_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Valid Until","description":"Timestamp when this fact was superseded (null if still valid)","examples":["2024-06-15T12:00:00Z",null]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the record was created","examples":["2024-11-15T10:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Timestamp when the record was last updated","examples":["2024-11-15T14:45:00Z"]},"campaign_id":{"type":"string","maxLength":64,"minLength":1,"title":"Campaign Id","description":"Campaign identifier for tenant isolation","examples":["camp_2024_senate_tx"]},"item_type":{"$ref":"#/components/schemas/cbscout__ItemType","description":"Type of atomic fact","examples":["stance","demographic"]},"entity_id":{"type":"string","maxLength":128,"minLength":1,"title":"Entity Id","description":"ID of the entity this item relates to","examples":["voter_12345"]},"entity_type":{"type":"string","maxLength":64,"minLength":1,"title":"Entity Type","description":"Type of entity","examples":["voter","household"]},"category":{"type":"string","maxLength":64,"minLength":1,"title":"Category","description":"Category name for grouping related items","examples":["issues","demographics","contact_history"]},"content":{"type":"string","minLength":1,"title":"Content","description":"The atomic fact content","examples":["Supports increased education funding"]},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence","description":"Confidence score for extracted facts (0.0 to 1.0)","default":1.0,"examples":[0.95,0.8]},"source_resource_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Source Resource Id","description":"ID of the Resource this item was extracted from","examples":["550e8400-e29b-41d4-a716-446655440000"]},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Additional metadata","examples":[{"intensity":"strong","topic":"education"}]},"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for the item","examples":["660e8400-e29b-41d4-a716-446655440001"]},"superseded_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Superseded By","description":"ID of the item that superseded this one (if archived)","examples":["770e8400-e29b-41d4-a716-446655440002"]},"access_count":{"type":"integer","minimum":0.0,"title":"Access Count","description":"Number of times this item has been accessed","default":0},"last_accessed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Accessed At","description":"Timestamp of last access for decay scoring","examples":["2024-11-15T10:30:00Z"]}},"type":"object","required":["valid_from","created_at","updated_at","campaign_id","item_type","entity_id","entity_type","category","content","id"],"title":"Item","description":"Complete Item model with all fields.\n\nItems are versioned via temporal validity. When a conflicting\nfact is added, the old item's valid_until is set and a new\nitem is created with valid_from set to now."},"cbscout__ItemSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Item identifier"},"item_type":{"$ref":"#/components/schemas/cbscout__ItemType","description":"Type of fact"},"content":{"type":"string","title":"Content","description":"The atomic fact"},"confidence":{"type":"number","title":"Confidence","description":"Confidence score"},"valid_from":{"type":"string","title":"Valid From","description":"Valid from timestamp as ISO string"},"is_current":{"type":"boolean","title":"Is Current","description":"Whether this item is currently valid"}},"type":"object","required":["id","item_type","content","confidence","valid_from","is_current"],"title":"ItemSummary","description":"Lightweight item reference for inclusion in category summaries."},"cbscout__ItemType":{"type":"string","enum":["stance","opinion","priority","demographic","household","occupation","contact","interaction","response","relationship","affiliation","preference","channel","timing","event","commitment","action","precedent"],"title":"ItemType","description":"Types of atomic facts that can be stored.\n\nStance group: STANCE for explicit political positions (supports/opposes),\nOPINION for softer preferences, PRIORITY for issue ranking.\nDemographic group: DEMOGRAPHIC for voter characteristics (age, ethnicity),\nHOUSEHOLD for family info, OCCUPATION for employment.\nContact group: CONTACT for interaction records, INTERACTION for general\nnotes, RESPONSE for voter replies to outreach.\nRelationship group: RELATIONSHIP for personal connections,\nAFFILIATION for organizational ties.\nPreference group: PREFERENCE for general preferences, CHANNEL for\npreferred contact method, TIMING for best contact windows.\nEvent group: EVENT for attended events, COMMITMENT for pledges\n(vote, volunteer), ACTION for completed tasks."},"cbscout__MaintenanceResponse":{"properties":{"items_processed":{"type":"integer","minimum":0.0,"title":"Items Processed","description":"Total number of items examined during the run","examples":[150]},"items_archived":{"type":"integer","minimum":0.0,"title":"Items Archived","description":"Number of items archived (valid_until set) during the run","examples":[12]},"items_deleted":{"type":"integer","minimum":0.0,"title":"Items Deleted","description":"Number of items hard-deleted during the run","examples":[0]},"categories_evolved":{"type":"integer","minimum":0.0,"title":"Categories Evolved","description":"Number of category summaries regenerated as a result","examples":[3]},"errors":{"items":{"type":"string"},"type":"array","title":"Errors","description":"Non-fatal error messages collected during the run","examples":[[]]}},"type":"object","required":["items_processed","items_archived","items_deleted","categories_evolved","errors"],"title":"MaintenanceResponse","description":"Summary of a single maintenance run.\n\nReturned by the consolidation, summarization, and archival\nendpoints. Counts describe what the run touched; ``errors``\ncarries any per-item failures encountered without aborting the run."},"cbscout__MemorizeRequest":{"properties":{"entity_id":{"type":"string","maxLength":128,"minLength":1,"title":"Entity Id","description":"ID of the entity to memorize information about","examples":["voter_12345"]},"entity_type":{"type":"string","maxLength":64,"minLength":1,"title":"Entity Type","description":"Type of entity","examples":["voter","household"]},"content":{"type":"string","minLength":1,"title":"Content","description":"Raw content to memorize (will be stored and processed)","examples":["Voter expressed strong support for education funding and mentioned having 3 school-age children."]},"resource_type":{"$ref":"#/components/schemas/cbscout__ResourceType","description":"Type of source material","examples":["canvass_log"]},"source":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Source","description":"Source identifier (agent ID, system, etc.)","examples":["agent_canvasser_01"]},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Additional metadata to attach","examples":[{"location":"Austin, TX"}]}},"type":"object","required":["entity_id","entity_type","content","resource_type"],"title":"MemorizeRequest","description":"Request to memorize new content about an entity.\n\nThe write path:\n1. Stores raw content as immutable Resource\n2. Extracts atomic Items (facts)\n3. Resolves conflicts with existing Items\n4. Evolves category summaries"},"cbscout__MemorizeResponse":{"properties":{"resource_id":{"type":"string","format":"uuid","title":"Resource Id","description":"ID of the created Resource"},"items_created":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Items Created","description":"IDs of newly created Items"},"items_superseded":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Items Superseded","description":"IDs of Items that were superseded by new facts"},"categories_evolved":{"items":{"type":"string"},"type":"array","title":"Categories Evolved","description":"Names of categories that were evolved"}},"type":"object","required":["resource_id","items_created","items_superseded","categories_evolved"],"title":"MemorizeResponse","description":"Response after memorizing content.\n\nReturns IDs for the created Resource and extracted Items."},"cbscout__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbscout__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbscout__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbscout__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbscout__ReindexReport":{"properties":{"added":{"items":{"type":"string"},"type":"array","title":"Added"},"updated":{"items":{"type":"string"},"type":"array","title":"Updated"},"deleted":{"items":{"type":"string"},"type":"array","title":"Deleted"},"unchanged":{"items":{"type":"string"},"type":"array","title":"Unchanged"},"errors":{"items":{"prefixItems":[{"type":"string"},{"type":"string"}],"type":"array","maxItems":2,"minItems":2},"type":"array","title":"Errors"}},"type":"object","title":"ReindexReport"},"cbscout__Resource":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the record was created","examples":["2024-11-15T10:30:00Z"]},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Timestamp when the record was last updated","examples":["2024-11-15T14:45:00Z"]},"campaign_id":{"type":"string","maxLength":64,"minLength":1,"title":"Campaign Id","description":"Campaign identifier for tenant isolation","examples":["camp_2024_senate_tx"]},"resource_type":{"$ref":"#/components/schemas/cbscout__ResourceType","description":"Type of raw data resource","examples":["canvass_log","call_transcript"]},"entity_id":{"type":"string","maxLength":128,"minLength":1,"title":"Entity Id","description":"ID of the entity this resource relates to (e.g., voter ID)","examples":["voter_12345","household_789"]},"entity_type":{"type":"string","maxLength":64,"minLength":1,"title":"Entity Type","description":"Type of entity (voter, household, organization, etc.)","examples":["voter","household","organization"]},"content":{"type":"string","title":"Content","description":"Raw content of the resource (text, JSON, etc.)","examples":["Voter expressed strong support for education funding."]},"source":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Source","description":"Source identifier (agent ID, system, user, etc.)","examples":["agent_canvasser_01","import_votebuilder"]},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Additional metadata as key-value pairs","examples":[{"duration_seconds":180,"location":"Austin, TX"}]},"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier for the resource","examples":["550e8400-e29b-41d4-a716-446655440000"]},"content_hash":{"type":"string","title":"Content Hash","description":"SHA-256 hash of content for deduplication","examples":["a3f2b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1"]},"processed":{"type":"boolean","title":"Processed","description":"Whether this resource has been processed into Items","default":false}},"type":"object","required":["created_at","updated_at","campaign_id","resource_type","entity_id","entity_type","content","id","content_hash"],"title":"Resource","description":"Complete Resource model with all fields.\n\nResources are immutable once created. The content is preserved\nexactly as received for audit and reprocessing purposes."},"cbscout__ResourceSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Resource identifier"},"resource_type":{"$ref":"#/components/schemas/cbscout__ResourceType","description":"Type of resource"},"created_at":{"type":"string","title":"Created At","description":"Creation timestamp as ISO string"}},"type":"object","required":["id","resource_type","created_at"],"title":"ResourceSummary","description":"Lightweight resource reference for inclusion in other responses."},"cbscout__ResourceType":{"type":"string","enum":["canvass_log","call_transcript","survey_response","email","sms","social_media","event_attendance","donation_record","volunteer_log","external_import","agent_note"],"title":"ResourceType","description":"Types of raw data resources that can be stored.\n\nUse CANVASS_LOG for door-to-door contact records,\nCALL_TRANSCRIPT for phone interactions, SURVEY_RESPONSE for\nstructured survey data, EMAIL/SMS/SOCIAL_MEDIA for digital\noutreach, EVENT_ATTENDANCE for rally or event records,\nDONATION_RECORD for financial contributions,\nVOLUNTEER_LOG for volunteer activity, EXTERNAL_IMPORT for\nbulk data from third-party systems (e.g., VoteBuilder),\nand AGENT_NOTE for AI agent-generated observations."},"cbscout__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbscout__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbscout__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbsearch__AddItemRequest":{"properties":{"kind":{"type":"string","pattern":"^(workspace|transcript|dataset|pipeline|query|file|doc|job)$","title":"Kind"},"ref":{"type":"string","minLength":1,"title":"Ref","examples":["redistricting_plans"]},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","required":["kind","ref"],"title":"AddItemRequest"},"cbsearch__AskRequest":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id"},"text":{"type":"string","title":"Text"}},"type":"object","required":["conversation_id","text"],"title":"AskRequest"},"cbsearch__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbsearch__ConcurIngestRequest":{"properties":{"envelopes":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Envelopes"}},"type":"object","title":"ConcurIngestRequest"},"cbsearch__CreateConversationRequest":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"catalog":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Catalog","examples":["victory"]},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"},"project_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Id"}},"type":"object","title":"CreateConversationRequest"},"cbsearch__CreateProjectRequest":{"properties":{"name":{"type":"string","title":"Name","examples":["Redistrict 2026"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"}},"type":"object","required":["name"],"title":"CreateProjectRequest"},"cbsearch__CreateWorkspaceRequest":{"properties":{"name":{"type":"string","title":"Name","examples":["donations-analysis"]}},"type":"object","required":["name"],"title":"CreateWorkspaceRequest"},"cbsearch__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbsearch__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbsearch__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsearch__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbsearch__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsearch__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbsearch__IngestRequest":{"properties":{"probe_id":{"type":"string","minLength":1,"title":"Probe Id"},"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"select_inner":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Select Inner"},"select_all":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Select All"}},"type":"object","required":["probe_id","name"],"title":"IngestRequest"},"cbsearch__KeyRequest":{"properties":{"key":{"type":"string","minLength":1,"title":"Key"}},"type":"object","required":["key"],"title":"KeyRequest"},"cbsearch__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbsearch__MoveConversationRequest":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"}},"type":"object","required":["workspace_id"],"title":"MoveConversationRequest"},"cbsearch__PinArtifactRequest":{"properties":{"type":{"type":"string","pattern":"^(table|pipeline|dataset)$","title":"Type","default":"table"},"title":{"type":"string","title":"Title"},"ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref"},"payload":{"anyOf":[{},{"type":"null"}],"title":"Payload"}},"type":"object","required":["title"],"title":"PinArtifactRequest"},"cbsearch__PresenceRequest":{"properties":{"session_id":{"type":"string","maxLength":80,"minLength":4,"title":"Session Id"},"action":{"type":"string","pattern":"^(hello|heartbeat|close|takeover)$","title":"Action","default":"heartbeat"},"workspace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workspace Id"},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id"}},"type":"object","required":["session_id"],"title":"PresenceRequest"},"cbsearch__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbsearch__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbsearch__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbsearch__RenameRequest":{"properties":{"title":{"type":"string","title":"Title"}},"type":"object","required":["title"],"title":"RenameRequest"},"cbsearch__RenameWorkspaceRequest":{"properties":{"name":{"type":"string","title":"Name"}},"type":"object","required":["name"],"title":"RenameWorkspaceRequest"},"cbsearch__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbsearch__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsearch__UpdateProjectRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","title":"UpdateProjectRequest"},"cbsearch__UploadUrlRequest":{"properties":{"filename":{"type":"string","maxLength":200,"minLength":1,"title":"Filename"},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type"}},"type":"object","required":["filename"],"title":"UploadUrlRequest"},"cbsearch__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbship__CapacityReport":{"properties":{"nodes":{"items":{"$ref":"#/components/schemas/cbship__NodeCapacity"},"type":"array","title":"Nodes"},"allocatable_nodes":{"type":"integer","title":"Allocatable Nodes"},"total_available":{"additionalProperties":true,"type":"object","title":"Total Available"},"live_leases":{"type":"integer","title":"Live Leases"}},"type":"object","required":["nodes","allocatable_nodes","total_available","live_leases"],"title":"CapacityReport"},"cbship__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbship__EnrollResponse":{"properties":{"verified":{"type":"boolean","title":"Verified","description":"the attestation checked out cryptographically"},"accepted":{"type":"boolean","title":"Accepted","description":"the fleet admitted this node (may require an operator)"},"status":{"type":"string","title":"Status","description":"pending | accepted | rejected"},"node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Node Id","description":"fleet identifier for this machine"},"fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fingerprint","description":"stable hardware fingerprint"},"enroll_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Enroll Count","description":"times this machine has enrolled"},"first_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Seen"},"role_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role Hint","description":"cpu-host | gpu-host"},"mesh_joinable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mesh Joinable"},"failures":{"items":{"type":"string"},"type":"array","title":"Failures","description":"why verification failed"},"capacity_overcommit":{"additionalProperties":true,"type":"object","title":"Capacity Overcommit","description":"set when THIS report shrank the node below its outstanding leases — the machine is told so it lands in the local join record too"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings","description":"probe warnings worth acting on"},"next_steps":{"items":{"type":"string"},"type":"array","title":"Next Steps","description":"what the operator/machine does next"},"message":{"type":"string","title":"Message","default":""}},"type":"object","required":["verified","accepted","status"],"title":"EnrollResponse","description":"What a joining machine gets back.\n\n`accepted` is deliberately separate from `verified`: a verified attestation\nproves who produced the report, not that the fleet wants the machine. See\ndocs/ATTEST.md — authenticated input to a placement decision, never\nauthorization."},"cbship__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbship__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbship__HandoffResult":{"properties":{"fingerprint":{"type":"string","title":"Fingerprint"},"status":{"type":"string","title":"Status","description":"handed_off | accepted (delivery failed) | skipped"},"delivered":{"type":"boolean","title":"Delivered","description":"the notification reached cbnotify"},"already_handed_off":{"type":"boolean","title":"Already Handed Off","description":"idempotent no-op — this node was handed off before","default":false},"attempts":{"type":"integer","title":"Attempts","default":0},"error":{"type":"string","title":"Error","default":""},"packet":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Packet","description":"the hand-off packet"}},"type":"object","required":["fingerprint","status","delivered"],"title":"HandoffResult","description":"Outcome of a hand-off attempt."},"cbship__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbship__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbship__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbship__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbship__HolderNotifyResult":{"properties":{"considered":{"type":"integer","title":"Considered"},"delivered":{"type":"integer","title":"Delivered"},"failed":{"type":"integer","title":"Failed"},"no_callback":{"type":"integer","title":"No Callback","description":"holders with no notify_service — covered by the cbnotify fallback","default":0},"broadcast":{"type":"boolean","title":"Broadcast","description":"cbnotify fallback fired","default":false},"broadcast_error":{"type":"string","title":"Broadcast Error","default":""},"events":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Events"}},"type":"object","required":["considered","delivered","failed"],"title":"HolderNotifyResult","description":"Outcome of telling holders their capacity is gone or going."},"cbship__Lease":{"properties":{"lease_id":{"type":"string","title":"Lease Id"},"state":{"type":"string","title":"State","description":"active | released | expired"},"fingerprint":{"type":"string","title":"Fingerprint"},"holder":{"type":"string","title":"Holder"},"purpose":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purpose"},"vcpu":{"type":"integer","title":"Vcpu","default":0},"memory_mb":{"type":"integer","title":"Memory Mb","default":0},"gpu_vram_mb":{"type":"integer","title":"Gpu Vram Mb","default":0},"disk_gb":{"type":"integer","title":"Disk Gb","default":0},"granted_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Granted At"},"granted_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Granted By"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"renewed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Renewed At"},"renew_count":{"type":"integer","title":"Renew Count","default":0},"released_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Released At"},"release_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Release Reason"},"notify_service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notify Service"},"overcommit_notified_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Overcommit Notified At"},"expiry_notified_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiry Notified At"},"notify_attempts":{"type":"integer","title":"Notify Attempts","default":0},"notify_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notify Error"}},"type":"object","required":["lease_id","state","fingerprint","holder"],"title":"Lease"},"cbship__LeaseList":{"properties":{"leases":{"items":{"$ref":"#/components/schemas/cbship__Lease"},"type":"array","title":"Leases"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["leases","count"],"title":"LeaseList"},"cbship__LeaseReapResult":{"properties":{"reaped":{"type":"integer","title":"Reaped"},"leases":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Leases"}},"type":"object","required":["reaped"],"title":"LeaseReapResult","description":"Expired leases marked released. Reclaim is expiry-only, never idle-based."},"cbship__LeaseRefusal":{"properties":{"granted":{"type":"boolean","title":"Granted","default":false},"reason":{"type":"string","title":"Reason"},"requested":{"additionalProperties":true,"type":"object","title":"Requested"},"candidates_considered":{"type":"integer","title":"Candidates Considered"},"rejections":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Rejections"},"retry_after_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Retry After Seconds","description":"soonest a lease expires and capacity frees up"}},"type":"object","required":["reason","requested","candidates_considered"],"title":"LeaseRefusal","description":"Why no node could satisfy the request.\n\nDeliberately detailed: a consumer that cannot place work needs to know whether\nto retry, ask for less, or escalate — and an operator needs to see which nodes\nwere considered and rejected."},"cbship__LeaseReleaseRequest":{"properties":{"reason":{"type":"string","maxLength":500,"title":"Reason","default":"released by holder"}},"type":"object","title":"LeaseReleaseRequest"},"cbship__LeaseRequest":{"properties":{"holder":{"type":"string","maxLength":200,"minLength":1,"title":"Holder","description":"who is claiming it"},"purpose":{"type":"string","maxLength":500,"title":"Purpose","default":""},"vcpu":{"type":"integer","minimum":0.0,"title":"Vcpu","default":0},"memory_mb":{"type":"integer","minimum":0.0,"title":"Memory Mb","default":0},"gpu_vram_mb":{"type":"integer","minimum":0.0,"title":"Gpu Vram Mb","default":0},"disk_gb":{"type":"integer","minimum":0.0,"title":"Disk Gb","default":0},"require_gpu":{"type":"boolean","title":"Require Gpu","description":"only place on a compute-GPU node","default":false},"role_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role Hint","description":"restrict to cpu-host | gpu-host"},"fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fingerprint","description":"pin to one specific node"},"ttl_seconds":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Ttl Seconds","description":"lease lifetime"},"notify_service":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notify Service","description":"canonical cb* mesh service name to notify when this lease stops being backed by capacity. NOT a URL — cbship does not POST to caller-supplied URLs. The service implements POST /api/v1/cbship/lease-event."}},"type":"object","required":["holder"],"title":"LeaseRequest","description":"Ask for capacity.\n\nEither grants in full or refuses with the shortfall — never partial, never\novercommitted. A consumer that cannot get a lease must wait and retry, never\nproceed unallocated (the cbvpn failure mode this design exists to avoid)."},"cbship__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbship__NodeCapacity":{"properties":{"capacity_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Capacity Age Seconds","description":"seconds since the node last re-probed"},"capacity_stale":{"type":"boolean","title":"Capacity Stale","default":false},"capacity_too_stale_to_allocate":{"type":"boolean","title":"Capacity Too Stale To Allocate","default":false},"last_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen"},"enroll_count":{"type":"integer","title":"Enroll Count","default":0},"fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fingerprint"},"node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Node Id"},"hostname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hostname"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"role_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role Hint"},"compute_gpu":{"type":"boolean","title":"Compute Gpu","default":false},"bare_metal":{"type":"boolean","title":"Bare Metal","default":false},"allocatable":{"type":"boolean","title":"Allocatable","default":false},"not_allocatable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Not Allocatable Reason","description":"prose for the FIRST blocker; human copy, not a contract"},"not_allocatable_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Not Allocatable Code","description":"stable code for the first blocker — switch on this, not the prose"},"blocking_reasons":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Blocking Reasons","description":"EVERY reason this node is not allocatable, as {code, reason}, primary first. A node can have more than one, and clearing the visible one need not help."},"already_fleet_member":{"type":"boolean","title":"Already Fleet Member","description":"the probe found the cb* fleet already running here; not allocatable by default","default":false},"allocatable_after_admission":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Allocatable After Admission","description":"would admitting this node make it allocatable? null when already admitted. FALSE means another blocker waits behind the status gate — say so before offering an Admit action."},"capacity":{"additionalProperties":true,"type":"object","title":"Capacity"},"memory_source":{"type":"string","title":"Memory Source","description":"'available' (honest) or 'total' (fallback)"},"reserved":{"additionalProperties":true,"type":"object","title":"Reserved"},"allocated":{"additionalProperties":true,"type":"object","title":"Allocated"},"available":{"additionalProperties":true,"type":"object","title":"Available"},"active_leases":{"type":"integer","title":"Active Leases","default":0},"overcommit":{"additionalProperties":true,"type":"object","title":"Overcommit","description":"dimensions where live leases exceed capacity-reserved; empty is healthy"}},"type":"object","required":["capacity","memory_source","reserved","allocated","available"],"title":"NodeCapacity"},"cbship__NodeList":{"properties":{"nodes":{"items":{"$ref":"#/components/schemas/cbship__NodeSummary"},"type":"array","title":"Nodes"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["nodes","count"],"title":"NodeList"},"cbship__NodeSummary":{"properties":{"node_id":{"type":"string","title":"Node Id"},"fingerprint":{"type":"string","title":"Fingerprint"},"hostname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hostname"},"status":{"type":"string","title":"Status"},"role_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role Hint"},"mesh_joinable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mesh Joinable"},"os_pretty":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Os Pretty"},"kernel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kernel"},"virt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Virt"},"bare_metal":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Bare Metal"},"compute_gpu":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Compute Gpu"},"gpu_vram_mb":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Gpu Vram Mb"},"cpus_logical":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cpus Logical"},"memory_gb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Memory Gb"},"token_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Id"},"enroll_count":{"type":"integer","title":"Enroll Count","default":0},"first_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Seen"},"last_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen"}},"type":"object","required":["node_id","fingerprint","status"],"title":"NodeSummary","description":"A registered node as the fleet sees it."},"cbship__OvercommittedNode":{"properties":{"fingerprint":{"type":"string","title":"Fingerprint"},"hostname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hostname"},"overcommit":{"additionalProperties":true,"type":"object","title":"Overcommit","description":"how much over, per dimension"},"capacity":{"additionalProperties":true,"type":"object","title":"Capacity"},"reserved":{"additionalProperties":true,"type":"object","title":"Reserved"},"allocated":{"additionalProperties":true,"type":"object","title":"Allocated"},"active_leases":{"type":"integer","title":"Active Leases","default":0},"implicated_leases":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Implicated Leases","description":"live leases, newest first — NOT auto-released"}},"type":"object","required":["fingerprint","overcommit","capacity","reserved","allocated"],"title":"OvercommittedNode","description":"A node whose live leases exceed what it can now honour."},"cbship__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbship__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbship__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbship__PromoteRequest":{"properties":{"status":{"type":"string","pattern":"^(accepted|rejected)$","title":"Status","description":"accepted | rejected"},"note":{"type":"string","title":"Note","default":""}},"type":"object","required":["status"],"title":"PromoteRequest","description":"Operator decision on a pending node."},"cbship__RateLimitStatus":{"properties":{"window_seconds":{"type":"integer","title":"Window Seconds"},"limits":{"additionalProperties":true,"type":"object","title":"Limits"},"tracked_sources":{"type":"integer","title":"Tracked Sources"},"tracked_failing_sources":{"type":"integer","title":"Tracked Failing Sources"},"tracked_failing_tokens":{"type":"integer","title":"Tracked Failing Tokens"},"top_failing_sources":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Top Failing Sources"}},"type":"object","required":["window_seconds","limits","tracked_sources","tracked_failing_sources","tracked_failing_tokens"],"title":"RateLimitStatus","description":"Current limiter pressure.\n\nRate-limited requests are deliberately not written to the audit table — that\nwrite is the amplification being defended against — so this endpoint is how an\noperator sees a flood in progress."},"cbship__RefreshSweepResult":{"properties":{"checked":{"type":"integer","title":"Checked"},"stale":{"type":"integer","title":"Stale"},"too_stale_to_allocate":{"type":"integer","title":"Too Stale To Allocate"},"stale_after_seconds":{"type":"integer","title":"Stale After Seconds"},"refuse_after_seconds":{"type":"integer","title":"Refuse After Seconds"},"nodes":{"items":{"$ref":"#/components/schemas/cbship__StaleNode"},"type":"array","title":"Nodes"},"overcommitted":{"type":"integer","title":"Overcommitted","default":0},"overcommitted_nodes":{"items":{"$ref":"#/components/schemas/cbship__OvercommittedNode"},"type":"array","title":"Overcommitted Nodes"},"holder_notify":{"anyOf":[{"$ref":"#/components/schemas/cbship__HolderNotifyResult"},{"type":"null"}],"description":"outcome of telling the affected holders"},"notified":{"type":"boolean","title":"Notified","default":false},"notify_error":{"type":"string","title":"Notify Error","default":""}},"type":"object","required":["checked","stale","too_stale_to_allocate","stale_after_seconds","refuse_after_seconds"],"title":"RefreshSweepResult","description":"Capacity health: which nodes stopped re-probing, and which are overcommitted.\n\ncbship cannot re-probe a node itself — the probe is a binary the node runs, and\ncbship has no execution path onto it. This sweep therefore *surfaces* staleness\nrather than fixing it; the fix is the node re-enrolling (docs/REPROBE.md)."},"cbship__StaleNode":{"properties":{"fingerprint":{"type":"string","title":"Fingerprint"},"hostname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hostname"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"last_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen"},"capacity_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Capacity Age Seconds"},"allocatable":{"type":"boolean","title":"Allocatable","default":false},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["fingerprint"],"title":"StaleNode"},"cbship__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbship__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbship__SweepResult":{"properties":{"considered":{"type":"integer","title":"Considered"},"delivered":{"type":"integer","title":"Delivered"},"failed":{"type":"integer","title":"Failed"},"results":{"items":{"$ref":"#/components/schemas/cbship__HandoffResult"},"type":"array","title":"Results"}},"type":"object","required":["considered","delivered","failed"],"title":"SweepResult","description":"Re-drive of accepted-but-undelivered hand-offs (cbcron tick)."},"cbship__TokenList":{"properties":{"tokens":{"items":{"$ref":"#/components/schemas/cbship__TokenSummary"},"type":"array","title":"Tokens"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["tokens","count"],"title":"TokenList"},"cbship__TokenMintRequest":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"what/who this token is for"},"ttl_hours":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Ttl Hours","description":"expiry window; 0 or null = never expires","default":24},"max_uses":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Max Uses","description":"use budget; 0 or null = unlimited","default":1},"bind_on_first_use":{"type":"boolean","title":"Bind On First Use","description":"pin the token to the first machine that enrols with it","default":true},"note":{"type":"string","title":"Note","default":""}},"type":"object","title":"TokenMintRequest","description":"Mint an enrollment token.\n\nDefaults are the tight ones on purpose: single-use, short-lived, bound to the\nfirst machine that uses it. Widening any of them should be a deliberate act."},"cbship__TokenMintResponse":{"properties":{"token_id":{"type":"string","title":"Token Id"},"secret":{"type":"string","title":"Secret","description":"THE ENROLLMENT TOKEN — shown once, never retrievable"},"warning":{"type":"string","title":"Warning","default":"Store this now — it is not recoverable. It is a bearer credential: whoever holds it can enrol a machine. Deliver it out-of-band, never on a command line (ps exposes arguments)."},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"max_uses":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Uses"},"bind_on_first_use":{"type":"boolean","title":"Bind On First Use","default":true},"usage":{"type":"string","title":"Usage","default":"On the new machine: cbship join --token-file <path>  (or CBSHIP_ENROLL_TOKEN=<secret> cbship join)"}},"type":"object","required":["token_id","secret"],"title":"TokenMintResponse","description":"The **only** time the secret is ever returned."},"cbship__TokenReapResult":{"properties":{"reaped":{"type":"integer","title":"Reaped"},"tokens":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Tokens","description":"token_id + terminal status"}},"type":"object","required":["reaped"],"title":"TokenReapResult","description":"Key material destroyed for terminally-unusable tokens."},"cbship__TokenRevokeRequest":{"properties":{"reason":{"type":"string","maxLength":500,"title":"Reason","default":"revoked by operator"}},"type":"object","title":"TokenRevokeRequest"},"cbship__TokenSummary":{"properties":{"token_id":{"type":"string","title":"Token Id"},"status":{"type":"string","title":"Status","description":"active | revoked | expired | exhausted"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"minted_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Minted At"},"minted_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Minted By"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"max_uses":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Uses"},"uses":{"type":"integer","title":"Uses","default":0},"bind_on_first_use":{"type":"boolean","title":"Bind On First Use","default":true},"bound_fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bound Fingerprint"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At"},"revoked_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked By"},"revoke_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoke Reason"},"legacy":{"type":"boolean","title":"Legacy","default":false},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"secret_present":{"type":"boolean","title":"Secret Present","description":"key material is on disk; a token needs BOTH this and the row","default":false}},"type":"object","required":["token_id","status"],"title":"TokenSummary","description":"Token metadata. Never contains the secret."},"cbship__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbsme__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbsme__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbsme__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbsme__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsme__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbsme__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsme__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbsme__LookupRequest":{"properties":{"question":{"type":"string","title":"Question","description":"Natural-language question about the discovered rules."},"max_results":{"type":"integer","maximum":20.0,"minimum":1.0,"title":"Max Results","default":5},"engagement":{"type":"string","title":"Engagement","description":"Engagement slug; empty = default.","default":""}},"type":"object","required":["question"],"title":"LookupRequest","description":"A question for the engagement's knowledge base."},"cbsme__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbsme__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbsme__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbsme__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbsme__QueryRequest":{"properties":{"sql":{"type":"string","title":"Sql","description":"DuckDB SQL. Tables are addressed as `catalog_name.table_name` (e.g. `cc_discovery.deployments`). Cross-catalog JOINs work natively."},"catalogs":{"type":"string","title":"Catalogs","description":"Comma-separated catalog names to scope (empty = all visible)","default":""},"allow_full_scan":{"type":"boolean","title":"Allow Full Scan","description":"Unbounded queries against huge catalogs are refused by the full-scan guard. Pass true ONLY after the user explicitly confirmed a full scan.","default":false}},"type":"object","required":["sql"],"title":"QueryRequest","description":"Mirror of cbmodels' federation QueryRequest — forwarded verbatim."},"cbsme__ResolveRequest":{"properties":{"consensus":{"type":"string","title":"Consensus","description":"The agreed rule as a standalone fact."},"title":{"type":"string","title":"Title","description":"Short title for the rule record."},"stage":{"type":"string","title":"Stage","description":"Lifecycle stage, e.g. '07-commissions'."},"resolves_record_id":{"type":"string","title":"Resolves Record Id","description":"Open-question KB id this settles.","default":""},"decided_by":{"type":"string","title":"Decided By","description":"Attribution; empty = the engagement's SME.","default":""},"conversation_ref":{"type":"string","title":"Conversation Ref","description":"cbsearch conversation id.","default":""},"engagement":{"type":"string","title":"Engagement","description":"Engagement slug; empty = default.","default":""}},"type":"object","required":["consensus","title","stage"],"title":"ResolveRequest","description":"A consensus decision to record. The SME's word is final — writes immediately."},"cbsme__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbsme__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsme__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbsms__BulkSMSRequest":{"properties":{"messages":{"items":{"$ref":"#/components/schemas/cbsms__SMSRequest"},"type":"array","title":"Messages"}},"type":"object","required":["messages"],"title":"BulkSMSRequest","description":"Request model for sending multiple SMS messages.","example":{"messages":[{"message":"Hello, this is a test message 1.","port":"1.01","recipient":"13686876620"},{"message":"Hello, this is a test message 2.","port":"2.01","recipient":"13686876621"}]}},"cbsms__CommandType":{"type":"string","enum":["lock","unlock","switch","reset","save","reboot"],"title":"CommandType","description":"Types of commands that can be sent to the device."},"cbsms__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbsms__DeviceCommandRequest":{"properties":{"command":{"$ref":"#/components/schemas/cbsms__CommandType"},"ports":{"type":"string","title":"Ports"}},"type":"object","required":["command","ports"],"title":"DeviceCommandRequest","description":"Request model for sending commands to the device.","example":{"command":"switch","ports":"1.01"}},"cbsms__DeviceCommandResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsms__Status"},"message":{"type":"string","title":"Message"},"command":{"$ref":"#/components/schemas/cbsms__CommandType"},"ports":{"type":"string","title":"Ports"}},"type":"object","required":["status","message","command","ports"],"title":"DeviceCommandResponse","description":"Response model for device command operations.","example":{"command":"switch","message":"Command executed successfully","ports":"1.01","status":"ok"}},"cbsms__DeviceStatusResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsms__Status"},"message":{"type":"string","title":"Message"},"device_ip":{"type":"string","title":"Device Ip"},"total_ports":{"type":"integer","title":"Total Ports"},"ports_status":{"items":{"$ref":"#/components/schemas/cbsms__PortStatus"},"type":"array","title":"Ports Status"},"locked_remaining_s":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Locked Remaining S"},"send_pacing":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Send Pacing"}},"type":"object","required":["status","message","device_ip","total_ports","ports_status"],"title":"DeviceStatusResponse","description":"Response model for device status check operations.","example":{"device_ip":"192.168.1.67","message":"Device status retrieved successfully","ports_status":[{"balance":100.0,"operator":"46000 China Mobile","port":"1.01","sim_number":"13986543210","sim_number_source":"config","status":"3 OK"}],"status":"ok","total_ports":32}},"cbsms__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbsms__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbsms__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsms__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbsms__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsms__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbsms__IncomingSMSResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsms__Status"},"message":{"type":"string","title":"Message"},"message_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Message Id"}},"type":"object","required":["status","message"],"title":"IncomingSMSResponse","description":"Response model for acknowledging incoming SMS webhooks.","example":{"message":"SMS received and processed","message_id":1,"status":"ok"}},"cbsms__MMSRequest":{"properties":{"recipient":{"type":"string","title":"Recipient"},"message":{"type":"string","title":"Message"},"subject":{"type":"string","title":"Subject"},"port":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Port"},"attachments":{"type":"string","title":"Attachments"},"task_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Task Id"},"timeout":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timeout"}},"type":"object","required":["recipient","message","subject","attachments"],"title":"MMSRequest","description":"Request model for sending a single MMS.","example":{"attachments":"jpg|/9j/4AAQSkZJRgABAQEASABIAAD/4QBMRXhpZgAA;txt|SGVsbG8gV29ybGQh","message":"Hello, this is a test message with attachments.","port":"1.01","recipient":"13686876620","subject":"Test MMS"}},"cbsms__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbsms__MessageStatus":{"properties":{"message_id":{"type":"integer","title":"Message Id"},"recipient":{"type":"string","title":"Recipient"},"status":{"$ref":"#/components/schemas/cbsms__Status"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["message_id","recipient","status"],"title":"MessageStatus","description":"Status of an individual message within a task.","example":{"description":"Message delivered","message_id":1,"recipient":"13686876620","sent_at":"2023-10-25T14:30:00","status":"completed","updated_at":"2023-10-25T14:30:05"}},"cbsms__PortStatus":{"properties":{"port":{"type":"string","title":"Port"},"status":{"type":"string","title":"Status"},"sim_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sim Number"},"sim_number_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sim Number Source"},"operator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator"},"balance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Balance"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["port","status"],"title":"PortStatus","description":"Status information for a specific port.","example":{"balance":100.0,"operator":"46000 China Mobile","port":"1.01","sim_number":"13986543210","sim_number_source":"config","status":"3 OK"}},"cbsms__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbsms__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbsms__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbsms__RecentResponsesResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsms__Status"},"message":{"type":"string","title":"Message"},"messages":{"items":{"$ref":"#/components/schemas/cbsms__SMSMessageDetail"},"type":"array","title":"Messages"}},"type":"object","required":["status","message","messages"],"title":"RecentResponsesResponse","description":"Response model for retrieving recent SMS responses.","example":{"message":"Recent SMS responses retrieved","messages":[{"message":"Hi","message_id":1,"port":"1.01","received_at":"2025-03-26T19:29:43.556719","recipient":"16165550142","sender":"16165550150"}],"status":"ok"}},"cbsms__SMSMessageDetail":{"properties":{"message_id":{"type":"integer","title":"Message Id"},"sender":{"type":"string","title":"Sender"},"recipient":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient"},"port":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Port"},"message":{"type":"string","title":"Message"},"received_at":{"type":"string","title":"Received At"},"original_message_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Original Message Id"}},"type":"object","required":["message_id","sender","message","received_at"],"title":"SMSMessageDetail","description":"Model for a single SMS message in a response list."},"cbsms__SMSRequest":{"properties":{"recipient":{"type":"string","title":"Recipient"},"message":{"type":"string","title":"Message"},"port":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Port"},"flash_sms":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Flash Sms","default":false},"task_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Task Id"},"timeout":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timeout"},"coding":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Coding"},"smstype":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Smstype","default":0},"smstitle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Smstitle"},"attachments":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Attachments"}},"type":"object","required":["recipient","message"],"title":"SMSRequest","description":"Request model for sending a single SMS.","example":{"flash_sms":false,"message":"Hello, this is a test message.","port":"1.01","recipient":"13686876620"}},"cbsms__SMSStatusRequest":{"properties":{"task_id":{"type":"integer","title":"Task Id"}},"type":"object","required":["task_id"],"title":"SMSStatusRequest","description":"Request model for checking SMS status.","example":{"task_id":1223}},"cbsms__SMSStatusResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsms__Status"},"message":{"type":"string","title":"Message"},"task_id":{"type":"integer","title":"Task Id"},"total":{"type":"integer","title":"Total"},"sent":{"type":"integer","title":"Sent"},"failed":{"type":"integer","title":"Failed"},"pending":{"type":"integer","title":"Pending"},"delivered":{"type":"integer","title":"Delivered","default":0},"messages":{"items":{"$ref":"#/components/schemas/cbsms__MessageStatus"},"type":"array","title":"Messages"}},"type":"object","required":["status","message","task_id","total","sent","failed","pending","messages"],"title":"SMSStatusResponse","description":"Response model for SMS status check operations.","example":{"failed":0,"message":"Task status retrieved successfully","messages":[{"description":"Message delivered","message_id":1,"recipient":"13686876620","sent_at":"2023-10-25T14:30:00","status":"completed","updated_at":"2023-10-25T14:30:05"}],"pending":0,"sent":1,"status":"ok","task_id":1223,"total":1}},"cbsms__SendSMSResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsms__Status"},"message":{"type":"string","title":"Message"},"task_id":{"type":"integer","title":"Task Id"},"message_count":{"type":"integer","title":"Message Count"},"successful":{"type":"integer","title":"Successful","default":0},"failed":{"type":"integer","title":"Failed","default":0},"pending":{"type":"integer","title":"Pending","default":0}},"type":"object","required":["status","message","task_id","message_count"],"title":"SendSMSResponse","description":"Response model for SMS sending operations.","example":{"failed":0,"message":"SMS task created successfully","message_count":1,"pending":1,"status":"ok","successful":0,"task_id":1223}},"cbsms__Status":{"type":"string","enum":["ok","error","pending","in_progress","completed","failed","sent","delivered","locked_out"],"title":"Status","description":"Status enumeration for various operations."},"cbsms__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbsms__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsms__TaskManagementRequest":{"properties":{"task_ids":{"items":{"type":"integer"},"type":"array","title":"Task Ids"}},"type":"object","required":["task_ids"],"title":"TaskManagementRequest","description":"Base model for task management operations.","example":{"task_ids":[2,3]}},"cbsms__TaskManagementResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsms__Status"},"message":{"type":"string","title":"Message"},"tasks":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Tasks"}},"type":"object","required":["status","message","tasks"],"title":"TaskManagementResponse","description":"Response model for task management operations.","example":{"message":"Tasks processed successfully","status":"ok","tasks":[{"status":"ok","task_id":2},{"status":"ok","task_id":3}]}},"cbsms__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbsnailmail__Address":{"properties":{"name":{"type":"string","title":"Name"},"line1":{"type":"string","title":"Line1"},"line2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line2"},"city":{"type":"string","title":"City"},"state":{"type":"string","title":"State"},"zip":{"type":"string","title":"Zip"},"country":{"type":"string","title":"Country","default":"US"}},"type":"object","required":["name","line1","city","state","zip"],"title":"Address","description":"A US postal address. ``name`` + ``line1`` + ``city``/``state``/``zip``\nare the minimum a provider needs; ``line2`` and ``country`` are optional."},"cbsnailmail__AddressVerifyResult":{"properties":{"deliverable":{"type":"boolean","title":"Deliverable"},"normalized":{"anyOf":[{"$ref":"#/components/schemas/cbsnailmail__Address"},{"type":"null"}]},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"provider":{"type":"string","title":"Provider"}},"type":"object","required":["deliverable","provider"],"title":"AddressVerifyResult","description":"Result of an address-verification check."},"cbsnailmail__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbsnailmail__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbsnailmail__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbsnailmail__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsnailmail__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbsnailmail__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsnailmail__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbsnailmail__MailStatus":{"type":"string","enum":["draft","queued","submitted","in_transit","delivered","returned","failed","canceled","simulated"],"title":"MailStatus","description":"Lifecycle of a mailpiece. Provider webhooks advance it.\n\n``simulated`` is stub-only — it means \"would have been sent, but the\nservice is in stub mode (no key/no cost)\". It is never a real Lob state."},"cbsnailmail__Mailpiece":{"properties":{"id":{"type":"integer","title":"Id"},"tenant":{"type":"string","title":"Tenant"},"to":{"$ref":"#/components/schemas/cbsnailmail__Address"},"from":{"$ref":"#/components/schemas/cbsnailmail__Address"},"piece_type":{"$ref":"#/components/schemas/cbsnailmail__PieceType"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"status":{"$ref":"#/components/schemas/cbsnailmail__MailStatus"},"provider":{"type":"string","title":"Provider"},"provider_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Id"},"cost_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cost Cents"},"tracking":{"items":{"$ref":"#/components/schemas/cbsnailmail__TrackingEvent"},"type":"array","title":"Tracking"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","tenant","to","from","piece_type","description","status","provider","provider_id","cost_cents","created_at","updated_at"],"title":"Mailpiece","description":"A persisted mailpiece with its tracking history."},"cbsnailmail__MailpieceInput":{"properties":{"tenant":{"type":"string","title":"Tenant","default":"default"},"to":{"$ref":"#/components/schemas/cbsnailmail__Address"},"from":{"anyOf":[{"$ref":"#/components/schemas/cbsnailmail__Address"},{"type":"null"}]},"piece_type":{"$ref":"#/components/schemas/cbsnailmail__PieceType","default":"letter"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"artifact_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Artifact Ref"},"content_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Url"},"content_html":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Html"},"draft_only":{"type":"boolean","title":"Draft Only","default":false},"options":{"additionalProperties":true,"type":"object","title":"Options"}},"type":"object","required":["to"],"title":"MailpieceInput","description":"Create-a-mailpiece request."},"cbsnailmail__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbsnailmail__PieceType":{"type":"string","enum":["letter","postcard"],"title":"PieceType","description":"Supported physical formats (the common Lob primitives)."},"cbsnailmail__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbsnailmail__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbsnailmail__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbsnailmail__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbsnailmail__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsnailmail__TrackingEvent":{"properties":{"status":{"$ref":"#/components/schemas/cbsnailmail__MailStatus"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"at":{"type":"string","title":"At"}},"type":"object","required":["status","at"],"title":"TrackingEvent","description":"A single status transition (from a webhook or a submit response)."},"cbsnailmail__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbsocial__Chat":{"properties":{"accountId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accountid","description":"Account this chat belongs to (null = unattributed)."},"jid":{"type":"string","title":"Jid","description":"WhatsApp JID — the contact or group identifier."},"displayName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Displayname","description":"Best-known human-readable name for the chat, if any."},"messageCount":{"type":"integer","title":"Messagecount","description":"Total messages observed in this chat in the archive.","default":0},"lastActivity":{"type":"integer","title":"Lastactivity","description":"Unix timestamp of the most recent message in this chat.","default":0}},"type":"object","required":["jid"],"title":"Chat","description":"Summary of one chat — an (account, JID) pair — with activity stats.\n\nA \"chat\" is a conversation thread: a one-to-one contact or a group,\nidentified by its WhatsApp JID under a given account. The counts are\nderived from the in-memory message index, not stored separately.","example":{"accountId":"default","displayName":"Jane Organizer","jid":"14155550123@s.whatsapp.net","lastActivity":1718841600,"messageCount":42}},"cbsocial__ChatListResponse":{"properties":{"chats":{"items":{"$ref":"#/components/schemas/cbsocial__Chat"},"type":"array","title":"Chats","description":"The chats matching the request."},"total":{"type":"integer","title":"Total","description":"Number of chats returned."}},"type":"object","required":["chats","total"],"title":"ChatListResponse","description":"A list of chats with its element count.","example":{"chats":[{"accountId":"default","displayName":"Jane Organizer","jid":"14155550123@s.whatsapp.net","lastActivity":1718841600,"messageCount":42}],"total":1}},"cbsocial__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbsocial__CreateAccountBody":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Optional human-friendly label for the account, shown in listings and usable as a routing key by /api/v1/send."}},"type":"object","title":"CreateAccountBody","description":"Body for creating a new WhatsApp account (begins QR pairing).","example":{"label":"field-team"}},"cbsocial__CreateSlotBody":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Optional human-friendly label for the slot."},"secretRef":{"type":"string","title":"Secretref","description":"Name of the SLACK_-prefixed env var (SOPS-backed) holding the bot token, e.g. SLACK_TOKEN_ACME."}},"type":"object","required":["secretRef"],"title":"CreateSlotBody","description":"Body for registering a Slack send-only slot.\n\nThe bot token is never sent in this request — only the name of a\n`SLACK_`-prefixed env var (SOPS-backed) that holds it. That ref is\nvalidated against `^SLACK_[A-Z0-9_]+$` so a slot can never be pointed at\nan arbitrary secret in the service environment.","example":{"label":"ACME alerts","secretRef":"SLACK_TOKEN_ACME"}},"cbsocial__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbsocial__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbsocial__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsocial__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbsocial__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsocial__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbsocial__MediaItem":{"properties":{"mimetype":{"type":"string","title":"Mimetype","description":"MIME type of the attachment; selects the Baileys payload kind (image/*, video/*, audio/*, else document)."},"content":{"type":"string","title":"Content","description":"Base64-encoded media bytes."},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename","description":"Original filename (used for document attachments)."},"caption":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caption","description":"Optional caption rendered alongside the media."}},"type":"object","required":["mimetype","content"],"title":"MediaItem","description":"One inline media attachment.\n\n`content` is base64-encoded bytes. The relay maps `mimetype` to the\nBaileys payload kind (image/video/audio/document).","example":{"caption":"Tonight's flyer","content":"iVBORw0KGgoAAAANSUhEUgAA...","filename":"flyer.png","mimetype":"image/png"}},"cbsocial__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbsocial__Message":{"properties":{"accountId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accountid","description":"Account ID this message was received under"},"timestamp":{"type":"integer","title":"Timestamp","description":"Unix timestamp of the message"},"from":{"type":"string","title":"From","description":"Sender display name"},"jid":{"type":"string","title":"Jid","description":"WhatsApp JID (sender/group identifier)"},"fromMe":{"type":"boolean","title":"Fromme","description":"Whether the message was sent by the account owner","default":false},"forwarded":{"type":"boolean","title":"Forwarded","description":"Whether this is a forwarded message","default":false},"forwardingScore":{"type":"integer","title":"Forwardingscore","description":"Number of times the message has been forwarded","default":0},"type":{"type":"string","title":"Type","description":"Message type: text, image, video, audio, document, sticker","default":"text"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Message text content"},"mediaFile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mediafile","description":"Relative path to media file on disk"},"mediaData":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mediadata","description":"Base64-encoded media content (when requested)"},"mediaType":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mediatype","description":"MIME type of the media file"}},"type":"object","required":["timestamp","from","jid"],"title":"Message","description":"A single WhatsApp message parsed from the on-disk JSONL archive.\n\nEach line of `messages_root/<account>/<jid>/messages.jsonl` deserializes\ninto one of these. The wire key is `from` (a Python keyword), so the field\nis declared as `from_` with a populate/serialization alias of `from`.\nMedia is referenced by `mediaFile` on disk; `mediaData` is only populated\nwhen a caller requests inline base64 via `include_media=true`.","example":{"accountId":"default","forwarded":false,"forwardingScore":0,"from":"Jane Organizer","fromMe":false,"jid":"14155550123@s.whatsapp.net","text":"Doors open at 6pm — see you there!","timestamp":1718841600,"type":"text"}},"cbsocial__MessageListResponse":{"properties":{"messages":{"items":{"$ref":"#/components/schemas/cbsocial__Message"},"type":"array","title":"Messages","description":"Messages on the current page."},"total":{"type":"integer","title":"Total","description":"Total messages matching the query."},"limit":{"type":"integer","title":"Limit","description":"Page size that was applied."},"offset":{"type":"integer","title":"Offset","description":"Zero-based offset of the current page."}},"type":"object","required":["messages","total","limit","offset"],"title":"MessageListResponse","description":"A pagination-wrapped page of messages.\n\n`total` is the full match count for the query; `messages` is the current\npage bounded by `limit`/`offset`.","example":{"limit":50,"messages":[{"accountId":"default","from":"Jane Organizer","fromMe":false,"jid":"14155550123@s.whatsapp.net","text":"Doors open at 6pm — see you there!","timestamp":1718841600,"type":"text"}],"offset":0,"total":42}},"cbsocial__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbsocial__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbsocial__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbsocial__ProcessRequest":{"properties":{"since":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Since","description":"Unix timestamp lower bound"},"until":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Until","description":"Unix timestamp upper bound"},"from_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Name","description":"Sender display name filter (substring match)"},"jid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jid","description":"JID filter"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"Message type filter"},"forwarded":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Forwarded","description":"Forwarded message filter"}},"type":"object","title":"ProcessRequest","description":"Query criteria selecting the messages to consolidate.\n\nAll fields are optional and combine as an AND filter; an empty body\nconsolidates the entire archive. The matching slice is folded by cbai\ninto a single Markdown digest (a \"processed run\").","example":{"forwarded":false,"from_name":"Jane","jid":"14155550123@s.whatsapp.net","since":1718668800,"type":"text","until":1718841600}},"cbsocial__ProcessResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique run identifier (UUID)"},"created_at":{"type":"string","title":"Created At","description":"ISO 8601 timestamp of when the run was created"},"message_count":{"type":"integer","title":"Message Count","description":"Number of messages included in this run"},"markdown":{"type":"string","title":"Markdown","description":"AI-consolidated Markdown output"},"manifest_path":{"type":"string","title":"Manifest Path","description":"Disk path to the manifest JSON file"},"markdown_path":{"type":"string","title":"Markdown Path","description":"Disk path to the consolidated Markdown file"},"query":{"additionalProperties":true,"type":"object","title":"Query","description":"Original query filters used for this run"}},"type":"object","required":["id","created_at","message_count","markdown","manifest_path","markdown_path","query"],"title":"ProcessResponse","description":"Result of a consolidation run — the digest plus its on-disk artifacts.","example":{"created_at":"2026-06-20T14:00:00Z","id":"550e8400-e29b-41d4-a716-446655440000","manifest_path":"/data/processed/550e8400/manifest.json","markdown":"# Daily digest\n\n- Doors open at 6pm...","markdown_path":"/data/processed/550e8400/consolidated.md","message_count":42,"query":{"since":1718668800,"type":"text"}}},"cbsocial__SendV1Body":{"properties":{"jid":{"type":"string","title":"Jid","description":"Recipient WhatsApp JID (contact or group)."},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Message text body."},"media":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbsocial__MediaItem"},"type":"array"},{"type":"null"}],"title":"Media","description":"Inline media attachments to send with the message."}},"type":"object","required":["jid"],"title":"SendV1Body","description":"Request body for /api/v1/send.\n\nSame shape as SendBody — keeps `jid` (not `recipient_jid` from the\ncbsocial#13 sketch) for consistency with the rest of the surface.\nThe cbea adapter maps `recipient_jid` → `jid` at the boundary.","example":{"jid":"14155550123@s.whatsapp.net","text":"Sent via X-User-Id routing."}},"cbsocial__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbsocial__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsocial__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbsocial__WebhookBody":{"properties":{"url":{"type":"string","title":"Url","description":"HTTPS URL the relay POSTs each incoming message to."},"envelope":{"type":"boolean","title":"Envelope","description":"If true, wrap the message in an envelope with delivery metadata instead of posting the bare message payload.","default":false}},"type":"object","required":["url"],"title":"WebhookBody","description":"Configuration for an account's incoming-message webhook.","example":{"envelope":false,"url":"https://hooks.campaignbrain.dev/whatsapp/inbound"}},"cbsocial__src__api__accounts__SendBody":{"properties":{"jid":{"type":"string","title":"Jid","description":"Recipient WhatsApp JID (contact or group)."},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Message text body."},"media":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbsocial__MediaItem"},"type":"array"},{"type":"null"}],"title":"Media","description":"Inline media attachments to send with the message."}},"type":"object","required":["jid"],"title":"SendBody","description":"Outbound WhatsApp message: text, media, or both (at least one required).","example":{"jid":"14155550123@s.whatsapp.net","text":"Reminder: doors open at 6pm."}},"cbsocial__src__api__slack__SendBody":{"properties":{"channel":{"type":"string","title":"Channel","description":"Channel ID (C…), DM/user ID (U…/D…), or #channel-name."},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Plain-text (mrkdwn) message body."},"blocks":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Blocks","description":"Slack Block Kit blocks for rich formatting."},"thread_ts":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thread Ts","description":"Parent message `ts` to post this as a threaded reply."},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"Override the bot's display name for this message."},"icon_emoji":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon Emoji","description":"Override the bot's icon with an emoji, e.g. `:rocket:`."}},"type":"object","required":["channel"],"title":"SendBody","description":"Outbound Slack message: text, Block Kit blocks, or both (≥1 required).","example":{"channel":"#alerts","text":"Pipeline run completed: 42 messages consolidated."}},"cbsquare__CheckoutRequest":{"properties":{"amount_cents":{"type":"integer","exclusiveMinimum":0.0,"title":"Amount Cents","description":"Charge amount in the smallest unit."},"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Line-item name, e.g. 'Donation'."},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Description"},"redirect_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redirect Url","description":"Where Square sends the donor after payment."},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency","description":"ISO currency; defaults to config."}},"type":"object","required":["amount_cents","name"],"title":"CheckoutRequest"},"cbsquare__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbsquare__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbsquare__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbsquare__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsquare__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbsquare__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsquare__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbsquare__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbsquare__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbsquare__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbsquare__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbsquare__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbsquare__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsquare__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbstitch__CadenceBody":{"properties":{"interval_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Interval S"},"slide_interval_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Slide Interval S"}},"type":"object","title":"CadenceBody","description":"Body for the cadence setter — either or both intervals, in seconds."},"cbstitch__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbstitch__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbstitch__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbstitch__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbstitch__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbstitch__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbstitch__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbstitch__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbstitch__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbstitch__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbstitch__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbstitch__SensitivityBody":{"properties":{"level":{"type":"string","title":"Level"}},"type":"object","required":["level"],"title":"SensitivityBody","description":"Body for the sensitivity setter — one of :data:`cbstitch.SENSITIVITY_LEVELS`."},"cbstitch__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbstitch__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbstitch__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbstitch__fl_board__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbstitch__fl_board__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbstitch__fl_board__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbstitch__fl_board_d__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbstitch__fl_board_d__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbstitch__fl_board_d__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbstitch__mi_convention_board__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbstitch__mi_convention_board__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbstitch__mi_convention_board__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbstitch__pipe_tagged_responses__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbstitch__pipe_tagged_responses__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbstitch__pipe_tagged_responses__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbstt__Body_submit_multipart_api_v1_transcriptions_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"Audio or video file"},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Tier name; default if omitted"},"metadata":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metadata","description":"JSON object, opaque, ≤2 KB"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"ISO-639-1; autodetect if omitted"},"word_timestamps":{"type":"boolean","title":"Word Timestamps","default":false},"initial_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Initial Prompt"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url"}},"type":"object","required":["file"],"title":"Body_submit_multipart_api_v1_transcriptions_post"},"cbstt__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbstt__FromUrlRequest":{"properties":{"url":{"type":"string","title":"Url","description":"HTTPS URL of the audio/video to transcribe (`cb://` URIs are reserved but not yet supported — V1 returns 501).","examples":["https://media.example.com/clips/town-hall.mp3"]},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Model tier to transcribe with; the configured default is used if omitted. Must be a currently-loaded tier (see GET /models).","examples":["base.en"]},"metadata":{"additionalProperties":{"type":"string"},"type":"object","title":"Metadata","description":"Opaque ≤2 KB JSON object echoed back on every job view; cbstt does not interpret it.","examples":[{"source":"cbintel","station_id":"WXYZ"}]},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"ISO-639-1 language hint; autodetected if omitted.","examples":["en"]},"word_timestamps":{"type":"boolean","title":"Word Timestamps","description":"Emit per-word timestamps in the verbose result.","default":false},"initial_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Initial Prompt","description":"Optional decoder prompt to bias spelling of names/jargon.","examples":["Campaign Brain, cbintel, faster-whisper"]},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url","description":"Optional URL to POST a completion notification to when the job reaches a terminal state.","examples":["https://cbintel.campaignbrain.dev/hooks/stt"]}},"type":"object","required":["url"],"title":"FromUrlRequest","description":"Request body for submitting a transcription job by URL.\n\nInstead of uploading bytes, the caller points cbstt at a remote audio/video\nresource; the service fetches it (streaming, under the upload size cap) and\nenqueues a job. `cb://` URIs are reserved for cbfiles resolution but not yet\nimplemented (returns `501` in V1 — pass an `https://` URL).","examples":[{"language":"en","metadata":{"source":"cbintel","station_id":"WXYZ"},"model":"base.en","url":"https://media.example.com/clips/town-hall.mp3","word_timestamps":true}]},"cbstt__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbstt__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbstt__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbstt__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbstt__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbstt__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbstt__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbstt__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbstt__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbstt__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbstt__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbstt__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbstt__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbsurveys__APIKeyCreateRequest":{"properties":{"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name","description":"Descriptive name for the key (e.g., 'production-server', 'staging-ci')"}},"type":"object","required":["name"],"title":"APIKeyCreateRequest","description":"Request to create a new API key.\n\nAPI keys are used for authentication. Each key should have a descriptive\nname indicating its purpose or the system using it.","example":{"name":"production-backend"}},"cbsurveys__APIKeyCreateResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique key identifier (for revocation)"},"name":{"type":"string","title":"Name","description":"Key name/description"},"prefix":{"type":"string","title":"Prefix","description":"First 8 characters for identification"},"key":{"type":"string","title":"Key","description":"Full API key (SAVE THIS - shown only once!)"}},"type":"object","required":["id","name","prefix","key"],"title":"APIKeyCreateResponse","description":"Response containing a newly created API key.\n\n**IMPORTANT**: The `key` field contains the full API key and is only\nreturned once at creation time. Store it securely - it cannot be retrieved later.\n\nThe key format is: `yasp_<random-token>`","example":{"id":"abc12345-6789-0def-ghij-klmnopqrstuv","key":"yasp_abcdefghijklmnopqrstuvwxyz123456789012","name":"production-backend","prefix":"yasp_abc"}},"cbsurveys__APIKeyResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique key identifier"},"name":{"type":"string","title":"Name","description":"Key name/description"},"prefix":{"type":"string","title":"Prefix","description":"First 8 characters (for identification)"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the key is active (false = revoked)"}},"type":"object","required":["id","name","prefix","is_active"],"title":"APIKeyResponse","description":"API key metadata (without the actual key value).\n\nUsed for listing keys. The actual key value is never retrievable\nafter creation for security reasons.","example":{"id":"abc12345-6789-0def-ghij-klmnopqrstuv","is_active":true,"name":"production-backend","prefix":"yasp_abc"}},"cbsurveys__BranchOp":{"type":"string","enum":["equals","not_equals","includes","answered"],"title":"BranchOp","description":"Comparison operators for skip-logic rules (spec §6).\n\nCompatibility matrix (validated in branching.validate_branching):\n    EQUALS      checkbox (bool value) · single_select (option value)\n    NOT_EQUALS  single_select (option value)\n    INCLUDES    multi_select (option value)\n    ANSWERED    any type (value must be None)"},"cbsurveys__BranchRule":{"properties":{"op":{"$ref":"#/components/schemas/cbsurveys__BranchOp"},"value":{"anyOf":[{"type":"string"},{"type":"boolean"},{"type":"null"}],"title":"Value","description":"Comparand: option value (selects), bool (checkbox); None for ANSWERED."},"goto":{"anyOf":[{"type":"string","format":"uuid"},{"type":"string","const":"end"}],"title":"Goto","description":"Jump target: a later question's id, or 'end' to finish the survey."}},"additionalProperties":false,"type":"object","required":["op","goto"],"title":"BranchRule","description":"Skip-logic rule (spec §6) evaluated against THIS question's own answer.\n\nRules run in list order after the question is answered; the FIRST match\njumps to ``goto`` (a later question's id, or ``\"end\"`` to finish the\nsurvey). No match — or no answer — falls through to the next question in\ndisplay order. Jumps are forward-only (enforced at save), which keeps the\nquestion graph acyclic and every question reachable by construction."},"cbsurveys__CheckboxAnswer":{"properties":{"question_id":{"type":"string","format":"uuid","title":"Question Id"},"question_type":{"type":"string","const":"checkbox","title":"Question Type","default":"checkbox"},"checked":{"type":"boolean","title":"Checked"}},"additionalProperties":false,"type":"object","required":["question_id","checked"],"title":"CheckboxAnswer","description":"Answer to a checkbox question."},"cbsurveys__CheckboxQuestion":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"string","format":"uuid","title":"Id"},"type":{"type":"string","const":"checkbox","title":"Type","default":"checkbox"},"label":{"type":"string","maxLength":64,"minLength":1,"title":"Label","description":"Internal reference label"},"text":{"type":"string","maxLength":1024,"minLength":1,"title":"Text","description":"Display text shown to respondent"},"help_text":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Help Text"},"required":{"type":"boolean","title":"Required","default":false},"display_order":{"type":"integer","title":"Display Order","default":0},"rules":{"items":{"$ref":"#/components/schemas/cbsurveys__BranchRule"},"type":"array","title":"Rules","description":"Skip-logic rules evaluated against this question's answer (first match wins)."},"default_checked":{"type":"boolean","title":"Default Checked","default":false}},"additionalProperties":false,"type":"object","required":["label","text"],"title":"CheckboxQuestion","description":"Yes/No or True/False question."},"cbsurveys__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbsurveys__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbsurveys__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbsurveys__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbsurveys__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbsurveys__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsurveys__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbsurveys__LinkDetail":{"properties":{"slug":{"type":"string","title":"Slug"},"url":{"type":"string","title":"Url"},"row_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Row Id"},"domain":{"type":"string","title":"Domain"},"created_at":{"type":"string","title":"Created At"},"is_active":{"type":"boolean","title":"Is Active"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"}},"type":"object","required":["slug","url","domain","created_at","is_active"],"title":"LinkDetail","description":"A minted link as the operator apps see it — full history row."},"cbsurveys__LinkMintRequest":{"properties":{"row_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Row Id","description":"cblist recipient row_id to attribute responses to; omit for an anonymous (survey-only) link."},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain","description":"Send domain (from the tenant's list); omitted → default."},"first_name":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"First Name","description":"Recipient first name — personalizes the public renderer's greeting."}},"type":"object","title":"LinkMintRequest","description":"Request to mint a public survey link."},"cbsurveys__LinkResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"url":{"type":"string","title":"Url"},"row_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Row Id"}},"type":"object","required":["slug","url"],"title":"LinkResponse"},"cbsurveys__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbsurveys__MetadataEntry":{"properties":{"key":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z][a-z0-9_]*$","title":"Key"},"value":{"type":"string","maxLength":1024,"title":"Value"}},"additionalProperties":false,"type":"object","required":["key","value"],"title":"MetadataEntry","description":"User-supplied key-value metadata."},"cbsurveys__MultiSelectAnswer":{"properties":{"question_id":{"type":"string","format":"uuid","title":"Question Id"},"question_type":{"type":"string","const":"multi_select","title":"Question Type","default":"multi_select"},"selected_values":{"items":{"type":"string"},"type":"array","title":"Selected Values"},"other_text":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Other Text"}},"additionalProperties":false,"type":"object","required":["question_id","selected_values"],"title":"MultiSelectAnswer","description":"Answer to a multi-select question."},"cbsurveys__MultiSelectQuestion":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"string","format":"uuid","title":"Id"},"type":{"type":"string","const":"multi_select","title":"Type","default":"multi_select"},"label":{"type":"string","maxLength":64,"minLength":1,"title":"Label","description":"Internal reference label"},"text":{"type":"string","maxLength":1024,"minLength":1,"title":"Text","description":"Display text shown to respondent"},"help_text":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Help Text"},"required":{"type":"boolean","title":"Required","default":false},"display_order":{"type":"integer","title":"Display Order","default":0},"rules":{"items":{"$ref":"#/components/schemas/cbsurveys__BranchRule"},"type":"array","title":"Rules","description":"Skip-logic rules evaluated against this question's answer (first match wins)."},"options":{"items":{"$ref":"#/components/schemas/cbsurveys__SelectOption"},"type":"array","minItems":2,"title":"Options"},"min_selections":{"type":"integer","minimum":0.0,"title":"Min Selections","default":0},"max_selections":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Selections","description":"None = unlimited"},"allow_other":{"type":"boolean","title":"Allow Other","default":false}},"additionalProperties":false,"type":"object","required":["label","text","options"],"title":"MultiSelectQuestion","description":"Select one or more options from a list."},"cbsurveys__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbsurveys__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbsurveys__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbsurveys__QuestionCreateRequest":{"properties":{"type":{"$ref":"#/components/schemas/cbsurveys__QuestionType","description":"Question type discriminator: checkbox, single_select, multi_select, text, or scale"},"label":{"type":"string","maxLength":64,"minLength":1,"title":"Label","description":"Internal reference label (snake_case recommended, e.g., 'satisfaction_rating')"},"text":{"type":"string","maxLength":1024,"minLength":1,"title":"Text","description":"Question text displayed to respondents"},"help_text":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Help Text","description":"Optional help text shown below the question"},"required":{"type":"boolean","title":"Required","description":"If true, respondents must answer this question","default":false},"display_order":{"type":"integer","title":"Display Order","description":"Position in question list (lower = earlier)","default":0},"default_checked":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Default Checked","description":"[checkbox only] Initial checked state"},"options":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbsurveys__SelectOption"},"type":"array"},{"type":"null"}],"title":"Options","description":"[single_select, multi_select] List of selectable options (min 2)"},"allow_other":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Allow Other","description":"[single_select, multi_select] Allow free-text 'Other' response"},"min_selections":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Selections","description":"[multi_select only] Minimum required selections"},"max_selections":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Selections","description":"[multi_select only] Maximum allowed selections (null = no limit)"},"min_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Length","description":"[text only] Minimum answer character count"},"max_length":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Length","description":"[text only] Maximum answer character count"},"pattern":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Pattern","description":"[text only] Optional Python-regex pattern the answer must fully match"},"multiline":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Multiline","description":"[text only] UI hint: render as textarea"},"scale_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Scale Min","description":"[scale only] Low end — 0 for NPS, 1 for rating rows (default 1)"},"scale_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Scale Max","description":"[scale only] High end — 10 for NPS, 5 for rating rows (default 5)"},"min_label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Min Label","description":"[scale only] Anchor under the low end, e.g. 'Not at all likely'"},"max_label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Max Label","description":"[scale only] Anchor under the high end, e.g. 'Extremely likely'"},"point_labels":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Point Labels","description":"[scale only] One label per point, in order; must have exactly scale_max - scale_min + 1 entries"},"presentation":{"anyOf":[{"type":"string","enum":["stars","row","nps"]},{"type":"null"}],"title":"Presentation","description":"[scale only] Renderer hint; inferred from the bounds when omitted"},"allow_skip":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Allow Skip","description":"[scale only] Offer an escape option ('Did Not Use' / 'Not Applicable')"},"skip_label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Skip Label","description":"[scale only] Label for the escape option; requires allow_skip"},"rules":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbsurveys__BranchRule"},"type":"array"},{"type":"null"}],"title":"Rules","description":"Skip-logic rules evaluated against this question's answer (first match wins; forward-only jumps; goto = question id or 'end')."}},"type":"object","required":["type","label","text"],"title":"QuestionCreateRequest","description":"Request body for creating or updating a question.\n\nThe 'type' field determines which additional fields are required:\n\n**checkbox**: Simple yes/no question\n- default_checked (optional): Initial state, defaults to false\n\n**single_select**: Choose exactly one option\n- options (required): At least 2 SelectOption objects\n- allow_other (optional): Enable free-text \"Other\" option\n\n**multi_select**: Choose one or more options\n- options (required): At least 2 SelectOption objects\n- allow_other (optional): Enable free-text \"Other\" option\n- min_selections (optional): Minimum required selections\n- max_selections (optional): Maximum allowed selections (null = unlimited)\n\n**text**: Free-form answer\n- min_length / max_length (optional): Character bounds\n- pattern (optional): Regex the answer must fully match\n- multiline (optional): UI hint, render as a textarea\n\n**scale**: Numeric rating — use for anything you want to average or trend\n- scale_min / scale_max (optional): 0/10 for NPS, 1/5 for rating rows\n  (defaults 1 and 5)\n- min_label / max_label (optional): Endpoint anchors\n- point_labels (optional): One per point; must have exactly\n  scale_max - scale_min + 1 entries\n- presentation (optional): stars | row | nps; inferred from the bounds\n  when omitted\n- allow_skip / skip_label (optional): The escape option (\"Did Not Use\").\n  skip_label without allow_skip is rejected — the renderer would show the\n  option and the submit would then be refused.","examples":[{"summary":"Checkbox Question","value":{"default_checked":false,"label":"newsletter_signup","required":false,"text":"Would you like to receive our newsletter?","type":"checkbox"}},{"summary":"Single Select Question","value":{"allow_other":false,"label":"satisfaction","options":[{"display_order":1,"label":"Very Satisfied","value":"very_satisfied"},{"display_order":2,"label":"Satisfied","value":"satisfied"},{"display_order":3,"label":"Neutral","value":"neutral"},{"display_order":4,"label":"Dissatisfied","value":"dissatisfied"}],"required":true,"text":"How satisfied are you with our service?","type":"single_select"}},{"summary":"Multi Select Question","value":{"allow_other":true,"label":"features_used","max_selections":3,"min_selections":1,"options":[{"display_order":1,"label":"Dashboard","value":"dashboard"},{"display_order":2,"label":"Reports","value":"reports"},{"display_order":3,"label":"API Access","value":"api"}],"required":false,"text":"Which features do you use? (Select all that apply)","type":"multi_select"}},{"summary":"Scale Question (1-5 stars)","value":{"label":"overall","point_labels":["Poor","Fair","Good","Very Good","Exceptional"],"presentation":"stars","required":true,"scale_max":5,"scale_min":1,"text":"Overall, how was your stay?","type":"scale"}},{"summary":"Scale Question (rating row with an escape option)","value":{"allow_skip":true,"label":"pool_area","presentation":"row","scale_max":5,"scale_min":1,"skip_label":"Did Not Use","text":"Pool area","type":"scale"}},{"summary":"Scale Question (NPS 0-10)","value":{"label":"nps","max_label":"Extremely likely","min_label":"Not at all likely","presentation":"nps","scale_max":10,"scale_min":0,"text":"How likely are you to recommend us?","type":"scale"}}]},"cbsurveys__QuestionOrderRequest":{"properties":{"question_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Question Ids","description":"Ordered list of all question IDs in desired display order"}},"type":"object","required":["question_ids"],"title":"QuestionOrderRequest","description":"Request to reorder questions in a survey.\n\nProvide the complete list of question IDs in the desired order.\nAll existing question IDs must be included exactly once.","example":{"question_ids":["b2c3d4e5-f6a7-8901-bcde-f12345678901","a1b2c3d4-e5f6-7890-abcd-ef1234567890","c3d4e5f6-a7b8-9012-cdef-123456789012"]}},"cbsurveys__QuestionType":{"type":"string","enum":["checkbox","single_select","multi_select","text","scale"],"title":"QuestionType","description":"Discriminator for question type polymorphism.\n\nAdd new types here to extend the question system.\nEach type should have a corresponding model in models.py."},"cbsurveys__ResponseCountResponse":{"properties":{"count":{"type":"integer","title":"Count","description":"Total number of responses"}},"type":"object","required":["count"],"title":"ResponseCountResponse","description":"Response count for a survey.","example":{"count":1234}},"cbsurveys__ResponseDetail":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique response identifier"},"survey_id":{"type":"string","format":"uuid","title":"Survey Id","description":"ID of the survey this response belongs to"},"survey_version":{"type":"integer","title":"Survey Version","description":"Survey schema version at submission time"},"respondent_id":{"type":"string","title":"Respondent Id","description":"Respondent's identifier"},"row_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Row Id","description":"cblist recipient this response is attributed to (set via a per-recipient tracked link); null for anonymous/untracked."},"person_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Person Id","description":"cbcrm golden person, if resolved."},"answers":{"items":{"oneOf":[{"$ref":"#/components/schemas/cbsurveys__CheckboxAnswer"},{"$ref":"#/components/schemas/cbsurveys__SingleSelectAnswer"},{"$ref":"#/components/schemas/cbsurveys__MultiSelectAnswer"},{"$ref":"#/components/schemas/cbsurveys__TextAnswer"},{"$ref":"#/components/schemas/cbsurveys__ScaleAnswer"}],"discriminator":{"propertyName":"question_type","mapping":{"checkbox":"#/components/schemas/CheckboxAnswer","multi_select":"#/components/schemas/MultiSelectAnswer","scale":"#/components/schemas/ScaleAnswer","single_select":"#/components/schemas/SingleSelectAnswer","text":"#/components/schemas/TextAnswer"}}},"type":"array","title":"Answers","description":"List of answers with question-type-specific data"},"metadata":{"items":{"$ref":"#/components/schemas/cbsurveys__MetadataEntry"},"type":"array","title":"Metadata","description":"Custom tracking metadata"},"submitted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Submitted At","description":"Submission timestamp"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last modification timestamp"}},"type":"object","required":["id","survey_id","survey_version","respondent_id","answers","metadata","submitted_at","created_at","updated_at"],"title":"ResponseDetail","description":"Complete response with all answers and metadata.\n\nIncludes the full answer array with type-specific data.\nAnswer types are discriminated by the `question_type` field.","example":{"answers":[{"checked":true,"question_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","question_type":"checkbox"},{"question_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","question_type":"single_select","selected_value":"very_satisfied"}],"created_at":"2025-01-15T14:28:00Z","id":"resp-1234-5678-90ab-cdef12345678","metadata":[{"key":"source","value":"email_campaign"}],"respondent_id":"user_12345","submitted_at":"2025-01-15T14:30:00Z","survey_id":"550e8400-e29b-41d4-a716-446655440000","survey_version":1,"updated_at":"2025-01-15T14:30:00Z"}},"cbsurveys__ResponseListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/cbsurveys__ResponseSummary"},"type":"array","title":"Items","description":"Response summaries for this page"},"total":{"type":"integer","title":"Total","description":"Total number of responses matching the query"},"limit":{"type":"integer","title":"Limit","description":"Maximum items per page (as requested)"},"offset":{"type":"integer","title":"Offset","description":"Number of items skipped (as requested)"}},"type":"object","required":["items","total","limit","offset"],"title":"ResponseListResponse","description":"Paginated response list with metadata.\n\nStandard pagination: use `limit` and `offset` to page through results.\nThe `total` field indicates the full count for calculating pages.","example":{"items":[{"answer_count":5,"created_at":"2025-01-15T14:28:00Z","id":"resp-1234-5678-90ab-cdef12345678","respondent_id":"user_12345","submitted_at":"2025-01-15T14:30:00Z","survey_version":1}],"limit":100,"offset":0,"total":150}},"cbsurveys__ResponseSubmitRequest":{"properties":{"respondent_id":{"type":"string","maxLength":256,"minLength":1,"title":"Respondent Id","description":"Identifier for the respondent (user ID, session ID, or anonymous token)"},"answers":{"items":{"oneOf":[{"$ref":"#/components/schemas/cbsurveys__CheckboxAnswer"},{"$ref":"#/components/schemas/cbsurveys__SingleSelectAnswer"},{"$ref":"#/components/schemas/cbsurveys__MultiSelectAnswer"},{"$ref":"#/components/schemas/cbsurveys__TextAnswer"},{"$ref":"#/components/schemas/cbsurveys__ScaleAnswer"}],"discriminator":{"propertyName":"question_type","mapping":{"checkbox":"#/components/schemas/CheckboxAnswer","multi_select":"#/components/schemas/MultiSelectAnswer","scale":"#/components/schemas/ScaleAnswer","single_select":"#/components/schemas/SingleSelectAnswer","text":"#/components/schemas/TextAnswer"}}},"type":"array","title":"Answers","description":"List of answers, one per question. Each answer must include question_id and question_type."},"metadata":{"items":{"$ref":"#/components/schemas/cbsurveys__MetadataEntry"},"type":"array","title":"Metadata","description":"Optional key-value pairs for tracking (e.g., source, campaign, referrer)","default":[]}},"type":"object","required":["respondent_id","answers"],"title":"ResponseSubmitRequest","description":"Request to submit a survey response.\n\nSubmit answers to an active survey. The survey must be in ACTIVE state.\nNo authentication is required for submission.","example":{"answers":[{"checked":true,"question_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","question_type":"checkbox"},{"question_id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","question_type":"single_select","selected_value":"very_satisfied"}],"metadata":[{"key":"source","value":"email_campaign"},{"key":"utm_campaign","value":"q1_feedback"}],"respondent_id":"user_12345"}},"cbsurveys__ResponseSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique response identifier"},"respondent_id":{"type":"string","title":"Respondent Id","description":"Respondent's identifier"},"row_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Row Id","description":"cblist recipient this response is attributed to (set via a per-recipient tracked link); null for anonymous/untracked."},"person_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Person Id","description":"cbcrm golden person, if resolved."},"survey_version":{"type":"integer","title":"Survey Version","description":"Version of survey schema when response was submitted"},"answer_count":{"type":"integer","title":"Answer Count","description":"Number of answers in this response"},"submitted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Submitted At","description":"When the response was finalized (null if draft)"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the response record was created"}},"type":"object","required":["id","respondent_id","survey_version","answer_count","submitted_at","created_at"],"title":"ResponseSummary","description":"Compact response representation for list views.\n\nUsed in paginated listings. Use GET /surveys/{id}/responses/{response_id}\nfor full response details including all answers.","example":{"answer_count":5,"created_at":"2025-01-15T14:28:00Z","id":"resp-1234-5678-90ab-cdef12345678","respondent_id":"user_12345","submitted_at":"2025-01-15T14:30:00Z","survey_version":1}},"cbsurveys__ScaleAnswer":{"properties":{"question_id":{"type":"string","format":"uuid","title":"Question Id"},"question_type":{"type":"string","const":"scale","title":"Question Type","default":"scale"},"value":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Value","description":"None when skipped."},"skipped":{"type":"boolean","title":"Skipped","default":false}},"additionalProperties":false,"type":"object","required":["question_id"],"title":"ScaleAnswer","description":"Answer to a scale question.\n\n``skipped`` is a FLAG, not a sentinel value, and that is the whole point.\nThe rating rows offer \"Did Not Use\" / \"Not Applicable\"; stored as 0 it would\ndrag every average down in proportion to how often guests did not use an\namenity, inverting the meaning of the number. A skipped answer carries\n``value=None`` and must be excluded from aggregation."},"cbsurveys__ScaleQuestion":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"string","format":"uuid","title":"Id"},"type":{"type":"string","const":"scale","title":"Type","default":"scale"},"label":{"type":"string","maxLength":64,"minLength":1,"title":"Label","description":"Internal reference label"},"text":{"type":"string","maxLength":1024,"minLength":1,"title":"Text","description":"Display text shown to respondent"},"help_text":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Help Text"},"required":{"type":"boolean","title":"Required","default":false},"display_order":{"type":"integer","title":"Display Order","default":0},"rules":{"items":{"$ref":"#/components/schemas/cbsurveys__BranchRule"},"type":"array","title":"Rules","description":"Skip-logic rules evaluated against this question's answer (first match wins)."},"scale_min":{"type":"integer","title":"Scale Min","description":"0 for NPS, 1 for rating rows","default":1},"scale_max":{"type":"integer","title":"Scale Max","description":"10 for NPS, 5 for rating rows","default":5},"min_label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Min Label","description":"Anchor under the low end, e.g. 'Not at all likely'"},"max_label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Max Label","description":"Anchor under the high end, e.g. 'Extremely likely'"},"point_labels":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Point Labels","description":"One label per point, in order (e.g. Poor..Exceptional). None = numerals only."},"presentation":{"anyOf":[{"type":"string","enum":["stars","row","nps"]},{"type":"null"}],"title":"Presentation","description":"Renderer hint; inferred from the bounds when absent."},"allow_skip":{"type":"boolean","title":"Allow Skip","description":"Offer an escape option ('Did Not Use' / 'Not Applicable').","default":false},"skip_label":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Skip Label"}},"additionalProperties":false,"type":"object","required":["label","text"],"title":"ScaleQuestion","description":"A numeric rating: 1-5 stars, an eight-row 1-5 block, or an NPS 0-10.\n\nThe only way to express a rating before this was a string-valued\nsingle_select, whose answers cannot be averaged or trended — so a dashboard\nand a low-score alert were both impossible (cbsurveys#8).\n\n``presentation`` is a RENDERER HINT authored per question, not a derived\nproperty. The renderer can infer stars/nps/row from the bounds when it is\nabsent, but two questions with identical bounds can legitimately want\ndifferent treatments, so an explicit value always wins."},"cbsurveys__SelectOption":{"properties":{"value":{"type":"string","maxLength":128,"minLength":1,"title":"Value"},"label":{"type":"string","maxLength":256,"minLength":1,"title":"Label"},"display_order":{"type":"integer","title":"Display Order","default":0}},"additionalProperties":false,"type":"object","required":["value","label"],"title":"SelectOption","description":"Single option for select-type questions."},"cbsurveys__ServiceUserCreate":{"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"email":{"type":"string","maxLength":256,"title":"Email"}},"additionalProperties":false,"type":"object","required":["name","email"],"title":"ServiceUserCreate","description":"Request model for creating a service user."},"cbsurveys__SingleSelectAnswer":{"properties":{"question_id":{"type":"string","format":"uuid","title":"Question Id"},"question_type":{"type":"string","const":"single_select","title":"Question Type","default":"single_select"},"selected_value":{"type":"string","title":"Selected Value"},"other_text":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Other Text"}},"additionalProperties":false,"type":"object","required":["question_id","selected_value"],"title":"SingleSelectAnswer","description":"Answer to a single-select question."},"cbsurveys__SingleSelectQuestion":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"string","format":"uuid","title":"Id"},"type":{"type":"string","const":"single_select","title":"Type","default":"single_select"},"label":{"type":"string","maxLength":64,"minLength":1,"title":"Label","description":"Internal reference label"},"text":{"type":"string","maxLength":1024,"minLength":1,"title":"Text","description":"Display text shown to respondent"},"help_text":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Help Text"},"required":{"type":"boolean","title":"Required","default":false},"display_order":{"type":"integer","title":"Display Order","default":0},"rules":{"items":{"$ref":"#/components/schemas/cbsurveys__BranchRule"},"type":"array","title":"Rules","description":"Skip-logic rules evaluated against this question's answer (first match wins)."},"options":{"items":{"$ref":"#/components/schemas/cbsurveys__SelectOption"},"type":"array","minItems":2,"title":"Options"},"allow_other":{"type":"boolean","title":"Allow Other","description":"Allow free-text 'Other' option","default":false}},"additionalProperties":false,"type":"object","required":["label","text","options"],"title":"SingleSelectQuestion","description":"Select exactly one option from a list."},"cbsurveys__StateTransitionRequest":{"properties":{"state":{"$ref":"#/components/schemas/cbsurveys__SurveyState","description":"Target state for the survey"}},"type":"object","required":["state"],"title":"StateTransitionRequest","description":"Request to change survey lifecycle state.\n\nValid transitions:\n- DRAFT → ACTIVE (requires at least one question)\n- ACTIVE → INACTIVE\n- INACTIVE → ACTIVE\n- Any state → ARCHIVED (terminal, cannot be undone)","example":{"state":"active"}},"cbsurveys__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbsurveys__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbsurveys__SurveyCreate":{"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Description"},"metadata":{"items":{"$ref":"#/components/schemas/cbsurveys__MetadataEntry"},"type":"array","title":"Metadata"}},"additionalProperties":false,"type":"object","required":["name"],"title":"SurveyCreate","description":"Request model for creating a survey."},"cbsurveys__SurveyDetailResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique survey identifier"},"owner_id":{"type":"string","format":"uuid","title":"Owner Id","description":"ID of the user who owns this survey"},"name":{"type":"string","title":"Name","description":"Human-readable survey name (1-128 chars)"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional longer description (up to 2048 chars)"},"state":{"$ref":"#/components/schemas/cbsurveys__SurveyState","description":"Current lifecycle state"},"version":{"type":"integer","title":"Version","description":"Schema version, incremented on structural changes"},"metadata":{"items":{"$ref":"#/components/schemas/cbsurveys__MetadataEntry"},"type":"array","title":"Metadata","description":"Custom key-value pairs for categorization"},"question_count":{"type":"integer","title":"Question Count","description":"Number of questions in this survey"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last modification timestamp"},"questions":{"items":{"oneOf":[{"$ref":"#/components/schemas/cbsurveys__CheckboxQuestion"},{"$ref":"#/components/schemas/cbsurveys__SingleSelectQuestion"},{"$ref":"#/components/schemas/cbsurveys__MultiSelectQuestion"},{"$ref":"#/components/schemas/cbsurveys__TextQuestion"},{"$ref":"#/components/schemas/cbsurveys__ScaleQuestion"}],"discriminator":{"propertyName":"type","mapping":{"checkbox":"#/components/schemas/CheckboxQuestion","multi_select":"#/components/schemas/MultiSelectQuestion","scale":"#/components/schemas/ScaleQuestion","single_select":"#/components/schemas/SingleSelectQuestion","text":"#/components/schemas/TextQuestion"}}},"type":"array","title":"Questions","description":"Ordered list of survey questions"}},"type":"object","required":["id","owner_id","name","state","version","metadata","question_count","created_at","updated_at","questions"],"title":"SurveyDetailResponse","description":"Complete survey with all question definitions.\n\nIncludes the full question array with type-specific configurations.\nQuestion types are discriminated by the 'type' field:\n- checkbox: Yes/No toggle\n- scale: Numeric rating (1-5 stars, 1-5 row, NPS 0-10) — use this for\n  anything you want to average or trend; select answers are strings\n- single_select: Choose one from options\n- multi_select: Choose multiple from options","example":{"created_at":"2025-01-15T10:30:00Z","description":"Quarterly customer feedback survey","id":"550e8400-e29b-41d4-a716-446655440000","metadata":[],"name":"Customer Satisfaction Q1 2025","owner_id":"123e4567-e89b-12d3-a456-426614174000","question_count":2,"questions":[{"created_at":"2025-01-15T10:35:00Z","default_checked":false,"display_order":0,"help_text":"Monthly updates about new features","id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","label":"newsletter_signup","required":false,"text":"Would you like to receive our newsletter?","type":"checkbox","updated_at":"2025-01-15T10:35:00Z"},{"allow_other":false,"created_at":"2025-01-15T10:36:00Z","display_order":1,"id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","label":"satisfaction_rating","options":[{"display_order":1,"label":"Very Satisfied","value":"very_satisfied"},{"display_order":2,"label":"Satisfied","value":"satisfied"},{"display_order":3,"label":"Neutral","value":"neutral"},{"display_order":4,"label":"Dissatisfied","value":"dissatisfied"}],"required":true,"text":"How satisfied are you with our product?","type":"single_select","updated_at":"2025-01-15T10:36:00Z"}],"state":"draft","updated_at":"2025-01-15T10:30:00Z","version":1}},"cbsurveys__SurveyListItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique survey identifier (UUID)"},"name":{"type":"string","title":"Name","description":"Human-readable survey name"},"state":{"$ref":"#/components/schemas/cbsurveys__SurveyState","description":"Current lifecycle state: draft, active, inactive, or archived"},"response_count":{"type":"integer","title":"Response Count","description":"Total number of responses submitted to this survey","default":0},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the survey was created (ISO 8601)"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the survey was last modified (ISO 8601)"},"owner_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Owner Id","description":"Who owns this survey. Present only for an administrator listing across owners (cbsurveys#12) — an owner-scoped list is all one owner by definition, so the field is noise there and None. Without it a cross-owner list answers 'what exists' but not 'who can already read it', which is half the question an administrator is asking."}},"type":"object","required":["id","name","state","created_at","updated_at"],"title":"SurveyListItem","description":"Compact survey representation for list views.\n\nUsed in survey listings to provide essential information without\nincluding the full question definitions.","example":{"created_at":"2025-01-15T10:30:00Z","id":"550e8400-e29b-41d4-a716-446655440000","name":"Customer Satisfaction Q1 2025","response_count":42,"state":"active","updated_at":"2025-01-16T14:22:00Z"}},"cbsurveys__SurveyResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique survey identifier"},"owner_id":{"type":"string","format":"uuid","title":"Owner Id","description":"ID of the user who owns this survey"},"name":{"type":"string","title":"Name","description":"Human-readable survey name (1-128 chars)"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional longer description (up to 2048 chars)"},"state":{"$ref":"#/components/schemas/cbsurveys__SurveyState","description":"Current lifecycle state"},"version":{"type":"integer","title":"Version","description":"Schema version, incremented on structural changes"},"metadata":{"items":{"$ref":"#/components/schemas/cbsurveys__MetadataEntry"},"type":"array","title":"Metadata","description":"Custom key-value pairs for categorization"},"question_count":{"type":"integer","title":"Question Count","description":"Number of questions in this survey"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last modification timestamp"}},"type":"object","required":["id","owner_id","name","state","version","metadata","question_count","created_at","updated_at"],"title":"SurveyResponse","description":"Full survey metadata without questions.\n\nReturned after create/update operations. Use GET /surveys/{id}\nto retrieve the full survey including questions.","example":{"created_at":"2025-01-15T10:30:00Z","description":"Quarterly customer feedback survey","id":"550e8400-e29b-41d4-a716-446655440000","metadata":[{"key":"campaign_id","value":"q1_2025"},{"key":"department","value":"product"}],"name":"Customer Satisfaction Q1 2025","owner_id":"123e4567-e89b-12d3-a456-426614174000","question_count":5,"state":"draft","updated_at":"2025-01-15T10:30:00Z","version":1}},"cbsurveys__SurveyState":{"type":"string","enum":["draft","active","inactive","archived"],"title":"SurveyState","description":"Survey lifecycle states.\n\nState transitions:\n    DRAFT -> ACTIVE (publish)\n    ACTIVE -> INACTIVE (pause)\n    INACTIVE -> ACTIVE (resume)\n    ACTIVE -> ARCHIVED (close)\n    INACTIVE -> ARCHIVED (close)\n    DRAFT -> ARCHIVED (discard)"},"cbsurveys__SurveyUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"metadata":{"anyOf":[{"items":{"$ref":"#/components/schemas/cbsurveys__MetadataEntry"},"type":"array"},{"type":"null"}],"title":"Metadata"}},"additionalProperties":false,"type":"object","title":"SurveyUpdate","description":"Request model for updating survey metadata (not questions)."},"cbsurveys__TextAnswer":{"properties":{"question_id":{"type":"string","format":"uuid","title":"Question Id"},"question_type":{"type":"string","const":"text","title":"Question Type","default":"text"},"text":{"type":"string","maxLength":8192,"title":"Text"}},"additionalProperties":false,"type":"object","required":["question_id","text"],"title":"TextAnswer","description":"Answer to a free-form text question."},"cbsurveys__TextQuestion":{"properties":{"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"id":{"type":"string","format":"uuid","title":"Id"},"type":{"type":"string","const":"text","title":"Type","default":"text"},"label":{"type":"string","maxLength":64,"minLength":1,"title":"Label","description":"Internal reference label"},"text":{"type":"string","maxLength":1024,"minLength":1,"title":"Text","description":"Display text shown to respondent"},"help_text":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Help Text"},"required":{"type":"boolean","title":"Required","default":false},"display_order":{"type":"integer","title":"Display Order","default":0},"rules":{"items":{"$ref":"#/components/schemas/cbsurveys__BranchRule"},"type":"array","title":"Rules","description":"Skip-logic rules evaluated against this question's answer (first match wins)."},"min_length":{"type":"integer","minimum":0.0,"title":"Min Length","description":"Minimum answer character count","default":0},"max_length":{"type":"integer","maximum":8192.0,"minimum":1.0,"title":"Max Length","description":"Maximum answer character count","default":512},"pattern":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Pattern","description":"Optional Python-regex pattern the answer must fully match"},"multiline":{"type":"boolean","title":"Multiline","description":"Hint to UI: render as textarea vs input","default":false}},"additionalProperties":false,"type":"object","required":["label","text"],"title":"TextQuestion","description":"Free-form text question (single-line or multi-line)."},"cbsurveys__TransferRequest":{"properties":{"new_owner_email":{"type":"string","title":"New Owner Email","description":"Email of the new owner. Resolved through the same get_or_create_user_by_email the mesh-trust path uses, so an operator who has never called the API directly still resolves.","examples":["CJ@stj.media"]}},"type":"object","required":["new_owner_email"],"title":"TransferRequest","description":"Who the survey should belong to afterwards."},"cbsurveys__UserResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique user identifier (UUID)"},"name":{"type":"string","title":"Name","description":"Display name (1-128 chars)"},"email":{"type":"string","title":"Email","description":"Email address (used for identification, not notifications)"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the account is active (false = soft deleted)"}},"type":"object","required":["id","name","email","is_active"],"title":"UserResponse","description":"Service user account information.\n\nService users are API consumers that can create surveys and access data.\nEach user can have multiple API keys for different integrations.","example":{"email":"api@example.com","id":"123e4567-e89b-12d3-a456-426614174000","is_active":true,"name":"API Service Account"}},"cbsurveys__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbsurveys__yasp__api__routes__responses__ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message"}},"type":"object","required":["detail"],"title":"ErrorResponse","description":"Standard error response format."},"cbsurveys__yasp__api__routes__surveys__ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message"}},"type":"object","required":["detail"],"title":"ErrorResponse","description":"Standard error response format.","example":{"detail":"Survey not found: 550e8400-e29b-41d4-a716-446655440000"}},"cbsurveys__yasp__api__routes__users__ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message"}},"type":"object","required":["detail"],"title":"ErrorResponse","description":"Standard error response format."},"cbtags__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbtags__EntityBinding":{"properties":{"entity_type":{"type":"string","title":"Entity Type","description":"The kind of record referenced."},"entity_id":{"type":"string","title":"Entity Id","description":"The id of the record."},"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value","description":"Per-binding value, or null if the binding carries none."}},"type":"object","required":["entity_type","entity_id"],"title":"EntityBinding","description":"A record bound to a tag, with the value carried by that binding.","examples":[{"entity_id":"ac5f0f34-23c3-4225-9926-9a1975b55576","entity_type":"cbcron_job","value":"ephemeral"}]},"cbtags__EntityRef":{"properties":{"entity_type":{"type":"string","maxLength":64,"minLength":1,"title":"Entity Type","description":"The kind of record being referenced (free-form namespace).","examples":["person","file","dataset","workflow_run"]},"entity_id":{"type":"string","maxLength":256,"minLength":1,"title":"Entity Id","description":"The id of the record within its entity_type.","examples":["person_42","s3://bucket/key.pdf","9f1c2b7e"]},"value":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Value","description":"Optional per-binding value, giving AWS-style `key=value` tagging: the **tag name is the key** and this is the value *for this record*. Two records can carry the same tag with different values, which a tag-level field could not express. Re-assigning with a new value updates it. Ignored on unassign.","examples":["ephemeral","critical",null]}},"type":"object","required":["entity_type","entity_id"],"title":"EntityRef","description":"A polymorphic reference to anything that can be tagged.\n\nThe `(entity_type, entity_id)` pair points at any record across the fleet —\na person, file, survey, dataset, link, or workflow run — with no foreign-key\nconstraint on the target. Used as the body for assign/unassign.","examples":[{"entity_id":"person_42","entity_type":"person"},{"entity_id":"ac5f0f34-23c3-4225-9926-9a1975b55576","entity_type":"cbcron_job","value":"ephemeral"}]},"cbtags__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbtags__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbtags__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbtags__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbtags__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbtags__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbtags__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbtags__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbtags__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbtags__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbtags__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbtags__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbtags__Tag":{"properties":{"id":{"type":"string","title":"Id","description":"Server-assigned unique tag identifier.","examples":["tag_01HXYZ8K3M","9f1c2b7e"]},"tenant":{"type":"string","title":"Tenant","description":"Owning tenant id (from the X-Tenant-ID header).","examples":["default","greenroom"]},"name":{"type":"string","title":"Name","description":"Human-readable tag label, unique within the tenant.","examples":["VIP","needs-review"]},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Optional grouping bucket, or null if uncategorized.","examples":["audience",null]},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color","description":"Optional display color, or null if unset.","examples":["#e11d48",null]},"created_at":{"type":"string","title":"Created At","description":"UTC ISO-8601 timestamp of when the tag was created.","examples":["2026-06-20T14:48:00+00:00"]}},"type":"object","required":["id","tenant","name","created_at"],"title":"Tag","description":"A tenant-scoped, named tag as stored and returned by the service.","examples":[{"category":"audience","color":"#e11d48","created_at":"2026-06-20T14:48:00+00:00","id":"tag_01HXYZ8K3M","name":"VIP","tenant":"default"}]},"cbtags__TagCreate":{"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name","description":"Human-readable tag label, unique per tenant.","examples":["VIP","needs-review","donor"]},"category":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Category","description":"Optional grouping bucket used to namespace/filter tags.","examples":["status","audience",null]},"color":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Color","description":"Optional display color (hex or CSS name) for UI rendering.","examples":["#e11d48","amber",null]}},"type":"object","required":["name"],"title":"TagCreate","description":"Request body for creating a tag.\n\nCreation is **idempotent on name** within a tenant: posting a name that\nalready exists returns the existing tag unchanged rather than erroring.","examples":[{"category":"audience","color":"#e11d48","name":"VIP"}]},"cbtags__TagUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Name","description":"New label for the tag. Omit to leave unchanged.","examples":["VIP","high-value"]},"category":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Category","description":"New category. Omit to leave unchanged.","examples":["audience","status"]},"color":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Color","description":"New display color. Omit to leave unchanged.","examples":["#16a34a","teal"]}},"type":"object","title":"TagUpdate","description":"Request body for a partial tag update.\n\nOnly the fields present are applied; omitted fields are left unchanged.","examples":[{"color":"#16a34a"}]},"cbtags__TagUsage":{"properties":{"tag":{"$ref":"#/components/schemas/cbtags__Tag","description":"The tag being counted."},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Number of records currently bound to this tag.","examples":[0,17,1432]}},"type":"object","required":["tag","count"],"title":"TagUsage","description":"A tag paired with how many records currently carry it."},"cbtags__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbtapwatch__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbtapwatch__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbtapwatch__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbtapwatch__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbtapwatch__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbtapwatch__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbtapwatch__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbtapwatch__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbtapwatch__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbtapwatch__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbtapwatch__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbtapwatch__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbtapwatch__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbtapwatch__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbtenant__AnalysisHistoryResponse":{"properties":{"analyses":{"items":{"$ref":"#/components/schemas/cbtenant__AnalysisListItem"},"type":"array","title":"Analyses"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["analyses","total"],"title":"AnalysisHistoryResponse"},"cbtenant__AnalysisListItem":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"num_suggestions":{"type":"integer","title":"Num Suggestions","default":0}},"type":"object","required":["id","status","url","created_at","completed_at"],"title":"AnalysisListItem"},"cbtenant__Body_login_api_auth_login_post":{"properties":{"grant_type":{"anyOf":[{"type":"string","pattern":"^password$"},{"type":"null"}],"title":"Grant Type"},"username":{"type":"string","title":"Username"},"password":{"type":"string","format":"password","title":"Password"},"scope":{"type":"string","title":"Scope","default":""},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"password","title":"Client Secret"}},"type":"object","required":["username","password"],"title":"Body_login_api_auth_login_post"},"cbtenant__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbtenant__DeploymentResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Deployment record ID (UUID)"},"tenant_id":{"type":"string","title":"Tenant Id","description":"Associated tenant ID"},"status":{"type":"string","title":"Status","description":"Deployment status: pending, running, complete, failed","example":"complete"},"deployment_type":{"type":"string","title":"Deployment Type","description":"Type of deployment: initial, upgrade, redeploy","example":"upgrade"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At","description":"Deployment start timestamp"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"Deployment completion timestamp"},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds","description":"Total deployment duration in seconds","example":120},"current_step":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Step","description":"Current step being executed","example":"Running migrations"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Error message if deployment failed"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Record creation timestamp"}},"type":"object","required":["id","tenant_id","status","deployment_type","created_at"],"title":"DeploymentResponse","description":"Deployment record tracking a tenant deployment operation.\n\n**Status values:**\n- `pending`: Deployment queued\n- `running`: Deployment in progress\n- `complete`: Successfully completed\n- `failed`: Deployment failed (check error_message)"},"cbtenant__FirstAdminCreate":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name","description":"Display name for the new admin (defaults to email-local-part)"}},"type":"object","required":["email"],"title":"FirstAdminCreate"},"cbtenant__FirstAdminResponse":{"properties":{"user_id":{"type":"string","title":"User Id"},"email":{"type":"string","title":"Email"},"display_name":{"type":"string","title":"Display Name"},"tenant_slug":{"type":"string","title":"Tenant Slug"},"role":{"type":"string","title":"Role"},"login_url":{"type":"string","title":"Login Url"},"temp_password":{"type":"string","title":"Temp Password"},"created":{"type":"boolean","title":"Created"},"membership_created":{"type":"boolean","title":"Membership Created"},"vault_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vault Ref"},"vault_stored":{"type":"boolean","title":"Vault Stored","default":false}},"type":"object","required":["user_id","email","display_name","tenant_slug","role","login_url","temp_password","created","membership_created"],"title":"FirstAdminResponse"},"cbtenant__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbtenant__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbtenant__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbtenant__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbtenant__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbtenant__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbtenant__LoginRequest":{"properties":{"username":{"type":"string","maxLength":100,"minLength":1,"title":"Username","description":"Username for authentication","example":"admin"},"password":{"type":"string","minLength":1,"title":"Password","description":"Password for authentication","example":"your-secure-password"}},"type":"object","required":["username","password"],"title":"LoginRequest","description":"Login credentials for JSON authentication endpoint.\n\nUse this model with POST /api/auth/login/json for programmatic access.\nFor browser-based login, use the OAuth2 form endpoint at POST /api/auth/login."},"cbtenant__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbtenant__NextAvailablePortsResponse":{"properties":{"frontend_port":{"type":"integer","title":"Frontend Port","description":"Available frontend port","example":32310},"backend_port":{"type":"integer","title":"Backend Port","description":"Available backend port","example":32311}},"type":"object","required":["frontend_port","backend_port"],"title":"NextAvailablePortsResponse","description":"Next available port pair for new tenant.\n\nReturns the first unallocated port pair that is also not in use\nby any external process on the system."},"cbtenant__NginxConfigResponse":{"properties":{"domain":{"type":"string","title":"Domain"},"api_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Domain"},"config_path":{"type":"string","title":"Config Path"},"api_config_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Config Path"},"message":{"type":"string","title":"Message"}},"type":"object","required":["domain","config_path","message"],"title":"NginxConfigResponse","description":"NGINX config generation response."},"cbtenant__PortAllocationResponse":{"properties":{"frontend_port":{"type":"integer","title":"Frontend Port","description":"Port number for frontend service","example":32300},"backend_port":{"type":"integer","title":"Backend Port","description":"Port number for backend API service","example":32301},"tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id","description":"ID of tenant using this port pair (null if available)"},"tenant_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Name","description":"Name of tenant using this port pair"},"allocated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Allocated At","description":"When port was allocated to tenant"},"status":{"type":"string","title":"Status","description":"Allocation status: available, in_use, conflict","example":"in_use"}},"type":"object","required":["frontend_port","backend_port","status"],"title":"PortAllocationResponse","description":"Port allocation record.\n\nEach allocation is a pair of ports (frontend + backend) that can be\nassigned to a tenant. Port range: 32300-32399.\n\n**Status values:**\n- `available`: Not assigned to any tenant\n- `in_use`: Assigned to a tenant\n- `conflict`: Not assigned but system port is in use (external process)"},"cbtenant__PortsListResponse":{"properties":{"allocations":{"items":{"$ref":"#/components/schemas/cbtenant__PortAllocationResponse"},"type":"array","title":"Allocations","description":"All port allocations in the pool"},"available_count":{"type":"integer","title":"Available Count","description":"Number of unallocated port pairs","example":45},"in_use_count":{"type":"integer","title":"In Use Count","description":"Number of allocated port pairs","example":5}},"type":"object","required":["allocations","available_count","in_use_count"],"title":"PortsListResponse","description":"List of all port allocations with summary counts."},"cbtenant__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbtenant__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbtenant__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbtenant__PullRequest":{"properties":{"restart":{"type":"string","title":"Restart","default":"both"}},"type":"object","title":"PullRequest","description":"Quick pull request parameters."},"cbtenant__PullResponse":{"properties":{"status":{"type":"string","title":"Status"},"previous_commit":{"type":"string","title":"Previous Commit"},"new_commit":{"type":"string","title":"New Commit"},"files_changed":{"type":"integer","title":"Files Changed"},"restarted":{"items":{"type":"string"},"type":"array","title":"Restarted"},"stash_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stash Message"}},"type":"object","required":["status","previous_commit","new_commit","files_changed","restarted"],"title":"PullResponse","description":"Quick pull response."},"cbtenant__ServiceProvisionResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"True if at least one service provisioned successfully","example":true},"services":{"additionalProperties":{"$ref":"#/components/schemas/cbtenant__ServiceProvisionResult"},"type":"object","title":"Services","description":"Individual service provisioning results"},"env_vars":{"additionalProperties":{"type":"string"},"type":"object","title":"Env Vars","description":"Environment variables to add to tenant .env","example":{"ANTHROPIC_API_KEY":"sk-ant-xxx...","FILES_API_KEY":"cbfiles_xxx...","FILES_BASE_URL":"https://files.campaignbrain.dev/api/v1","FILES_TENANT_BUCKET":"tenant-slug","YASP_API_KEY":"yasp_xxx...","YASP_BASE_URL":"https://surveys.campaignbrain.dev/api/v1"}},"env_updated":{"type":"boolean","title":"Env Updated","description":"Whether .env file was regenerated with new variables","default":false,"example":true}},"type":"object","required":["success","services","env_vars"],"title":"ServiceProvisionResponse","description":"Combined response from provisioning all external services.\n\nProvisioning continues even if individual services fail.\nCheck each service's status in the `services` dict.\n\nThe `env_vars` dict contains variables to add to the tenant's .env file."},"cbtenant__ServiceProvisionResult":{"properties":{"service":{"type":"string","title":"Service","description":"Service name: cbfiles, cbsurveys, anthropic","example":"cbfiles"},"status":{"type":"string","title":"Status","description":"Provisioning status: ok, error, skipped","example":"ok"},"message":{"type":"string","title":"Message","description":"Human-readable result message","example":"Provisioned CBFiles: user=abc, bucket=tenant-slug"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data","description":"Service-specific data (API keys, bucket names, etc.)","example":{"api_key":"cbfiles_xxx...","bucket":"tenant-slug","user_id":"550e8400-e29b-41d4-a716-446655440000"}}},"type":"object","required":["service","status","message"],"title":"ServiceProvisionResult","description":"Result of provisioning a single external service.\n\nEach service (CBFiles, cbsurveys, Anthropic) returns its own result.\nCheck `status` to determine if provisioning succeeded.\n\n**Status values:**\n- `ok`: Service provisioned successfully\n- `error`: Provisioning failed (check message)\n- `skipped`: Service not configured (admin key missing)"},"cbtenant__ServiceValidationResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant ID being validated","example":"550e8400-e29b-41d4-a716-446655440000"},"all_ok":{"type":"boolean","title":"All Ok","description":"True if all configured services validated successfully","example":true},"services":{"additionalProperties":{"$ref":"#/components/schemas/cbtenant__ServiceValidationResult"},"type":"object","title":"Services","description":"Individual service validation results"}},"type":"object","required":["tenant_id","all_ok","services"],"title":"ServiceValidationResponse","description":"Combined response from validating all external services.\n\nUse this to verify a tenant's services are properly configured\nand accessible after deployment or configuration changes."},"cbtenant__ServiceValidationResult":{"properties":{"service":{"type":"string","title":"Service","description":"Service name: cbfiles, cbsurveys, anthropic, database","example":"cbfiles"},"status":{"type":"string","title":"Status","description":"Validation status: ok, error, skipped","example":"ok"},"message":{"type":"string","title":"Message","description":"Validation result message","example":"Bucket 'tenant-slug' accessible"}},"type":"object","required":["service","status","message"],"title":"ServiceValidationResult","description":"Result of validating a single external service connection.\n\nValidates that the service is accessible with configured credentials.\n\n**Status values:**\n- `ok`: Service accessible and credentials valid\n- `error`: Service not accessible or credentials invalid\n- `skipped`: Service not configured"},"cbtenant__SettingResponse":{"properties":{"key":{"type":"string","title":"Key","description":"Setting identifier","example":"anthropic_api_key"},"value":{"type":"string","title":"Value","description":"Setting value (masked if is_secret=true)","example":"***hidden***"},"value_type":{"type":"string","title":"Value Type","description":"Data type: string, integer, boolean, json","example":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Human-readable description of this setting","example":"API key for Claude Vision theme analysis"},"is_secret":{"type":"boolean","title":"Is Secret","description":"Whether value should be masked in responses","example":true},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["key","value","value_type","is_secret","updated_at"],"title":"SettingResponse","description":"Global application setting.\n\nKey-value storage for system-wide configuration.\nSecret values are masked in responses (shown as ***hidden***)."},"cbtenant__SettingUpdate":{"properties":{"value":{"type":"string","title":"Value","description":"New value for the setting","example":"sk-ant-..."}},"type":"object","required":["value"],"title":"SettingUpdate","description":"Update a setting value.\n\nCreates the setting if it doesn't exist.\nFor secrets, the full value is stored but masked in responses."},"cbtenant__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbtenant__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbtenant__TenantAppCreate":{"properties":{"slug":{"type":"string","title":"Slug","description":"URL-safe identifier, unique per tenant"},"label":{"type":"string","title":"Label","description":"Display label, e.g. 'Invoice Recon'"},"kind":{"type":"string","title":"Kind","description":"One of ['bundle', 'external', 'legacy', 'loom', 'standalone']"},"target_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Url","description":"Absolute URL, or path for kind=loom"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"dashboard_visible":{"type":"boolean","title":"Dashboard Visible","default":true},"open_in":{"type":"string","title":"Open In","description":"inline | window | tab","default":"inline"},"display_order":{"type":"integer","title":"Display Order","default":100},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["slug","label","kind"],"title":"TenantAppCreate","description":"Create a tenant_app row."},"cbtenant__TenantAppResponse":{"properties":{"id":{"type":"string","title":"Id"},"tenant_slug":{"type":"string","title":"Tenant Slug"},"slug":{"type":"string","title":"Slug"},"label":{"type":"string","title":"Label"},"kind":{"type":"string","title":"Kind"},"target_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Url"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"dashboard_visible":{"type":"boolean","title":"Dashboard Visible"},"open_in":{"type":"string","title":"Open In"},"display_order":{"type":"integer","title":"Display Order"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","tenant_slug","slug","label","kind","target_url","icon","color","dashboard_visible","open_in","display_order","notes","created_at","updated_at"],"title":"TenantAppResponse"},"cbtenant__TenantAppUpdate":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"},"target_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Url"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"dashboard_visible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Dashboard Visible"},"open_in":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Open In"},"display_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Display Order"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"TenantAppUpdate","description":"Partial update for tenant_app."},"cbtenant__TenantConfigResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Configuration record ID (UUID)"},"tenant_id":{"type":"string","title":"Tenant Id","description":"Associated tenant ID"},"app_name":{"type":"string","title":"App Name","description":"Full application name displayed in UI","example":"MI-20 Clevenger Campaign"},"app_name_short":{"type":"string","title":"App Name Short","description":"Short name for headers/menus","example":"Clevenger"},"project_name":{"type":"string","title":"Project Name","description":"Internal project identifier","example":"mi20-clevenger"},"admin_email":{"type":"string","title":"Admin Email","description":"Administrator email for notifications","example":"admin@mi20.campaignbrain.dev"},"frontend_url":{"type":"string","title":"Frontend Url","description":"Public frontend URL (used for CORS and links)","example":"https://mi20.campaignbrain.dev"},"backend_api_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Backend Api Url","description":"Backend API URL (campaign tenants only)","example":"https://tenant.campaignbrain.dev/api"},"ws_base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ws Base Url","description":"WebSocket URL for real-time features (campaign tenants only)","example":"wss://mi20.campaignbrain.dev"},"db_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Db Path","description":"SQLite database file path (campaign tenants only)","example":"db/pocket.db"},"ollama_embed_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ollama Embed Model","description":"Ollama model for text embeddings (campaign tenants only)","example":"nomic-embed-text:latest"},"ollama_llm_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ollama Llm Model","description":"Ollama model for LLM features (campaign tenants only)","example":"llama3.2:latest"},"ollama_base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ollama Base Url","description":"Ollama API endpoint (campaign tenants only)","example":"http://10.8.0.2:11434"},"custom_env":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Env","description":"Custom environment variables (JSON string)","example":"{\"FEATURE_FLAG\": \"true\"}"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Configuration creation timestamp"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last update timestamp"}},"type":"object","required":["id","tenant_id","app_name","app_name_short","project_name","admin_email","frontend_url","created_at","updated_at"],"title":"TenantConfigResponse","description":"Tenant configuration settings.\n\nControls application behavior, branding, and service connections.\nAfter updating, regenerate .env file and restart services."},"cbtenant__TenantConfigUpdate":{"properties":{"app_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Name","description":"Full application name"},"app_name_short":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Name Short","description":"Short name for headers/menus"},"admin_email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Admin Email","description":"Administrator email"},"ollama_embed_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ollama Embed Model","description":"Ollama embedding model name"},"ollama_llm_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ollama Llm Model","description":"Ollama LLM model name"},"ollama_base_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ollama Base Url","description":"Ollama API endpoint URL"},"custom_env":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Custom Env","description":"Custom environment variables as key-value pairs","example":{"DEBUG":"false","FEATURE_FLAG":"true"}}},"type":"object","title":"TenantConfigUpdate","description":"Update tenant configuration.\n\nOnly provided fields will be updated. After updating:\n1. Call POST /{tenant_id}/config/regenerate-env\n2. Restart tenant services for changes to take effect"},"cbtenant__TenantCreate":{"properties":{"app_type":{"type":"string","pattern":"^(campaign|website|custom|radio)$","title":"App Type","description":"Application type: 'campaign' (Python cbapp), 'website' (Next.js site), 'custom' (user-managed app), or 'radio' (cbradio + cbradio-ui pair)","default":"campaign","example":"campaign"},"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Legal/system name (e.g. 'Ed Gallrein for State Rep'). Stored verbatim; rarely shown directly to users.","example":"MI-20 Clevenger Campaign"},"display_name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Display Name","description":"Friendly label shown in the tenant switcher and UI chrome. Falls back to `name` when null.","example":"MI-20"},"category":{"type":"string","maxLength":50,"minLength":1,"title":"Category","description":"Coarse business bucket for grouping in the tenant switcher (Platform, Campaigns, Sites).","default":"Sites","example":"Campaigns"},"slug":{"type":"string","maxLength":100,"minLength":1,"pattern":"^[a-z0-9-]+$","title":"Slug","description":"URL-safe identifier (lowercase, alphanumeric, hyphens only)","example":"mi20-clevenger"},"domain":{"type":"string","minLength":1,"title":"Domain","description":"Frontend domain for the tenant","example":"mi20.campaignbrain.dev"},"api_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Domain","description":"API domain for the tenant (required for campaign and radio, optional for custom, not needed for website)","example":"mi20-api.campaignbrain.dev"},"frontend_port":{"anyOf":[{"type":"integer","maximum":32399.0,"minimum":32300.0},{"type":"null"}],"title":"Frontend Port","description":"Frontend port (auto-allocated from 32300-32399 if not provided)","example":32300},"backend_port":{"anyOf":[{"type":"integer","maximum":32399.0,"minimum":32300.0},{"type":"null"}],"title":"Backend Port","description":"Backend API port (auto-allocated for campaign and radio, optional for custom, not used for website)","example":32301},"directory":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Directory","description":"Filesystem path for tenant (defaults to /home/bisenbek/projects/nominate/{slug})","example":"/home/bisenbek/projects/nominate/mi20-clevenger"},"github_repo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Github Repo","description":"GitHub repository URL for code source","example":"https://github.com/org/cbapp"},"github_branch":{"type":"string","title":"Github Branch","description":"Git branch to deploy from","default":"main","example":"main"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Optional notes about this tenant","example":"Primary campaign for MI District 20"}},"type":"object","required":["name","slug","domain"],"title":"TenantCreate","description":"Create a new tenant (CampaignBrain instance).\n\n**Required fields:** name, slug, domain, api_domain\n\n**Auto-allocated if not provided:** frontend_port, backend_port, directory\n\n**Naming conventions:**\n- `slug`: lowercase alphanumeric with hyphens (e.g., `mi20-clevenger`)\n- `domain`: Frontend URL (e.g., `mi20.campaignbrain.dev`)\n- `api_domain`: API URL (e.g., `mi20-api.campaignbrain.dev`)"},"cbtenant__TenantDetailResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique tenant identifier (UUID)","example":"550e8400-e29b-41d4-a716-446655440000"},"app_type":{"type":"string","title":"App Type","description":"Application type: 'campaign', 'website', 'custom', or 'radio'","example":"campaign"},"name":{"type":"string","title":"Name","description":"Legal/system name (e.g. 'MI-20 Clevenger Campaign'). Use display_name for UI labels.","example":"MI-20 Clevenger Campaign"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name","description":"Friendly label shown in the tenant switcher and UI chrome. Falls back to `name` when null.","example":"MI-20"},"category":{"type":"string","title":"Category","description":"Coarse business bucket for grouping in the tenant switcher.","default":"Sites","example":"Campaigns"},"slug":{"type":"string","title":"Slug","description":"URL-safe identifier","example":"mi20-clevenger"},"domain":{"type":"string","title":"Domain","description":"Frontend domain","example":"mi20.campaignbrain.dev"},"api_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Domain","description":"API domain (campaign tenants only)","example":"mi20-api.campaignbrain.dev"},"status":{"type":"string","title":"Status","description":"Tenant status: pending, active, stopped, error","example":"active"},"frontend_port":{"type":"integer","title":"Frontend Port","description":"Port for frontend service","example":32300},"backend_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Backend Port","description":"Port for backend API service (campaign tenants only)","example":32301},"directory":{"type":"string","title":"Directory","description":"Filesystem path for tenant files","example":"/home/bisenbek/projects/nominate/mi20-clevenger"},"health_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Health Status","description":"Last health check result: healthy, unhealthy, unknown","example":"healthy"},"last_health_check":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Health Check","description":"Timestamp of last health check"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Tenant creation timestamp (UTC)"},"deployed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deployed At","description":"Last deployment timestamp (UTC)"},"github_repo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Github Repo","description":"GitHub repository URL"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Optional notes about this tenant"},"config":{"anyOf":[{"$ref":"#/components/schemas/cbtenant__TenantConfigResponse"},{"type":"null"}],"description":"Current tenant configuration"},"theme":{"anyOf":[{"$ref":"#/components/schemas/cbtenant__TenantThemeResponse"},{"type":"null"}],"description":"Active theme settings"},"latest_deployment":{"anyOf":[{"$ref":"#/components/schemas/cbtenant__DeploymentResponse"},{"type":"null"}],"description":"Most recent deployment record"}},"type":"object","required":["id","app_type","name","slug","domain","status","frontend_port","directory","created_at"],"title":"TenantDetailResponse","description":"Detailed tenant response including related configuration, theme, and deployment.\n\nExtended version of TenantResponse with nested objects for:\n- Current configuration settings\n- Active theme colors\n- Latest deployment status"},"cbtenant__TenantDomainCreate":{"properties":{"domain":{"type":"string","title":"Domain","description":"FQDN, e.g. 'app.ruralamfm.com'"},"kind":{"type":"string","title":"Kind","description":"One of ['external_site', 'legacy_campaign', 'loom_desktop', 'standalone_app', 'system_canonical']"},"is_primary":{"type":"boolean","title":"Is Primary","description":"Exactly one primary per tenant","default":false},"app_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Id","description":"Optional link to tenant_apps.id"},"cert_provider":{"type":"string","title":"Cert Provider","description":"le | manual | cloudflare","default":"le"},"cert_status":{"type":"string","title":"Cert Status","description":"pending | active | expired","default":"pending"},"dns_owned_by":{"type":"string","title":"Dns Owned By","description":"us | customer","default":"us"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["domain","kind"],"title":"TenantDomainCreate","description":"Create a tenant_domain row."},"cbtenant__TenantDomainResponse":{"properties":{"id":{"type":"string","title":"Id"},"tenant_slug":{"type":"string","title":"Tenant Slug"},"domain":{"type":"string","title":"Domain"},"kind":{"type":"string","title":"Kind"},"is_primary":{"type":"boolean","title":"Is Primary"},"app_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Id"},"cert_provider":{"type":"string","title":"Cert Provider"},"cert_status":{"type":"string","title":"Cert Status"},"dns_owned_by":{"type":"string","title":"Dns Owned By"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","tenant_slug","domain","kind","is_primary","app_id","cert_provider","cert_status","dns_owned_by","notes","created_at","updated_at"],"title":"TenantDomainResponse"},"cbtenant__TenantDomainUpdate":{"properties":{"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"},"is_primary":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Primary"},"app_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"App Id"},"cert_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cert Provider"},"cert_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cert Status"},"dns_owned_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dns Owned By"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"TenantDomainUpdate","description":"Partial update for tenant_domain."},"cbtenant__TenantListResponse":{"properties":{"tenants":{"items":{"$ref":"#/components/schemas/cbtenant__TenantResponse"},"type":"array","title":"Tenants","description":"List of tenant records"},"total":{"type":"integer","title":"Total","description":"Total number of tenants matching the query","example":5},"limit":{"type":"integer","title":"Limit","description":"Maximum records returned per page","example":50},"offset":{"type":"integer","title":"Offset","description":"Number of records skipped","example":0}},"type":"object","required":["tenants","total","limit","offset"],"title":"TenantListResponse","description":"Paginated list of tenants.\n\nUse `limit` and `offset` for pagination. Maximum limit is 100."},"cbtenant__TenantResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique tenant identifier (UUID)","example":"550e8400-e29b-41d4-a716-446655440000"},"app_type":{"type":"string","title":"App Type","description":"Application type: 'campaign', 'website', 'custom', or 'radio'","example":"campaign"},"name":{"type":"string","title":"Name","description":"Legal/system name (e.g. 'MI-20 Clevenger Campaign'). Use display_name for UI labels.","example":"MI-20 Clevenger Campaign"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name","description":"Friendly label shown in the tenant switcher and UI chrome. Falls back to `name` when null.","example":"MI-20"},"category":{"type":"string","title":"Category","description":"Coarse business bucket for grouping in the tenant switcher.","default":"Sites","example":"Campaigns"},"slug":{"type":"string","title":"Slug","description":"URL-safe identifier","example":"mi20-clevenger"},"domain":{"type":"string","title":"Domain","description":"Frontend domain","example":"mi20.campaignbrain.dev"},"api_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Domain","description":"API domain (campaign tenants only)","example":"mi20-api.campaignbrain.dev"},"status":{"type":"string","title":"Status","description":"Tenant status: pending, active, stopped, error","example":"active"},"frontend_port":{"type":"integer","title":"Frontend Port","description":"Port for frontend service","example":32300},"backend_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Backend Port","description":"Port for backend API service (campaign tenants only)","example":32301},"directory":{"type":"string","title":"Directory","description":"Filesystem path for tenant files","example":"/home/bisenbek/projects/nominate/mi20-clevenger"},"health_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Health Status","description":"Last health check result: healthy, unhealthy, unknown","example":"healthy"},"last_health_check":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Health Check","description":"Timestamp of last health check"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Tenant creation timestamp (UTC)"},"deployed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deployed At","description":"Last deployment timestamp (UTC)"},"github_repo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Github Repo","description":"GitHub repository URL"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Optional notes about this tenant"}},"type":"object","required":["id","app_type","name","slug","domain","status","frontend_port","directory","created_at"],"title":"TenantResponse","description":"Tenant information returned from API endpoints.\n\nThis is the standard tenant response format used for list and detail views."},"cbtenant__TenantThemeCreate":{"properties":{"mode":{"type":"string","pattern":"^(light|dark)$","title":"Mode","description":"Color scheme this palette applies to","default":"dark"},"primary_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Primary Color"},"secondary_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Secondary Color"},"accent_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Accent Color"},"text_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Text Color"},"sidebar_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Sidebar Color"},"success_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Success Color"},"warning_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Warning Color"},"error_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Error Color"},"font_family":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Font Family"},"heading_font":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Heading Font"},"body_font":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Font"},"logo_full":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Full"},"logo_cropped":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Cropped"},"logo_icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Icon"},"logo_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Name"},"logo_mobile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Mobile"},"favicon_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Favicon Url"},"tagline":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Tagline"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"type":"object","required":["primary_color","secondary_color","accent_color"],"title":"TenantThemeCreate","description":"Create a new theme row for a tenant.\n\nprimary/secondary/accent are required; everything else is optional and\neither falls back to a neutral default or is left null (the public\n/brand endpoint handles the null case)."},"cbtenant__TenantThemeResponse":{"properties":{"id":{"type":"string","title":"Id"},"tenant_id":{"type":"string","title":"Tenant Id"},"mode":{"type":"string","title":"Mode","default":"dark"},"primary_color":{"type":"string","title":"Primary Color"},"secondary_color":{"type":"string","title":"Secondary Color"},"accent_color":{"type":"string","title":"Accent Color"},"text_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text Color"},"sidebar_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sidebar Color"},"success_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Success Color"},"warning_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning Color"},"error_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Color"},"font_family":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Font Family"},"heading_font":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Heading Font"},"body_font":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Font"},"logo_full":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Full"},"logo_cropped":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Cropped"},"logo_icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Icon"},"logo_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Name"},"logo_mobile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Mobile"},"favicon_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Favicon Url"},"tagline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tagline"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"ai_generated":{"type":"boolean","title":"Ai Generated","default":false},"source_website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Website"},"is_active":{"type":"boolean","title":"Is Active","default":true},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","tenant_id","primary_color","secondary_color","accent_color","created_at"],"title":"TenantThemeResponse","description":"Active theme settings for a tenant.\n\nColors are stored as hex codes (#RRGGBB format).\nThemes can be AI-generated from website analysis or manually set."},"cbtenant__TenantThemeUpdate":{"properties":{"mode":{"anyOf":[{"type":"string","pattern":"^(light|dark)$"},{"type":"null"}],"title":"Mode","description":"Color scheme this palette applies to"},"primary_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Primary Color"},"secondary_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Secondary Color"},"accent_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Accent Color"},"text_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Text Color"},"sidebar_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Sidebar Color"},"success_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Success Color"},"warning_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Warning Color"},"error_color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}],"title":"Error Color"},"font_family":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Font Family"},"heading_font":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Heading Font"},"body_font":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body Font"},"logo_full":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Full"},"logo_cropped":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Cropped"},"logo_icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Icon"},"logo_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Name"},"logo_mobile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Mobile"},"favicon_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Favicon Url"},"tagline":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Tagline"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"TenantThemeUpdate","description":"Update any theme field. Only provided fields are applied."},"cbtenant__TenantUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name","description":"Legal/system name. Rarely changes after creation."},"display_name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Display Name","description":"Friendly label shown in the tenant switcher and UI chrome."},"category":{"anyOf":[{"type":"string","maxLength":50,"minLength":1},{"type":"null"}],"title":"Category","description":"Coarse business bucket (Platform, Campaigns, Sites)."},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain","description":"Frontend domain (requires DNS/SSL/NGINX updates)"},"api_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Domain","description":"API domain (requires DNS/SSL/NGINX updates)"},"github_repo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Github Repo","description":"GitHub repository URL"},"github_branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Github Branch","description":"Git branch to deploy from"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Optional notes about this tenant"},"backend_port":{"anyOf":[{"type":"integer","maximum":65535.0,"minimum":1024.0},{"type":"null"}],"title":"Backend Port","description":"Backend/API port for this tenant's service instance. Normally auto-allocated at create time; settable here to record/correct the port of a hand-provisioned instance (e.g. an API-only cbapp CRM attached to an existing site tenant). The cbloom Campaign App keys off this to route /app/campaign to the instance — see cbloom#218."},"frontend_port":{"anyOf":[{"type":"integer","maximum":65535.0,"minimum":1024.0},{"type":"null"}],"title":"Frontend Port","description":"Frontend port for this tenant's service instance. Rarely changed after creation."}},"type":"object","title":"TenantUpdate","description":"Update tenant metadata.\n\nOnly provided fields will be updated. All fields are optional.\nNote: slug cannot be changed after creation."},"cbtenant__ThemeAnalysisRequest":{"properties":{"url":{"type":"string","title":"Url","description":"Website URL to analyze"}},"type":"object","required":["url"],"title":"ThemeAnalysisRequest"},"cbtenant__ThemeAnalysisStartResponse":{"properties":{"analysis_id":{"type":"string","title":"Analysis Id"},"status":{"type":"string","title":"Status","default":"pending"},"message":{"type":"string","title":"Message","default":"Analysis started"}},"type":"object","required":["analysis_id"],"title":"ThemeAnalysisStartResponse"},"cbtenant__ThemeAnalysisStatusResponse":{"properties":{"analysis_id":{"type":"string","title":"Analysis Id"},"status":{"type":"string","title":"Status"},"progress":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Progress","default":0},"message":{"type":"string","title":"Message","default":""},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"screenshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot Url"},"suggestions":{"items":{"$ref":"#/components/schemas/cbtenant__ThemeSuggestionData"},"type":"array","title":"Suggestions","default":[]},"detected_fonts":{"items":{"type":"string"},"type":"array","title":"Detected Fonts","default":[]},"style_notes":{"type":"string","title":"Style Notes","default":""},"overall_style":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Overall Style"},"dual_mode":{"type":"boolean","title":"Dual Mode","default":false},"analyzed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Analyzed At"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["analysis_id","status"],"title":"ThemeAnalysisStatusResponse"},"cbtenant__ThemePalette":{"properties":{"primary_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Primary Color"},"secondary_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Secondary Color"},"accent_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Accent Color"},"text_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Text Color"},"background_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Background Color"}},"type":"object","required":["primary_color","secondary_color","accent_color","text_color","background_color"],"title":"ThemePalette","description":"A complete palette for one mode (light or dark) — cbloom#103."},"cbtenant__ThemeSuggestionData":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"primary_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Primary Color"},"secondary_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Secondary Color"},"accent_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Accent Color"},"text_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Text Color","default":"#374151"},"background_color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","title":"Background Color","default":"#ffffff"},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence","default":0.8},"reasoning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reasoning"},"light_palette":{"anyOf":[{"$ref":"#/components/schemas/cbtenant__ThemePalette"},{"type":"null"}]},"dark_palette":{"anyOf":[{"$ref":"#/components/schemas/cbtenant__ThemePalette"},{"type":"null"}]}},"type":"object","required":["name","primary_color","secondary_color","accent_color"],"title":"ThemeSuggestionData"},"cbtenant__Token":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"JWT access token for API authentication","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},"token_type":{"type":"string","title":"Token Type","description":"Token type (always 'bearer')","default":"bearer","example":"bearer"}},"type":"object","required":["access_token"],"title":"Token","description":"JWT token response returned after successful authentication.\n\nInclude this token in subsequent requests:\n```\nAuthorization: Bearer <access_token>\n```\n\nToken expires after 30 minutes. Re-authenticate to obtain a new token."},"cbtenant__UpgradeRequest":{"properties":{"target_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Version"},"backup_db":{"type":"boolean","title":"Backup Db","default":true},"run_migrations":{"type":"boolean","title":"Run Migrations","default":true}},"type":"object","title":"UpgradeRequest","description":"Upgrade request parameters."},"cbtenant__UpgradeResponse":{"properties":{"status":{"type":"string","title":"Status"},"previous_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previous Version"},"new_version":{"type":"string","title":"New Version"},"commit":{"type":"string","title":"Commit"},"migrations_run":{"items":{"type":"string"},"type":"array","title":"Migrations Run"},"hooks_executed":{"items":{"type":"string"},"type":"array","title":"Hooks Executed"},"backup_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Backup Path"},"details":{"items":{"type":"string"},"type":"array","title":"Details"}},"type":"object","required":["status","previous_version","new_version","commit","migrations_run","hooks_executed","backup_path","details"],"title":"UpgradeResponse","description":"Upgrade response."},"cbtenant__UserResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Unique user identifier (UUID)","example":"550e8400-e29b-41d4-a716-446655440000"},"username":{"type":"string","title":"Username","description":"Username for login","example":"admin"},"email":{"type":"string","title":"Email","description":"User email address","example":"admin@campaignbrain.dev"},"role":{"type":"string","title":"Role","description":"User role: 'admin' or 'user'","example":"admin"},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the user account is active","example":true},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Account creation timestamp (UTC)"},"last_login":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login","description":"Last successful login timestamp (UTC)"}},"type":"object","required":["id","username","email","role","is_active","created_at"],"title":"UserResponse","description":"User account information.\n\nReturned from GET /api/auth/me to identify the authenticated user."},"cbtenant__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbtenant__VersionResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"slug":{"type":"string","title":"Slug"},"deployed_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deployed Version"},"deployed_commit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deployed Commit"},"latest_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Latest Version"},"latest_commit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Latest Commit"},"upgrade_available":{"type":"boolean","title":"Upgrade Available"}},"type":"object","required":["tenant_id","slug","deployed_version","deployed_commit","latest_version","latest_commit","upgrade_available"],"title":"VersionResponse","description":"Version check response."},"cbtenant__WebhookResponse":{"properties":{"status":{"type":"string","title":"Status"},"message":{"type":"string","title":"Message"},"event":{"type":"string","title":"Event"},"tenants_updated":{"type":"integer","title":"Tenants Updated","default":0},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details"}},"type":"object","required":["status","message","event"],"title":"WebhookResponse","description":"Webhook response."},"cbtiles__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbtiles__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbtiles__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbtiles__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbtiles__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbtiles__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbtiles__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbtiles__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbtiles__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbtiles__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbtiles__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbtor__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbtor__FetchRequest":{"properties":{"url":{"type":"string","title":"Url","description":"URL to fetch. Supports http://, https://, and .onion addresses.","examples":["https://check.torproject.org/api/ip","https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/"]},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode","description":"Load balancing mode. Options: round_robin (default), sticky, random, least_connections.","examples":["round_robin","sticky"]},"sticky_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sticky Key","description":"Session affinity key for sticky mode. Same key always routes to same worker. Use for maintaining consistent exit IP across requests.","examples":["user-session-abc123","scrape-job-42"]},"timeout":{"type":"number","maximum":300.0,"minimum":1.0,"title":"Timeout","description":"Request timeout in seconds. .onion sites typically need 10-30s. Max 300s.","default":30.0},"follow_redirects":{"type":"boolean","title":"Follow Redirects","description":"Follow HTTP 3xx redirects automatically.","default":true},"headers":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Headers","description":"Custom HTTP headers to send with the request.","examples":[{"Accept":"text/html","User-Agent":"Mozilla/5.0"}]},"method":{"type":"string","title":"Method","description":"HTTP method. Supports GET, POST, PUT, DELETE, HEAD, OPTIONS.","default":"GET"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Request body for POST/PUT requests. Send as string, will be passed as-is."}},"type":"object","required":["url"],"title":"FetchRequest","description":"Request to fetch a URL anonymously through the Tor network.\n\nThe URL can be either a clearnet address (http/https) or a .onion hidden service.\nClearnet requests route through Tor TransPort, while .onion requests use SOCKS5.","examples":[{"mode":"round_robin","timeout":30.0,"url":"https://check.torproject.org/api/ip"},{"mode":"sticky","sticky_key":"search-session-1","timeout":60.0,"url":"https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/"}]},"cbtor__FetchResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the fetch completed successfully. False if timeout or connection error."},"worker":{"type":"string","title":"Worker","description":"Worker ID that handled this request (e.g., 'worker-01')."},"is_onion":{"type":"boolean","title":"Is Onion","description":"Whether the URL was a .onion hidden service address."},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code","description":"HTTP status code from the target server. Null if connection failed."},"headers":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Headers","description":"Response headers from the target server."},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Response body content. May be truncated for large responses."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if success=false. Includes timeout, connection, and HTTP errors."},"timing_ms":{"type":"number","title":"Timing Ms","description":"Total request duration in milliseconds, including Tor circuit setup time."}},"type":"object","required":["success","worker","is_onion","timing_ms"],"title":"FetchResponse","description":"Response from an anonymous fetch operation.\n\nContains the fetched content, metadata about the request, and timing information.","examples":[{"body":"{\"IsTor\":true,\"IP\":\"185.220.101.20\"}","headers":{"content-type":"application/json"},"is_onion":false,"status_code":200,"success":true,"timing_ms":1234.5,"worker":"worker-01"},{"error":"Connection timeout after 30s","is_onion":true,"success":false,"timing_ms":30000.0,"worker":"worker-02"}]},"cbtor__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbtor__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbtor__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbtor__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbtor__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbtor__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbtor__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbtor__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbtor__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbtor__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbtor__WorkerAction":{"properties":{"worker_id":{"type":"string","title":"Worker Id","description":"Worker identifier. Format: 'worker-01' through 'worker-04'.","examples":["worker-01","worker-02"]}},"type":"object","required":["worker_id"],"title":"WorkerAction","description":"Specify a worker for drain or enable operations."},"cbtor__api__routers__fetch__HealthResponse":{"properties":{"healthy_workers":{"type":"integer","title":"Healthy Workers","description":"Number of workers currently available to receive traffic (HEALTHY or SUSPECT state)."},"total_workers":{"type":"integer","title":"Total Workers","description":"Total number of workers in the pool (always 4)."},"mode":{"type":"string","title":"Mode","description":"Current default load balancing mode for the pool."},"workers":{"additionalProperties":true,"type":"object","title":"Workers","description":"Detailed status for each worker including state, IP, connections, and success rate."}},"type":"object","required":["healthy_workers","total_workers","mode","workers"],"title":"HealthResponse","description":"Worker pool health status summary.\n\nShows the current state of all Tor workers and the active load balancing mode.","example":{"healthy_workers":4,"mode":"round_robin","total_workers":4,"workers":{"worker-01":{"connections":2,"consecutive_failures":0,"ip":"17.0.0.134","state":"healthy","success_rate":0.98,"total_requests":150},"worker-02":{"connections":1,"consecutive_failures":0,"ip":"17.0.0.202","state":"healthy","success_rate":0.95,"total_requests":142}}}},"cbtor__sysforge__core__health__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbtor__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbtor__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbunifi__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbunifi__GuestAuthorize":{"properties":{"minutes":{"type":"integer","minimum":1.0,"title":"Minutes"},"up_kbps":{"anyOf":[{"type":"integer","maximum":100000.0,"minimum":2.0},{"type":"null"}],"title":"Up Kbps"},"down_kbps":{"anyOf":[{"type":"integer","maximum":100000.0,"minimum":2.0},{"type":"null"}],"title":"Down Kbps"},"mb_quota":{"anyOf":[{"type":"integer","maximum":1048576.0,"minimum":1.0},{"type":"null"}],"title":"Mb Quota"}},"type":"object","required":["minutes"],"title":"GuestAuthorize","description":"Portal-facing authorization request; clamped, mapped inside the client."},"cbunifi__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbunifi__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbunifi__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbunifi__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbunifi__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbunifi__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbunifi__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbunifi__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbunifi__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbunifi__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbunifi__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbunifi__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbunifi__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbunifi__VoucherCreate":{"properties":{"note":{"type":"string","maxLength":120,"minLength":1,"title":"Note"},"count":{"type":"integer","minimum":1.0,"title":"Count","default":1},"time_limit_minutes":{"type":"integer","minimum":1.0,"title":"Time Limit Minutes"},"data_usage_limit_mbytes":{"anyOf":[{"type":"integer","maximum":1048576.0,"minimum":1.0},{"type":"null"}],"title":"Data Usage Limit Mbytes"},"rx_rate_limit_kbps":{"anyOf":[{"type":"integer","maximum":100000.0,"minimum":2.0},{"type":"null"}],"title":"Rx Rate Limit Kbps"},"tx_rate_limit_kbps":{"anyOf":[{"type":"integer","maximum":100000.0,"minimum":2.0},{"type":"null"}],"title":"Tx Rate Limit Kbps"},"authorized_guest_limit":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Authorized Guest Limit"}},"type":"object","required":["note","time_limit_minutes"],"title":"VoucherCreate","description":"Desk-facing voucher batch request; clamped, then mapped to UniFi's shape."},"cbuserguide__App":{"properties":{"id":{"type":"integer","title":"Id","description":"Unique app identifier"},"name":{"type":"string","title":"Name","description":"App name/identifier","examples":["testsite","cbapp"]},"base_url":{"type":"string","title":"Base Url","description":"Root URL of the application","examples":["https://testsite.campaignbrain.dev"]},"api_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Url","description":"API endpoint URL (null if using default)"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When app was registered"}},"type":"object","required":["id","name","base_url","api_url","created_at"],"title":"App","description":"A registered application.\n\nApps are Campaign Brain web applications that have been registered\nfor automated documentation generation."},"cbuserguide__AppCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Unique app identifier (e.g., 'cbapp', 'testsite')","examples":["testsite","cbapp","ky04"]},"base_url":{"type":"string","title":"Base Url","description":"Root URL of the web application","examples":["https://testsite.campaignbrain.dev","https://ky04.campaignbrain.dev"]},"api_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Url","description":"API endpoint URL; defaults to {base_url}/api","examples":["https://testsite.campaignbrain.dev/api"]},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"Login username for crawling","examples":["admin"]},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password","description":"Login password for crawling (stored securely)"}},"type":"object","required":["name","base_url"],"title":"AppCreate","description":"Request to register an app for documentation.\n\nApps represent Campaign Brain web applications to be documented.\nCredentials are stored securely and used for authenticated crawling.","example":{"base_url":"https://testsite.campaignbrain.dev","name":"testsite","password":"admin123","username":"admin"}},"cbuserguide__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbuserguide__GenerationOptions":{"properties":{"auto":{"type":"boolean","title":"Auto","description":"Enable automatic generation during crawling","default":true},"manual":{"type":"boolean","title":"Manual","description":"Allow manual artifact upload","default":false}},"type":"object","title":"GenerationOptions","description":"Generation options for an artifact type.\n\nControls whether artifacts are generated automatically during crawling\nor require manual upload."},"cbuserguide__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbuserguide__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbuserguide__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbuserguide__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbuserguide__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbuserguide__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbuserguide__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbuserguide__Module":{"properties":{"id":{"type":"integer","title":"Id","description":"Unique module identifier"},"version_id":{"type":"integer","title":"Version Id","description":"Parent version ID"},"name":{"type":"string","title":"Name","description":"Human-readable module name","examples":["admin-users","settings"]},"paths":{"items":{"type":"string"},"type":"array","title":"Paths","description":"URL paths included in this module","examples":[["/users","/users/new"]]},"state":{"$ref":"#/components/schemas/cbuserguide__ModuleState","description":"Current workflow state","default":"init"},"generation_options":{"$ref":"#/components/schemas/cbuserguide__ModuleGenerationConfig","description":"Artifact generation configuration"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When module was created"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When module was last modified"}},"type":"object","required":["id","version_id","name","paths","generation_options","created_at","updated_at"],"title":"Module","description":"A documentation module (section).\n\nModules organize documentation into logical sections. Each module has a\nlifecycle state that progresses through generation → validation → publication."},"cbuserguide__ModuleCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Human-readable module name","examples":["admin-users","settings","voter-search"]},"paths":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Paths","description":"URL paths to crawl for this module (relative to app base_url)","examples":[["/users","/users/new"],["/settings","/settings/tags"]]},"generation_options":{"$ref":"#/components/schemas/cbuserguide__ModuleGenerationConfig","description":"Options for artifact generation"}},"type":"object","required":["name","paths"],"title":"ModuleCreate","description":"Request to create a documentation module.\n\nModules represent logical sections of documentation (e.g., \"Admin Users\",\n\"Settings\"). Each module defines a list of URL paths to crawl and capture.","example":{"name":"admin-users","paths":["/users","/users/new","/users/{id}/edit"]}},"cbuserguide__ModuleGenerationConfig":{"properties":{"screenshots":{"$ref":"#/components/schemas/cbuserguide__GenerationOptions","description":"Screenshot generation options"},"markdown":{"$ref":"#/components/schemas/cbuserguide__GenerationOptions","description":"Markdown documentation generation options"}},"type":"object","title":"ModuleGenerationConfig","description":"Generation configuration for a module.\n\nDefines how each artifact type (screenshots, markdown) should be generated."},"cbuserguide__ModuleState":{"type":"string","enum":["init","generating","validating","published"],"title":"ModuleState","description":"Module lifecycle states.\n\nStates progress through the documentation generation workflow:\n\n- `init`: Ready to generate, no artifacts yet\n- `generating`: Crawler is running (async background task)\n- `validating`: Screenshots captured, awaiting human review\n- `published`: Approved and visible in user guide"},"cbuserguide__ModuleUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Name","description":"Updated module name"},"paths":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Paths","description":"Updated list of paths to crawl"},"generation_options":{"anyOf":[{"$ref":"#/components/schemas/cbuserguide__ModuleGenerationConfig"},{"type":"null"}],"description":"Updated generation options"}},"type":"object","title":"ModuleUpdate","description":"Request to update a module.\n\nAll fields are optional; only provided fields are updated.\nNote: Changing paths does not automatically regenerate artifacts."},"cbuserguide__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbuserguide__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbuserguide__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbuserguide__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbuserguide__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbuserguide__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbuserguide__Version":{"properties":{"id":{"type":"integer","title":"Id","description":"Unique version identifier"},"app_id":{"type":"integer","title":"App Id","description":"Parent app ID"},"version":{"type":"string","title":"Version","description":"Semantic version string","examples":["1.0.0","2.1.0"]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether this is the active (published) version","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When version was created"}},"type":"object","required":["id","app_id","version","created_at"],"title":"Version","description":"A documentation version for an app.\n\nEach app can have multiple versions. Only one version per app is active\n(published) at a time."},"cbuserguide__VersionCreate":{"properties":{"version":{"type":"string","maxLength":50,"minLength":1,"title":"Version","description":"Semantic version string","examples":["1.0.0","2.1.0","1.0.0-beta"]}},"type":"object","required":["version"],"title":"VersionCreate","description":"Request to create a documentation version.\n\nVersions track documentation releases using semver format.","example":{"version":"1.0.0"}},"cbvoice__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbvoice__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbvoice__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbvoice__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbvoice__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbvoice__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbvoice__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbvoice__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbvoice__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbvoice__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbvoice__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbvoice__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbvoice__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbvoice__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbvpn__Bank":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Human-readable bank name.","examples":["California Research","Germany Primary","UK Backup"]},"tag":{"type":"string","maxLength":100,"minLength":1,"title":"Tag","description":"Normalized unique identifier (lowercase, no spaces). Use in API paths.","examples":["californiaresearch","germanyprimary","ukbackup"]},"created":{"type":"string","format":"date-time","title":"Created","description":"When the bank was created."},"workers":{"items":{"type":"integer"},"type":"array","maxItems":16,"minItems":1,"title":"Workers","description":"Worker numbers assigned to this bank (1-15).","examples":[[1,2,3,4],[5,6]]},"worker_assignments":{"items":{"$ref":"#/components/schemas/cbvpn__BankWorkerAssignment"},"type":"array","title":"Worker Assignments","description":"Detailed VPN assignments per worker. Includes profiles and exit IPs.","default":[]},"filter":{"$ref":"#/components/schemas/cbvpn__BankFilter","description":"Geographic filter determining VPN exit locations."},"port":{"type":"integer","maximum":8999.0,"minimum":8890.0,"title":"Port","description":"HAProxy port for this bank. cbvpn generates a `listen` block that binds it and balances across the bank's workers, but verify with `endpoint_status` — a reboot or hand-edit can drop it, and `POST /banks/reconcile-endpoints` puts it back.","examples":[8890,8891,8895]},"status":{"type":"string","title":"Status","description":"Bank operational status: 'active' = routing traffic, 'degraded' = workers usable but the aggregate endpoint is not listening or the geography is wrong, 'idle' = paused, 'error' = needs attention.","default":"active","examples":["active","degraded","idle","error"]},"endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endpoint","description":"Aggregate HAProxy URL for this bank: one address that balances across every worker in the bank, so a dead worker is HAProxy's problem rather than the caller's. **Null when nothing is listening on `port`** — verified by a TCP connect at read time rather than synthesised, because synthesising it advertised two dead ports for a month (cbvpn#58). If it is null, either the bank is idle or the master needs `POST /banks/reconcile-endpoints`; `worker_endpoints` works meanwhile.","examples":["http://17.0.0.1:8890",null]},"endpoint_status":{"type":"string","title":"Endpoint Status","description":"Result of the endpoint probe: 'listening' (verified open), 'unreachable' (port closed — endpoint is null), 'unverified' (probe skipped or errored).","default":"unverified","examples":["listening","unreachable","unverified"]},"endpoint_detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endpoint Detail","description":"Why the endpoint is unusable, when it is.","examples":["nothing listening on 17.0.0.1:8904"]},"worker_endpoints":{"items":{"type":"string"},"type":"array","title":"Worker Endpoints","description":"Per-worker proxy URLs for workers verified to be carrying traffic. This is the path that actually works; prefer it over `endpoint`.","examples":[["http://17.0.0.10:3129","http://17.0.0.11:3129"]]},"health":{"anyOf":[{"$ref":"#/components/schemas/cbvpn__BankHealth"},{"type":"null"}],"description":"Aggregated health metrics. Null if not yet computed."},"last_updated":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Updated","description":"Last time bank state was refreshed."}},"type":"object","required":["name","tag","created","workers","filter","port"],"title":"Bank","description":"VPN Bank - isolated pool of workers with geographic routing.\n\nBanks provide dedicated VPN capacity for specific geographic regions.\nUse banks when you need to route traffic through particular countries\nor US states.\n\n## For LLMs\n\n- Check `status == \"active\"` before routing traffic\n- If `health.success_rate < 0.8`, bank may be unreliable\n- Prefer `endpoint`: one address for the whole bank, load-balanced across\n  its workers. It is **null unless something is actually listening on it**\n  (it used to be synthesised from the port and advertised dead ports —\n  cbvpn#58); `endpoint_detail` says why when it is.\n- Fall back to `worker_endpoints` — per-worker proxies, each verified to be\n  carrying traffic. Use these when `endpoint` is null, or when you want to\n  pin one exit rather than be balanced across several.\n- `health.workers_wrong_geo > 0` means the bank routes through the wrong\n  geography even though the tunnels are up.","example":{"created":"2024-01-15T10:30:00Z","endpoint_detail":"nothing listening on 17.0.0.1:8890","endpoint_status":"unreachable","filter":{"country":"us","state":"ca"},"health":{"success_rate":1.0,"workers_down":0,"workers_total":4,"workers_up":4,"workers_wrong_geo":0},"name":"California Research","port":8890,"status":"degraded","tag":"californiaresearch","worker_endpoints":["http://17.0.0.10:3129","http://17.0.0.11:3129"],"workers":[1,2,3,4]}},"cbvpn__BankCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name","description":"Human-readable bank name. Will be normalized to create 'tag'.","examples":["California Research","Germany Primary"]},"workers":{"items":{"type":"integer"},"type":"array","maxItems":16,"minItems":1,"title":"Workers","description":"Worker numbers to assign (1-15). Must not be already allocated to another bank.","examples":[[1,2,3,4],[5,6,7]]},"filter":{"type":"string","pattern":"^[a-z]{2}(:[a-z]{2})?$","title":"Filter","description":"Geographic filter string: '{country}' or '{country}:{state}'.","examples":["us:ca","de","us:ny","gb"]}},"type":"object","required":["name","workers","filter"],"title":"BankCreate","description":"Request model for creating a new VPN bank.\n\nCreates a bank with workers assigned to a specific geographic region.\nWorkers will be configured with VPN profiles matching the filter.","example":{"filter":"us:ca","name":"California Research","workers":[1,2,3,4]}},"cbvpn__BankFilter":{"properties":{"country":{"type":"string","maxLength":2,"minLength":2,"pattern":"^[a-z]{2}$","title":"Country","description":"ISO 3166-1 alpha-2 country code (lowercase).","examples":["us","de","gb","jp"]},"state":{"anyOf":[{"type":"string","maxLength":2,"minLength":2,"pattern":"^[a-z]{2}$"},{"type":"null"}],"title":"State","description":"US state code (lowercase). Only applicable when country='us'. **Selects on Proton's own labelling, which is not verified IP geolocation.** Measured 2026-08-22: all 12 `US-OH#*` servers are labelled Columbus, share ONE entry IP that geolocates to Kentucky, and produced exits in Atlanta, Seattle and Kentucky — varying per session, stable within one. If traffic must actually look local to a state, use residential egress (cbvpn#51), not a state filter.","examples":["ca","ny","tx","fl"]},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"Proton server feature the exit must carry: 'p2p', 'tor', 'streaming', 'secure_core', 'ipv6'. Resolved against the harvested inventory's feature bitmask, NOT the profile filename. Combine with an empty state slot for nationwide selection: 'us::p2p'.","examples":["p2p","tor"]}},"type":"object","required":["country"],"title":"BankFilter","description":"Geographic filter for VPN profile selection.\n\nSpecifies the geographic location for VPN exit nodes.\nUsed to route traffic through specific countries or US states."},"cbvpn__BankHealth":{"properties":{"workers_total":{"type":"integer","minimum":0.0,"title":"Workers Total","description":"Total number of workers assigned to this bank.","examples":[4,8]},"workers_up":{"type":"integer","minimum":0.0,"title":"Workers Up","description":"Number of workers with healthy VPN tunnels.","examples":[4,7]},"workers_down":{"type":"integer","minimum":0.0,"title":"Workers Down","description":"Number of workers with failed or stopped VPN tunnels.","examples":[0,1]},"success_rate":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Success Rate","description":"Ratio of healthy workers (workers_up / workers_total). Below 0.8 indicates degraded bank.","examples":[1.0,0.875,0.5]},"workers_wrong_geo":{"type":"integer","minimum":0.0,"title":"Workers Wrong Geo","description":"Workers whose deployed profile does not satisfy the bank's filter. Non-zero means the bank is routing through the wrong geography — traffic flows, so this does not show up as a tunnel failure, but geo-targeted crawling is silently mis-sited.","default":0,"examples":[0,1]}},"type":"object","required":["workers_total","workers_up","workers_down","success_rate"],"title":"BankHealth","description":"Bank health metrics for monitoring.\n\nAggregated health status across all workers in the bank.\nUse to determine if bank is ready for traffic routing."},"cbvpn__BankUpdate":{"properties":{"workers":{"anyOf":[{"items":{"type":"integer"},"type":"array","maxItems":16,"minItems":1},{"type":"null"}],"title":"Workers","description":"New worker assignments. Omit to keep current workers.","examples":[[1,2,3,4,5]]},"filter":{"anyOf":[{"type":"string","pattern":"^[a-z]{2}(:[a-z]{2})?$"},{"type":"null"}],"title":"Filter","description":"New geographic filter. Omit to keep current filter.","examples":["us:ny","de"]},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"New status: 'active' or 'idle'. Omit to keep current status.","examples":["active","idle"]}},"type":"object","title":"BankUpdate","description":"Request model for updating an existing bank.\n\nAllows modifying worker assignments, geographic filter, or status.\nChanging workers or filter may require bank to be idled first."},"cbvpn__BankWorkerAssignment":{"properties":{"worker_number":{"type":"integer","maximum":16.0,"minimum":1.0,"title":"Worker Number","description":"Worker identifier (1-15).","examples":[1,5,12]},"worker_ip":{"type":"string","title":"Worker Ip","description":"Internal IP address of the worker device.","examples":["17.0.0.101","17.0.0.105"]},"vpn_profile":{"type":"string","title":"Vpn Profile","description":"Name of the VPN profile assigned to this worker.","examples":["us-ca-1.ovpn","de-frankfurt-2.ovpn"]},"vpn_status":{"$ref":"#/components/schemas/cbvpn__VPNStatus","description":"Current VPN tunnel status for this worker."},"exit_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Ip","description":"Current public exit IP address. Null if VPN is down.","examples":["203.0.113.42","198.51.100.17"]},"proxy_endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proxy Endpoint","description":"HTTP proxy URL for this worker, set only when the worker is verified to carry traffic (VPN up, proxy running, exit IP observed). This is the endpoint consumers should use; see `Bank.worker_endpoints`. Null means do not route here.","examples":["http://17.0.0.10:3129"]},"geo_expected":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo Expected","description":"Filter the owning bank asks for, e.g. 'us:oh'.","examples":["us:oh","us::p2p"]},"geo_actual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo Actual","description":"Geography of the profile actually deployed, parsed from its filename, e.g. 'us:ca'. Null if no profile is identifiable.","examples":["us:ca","cz"]},"geo_match":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Geo Match","description":"False when the deployed profile does not satisfy the bank's filter — a real state that used to be invisible: the lifecycle daemon primes workers from its own geo and never consulted bank ownership, so an 'us:oh' bank ran a California exit (cbvpn#58). Null when it cannot be determined. **This compares the filter against the profile Proton labelled, not against where the exit IP geolocates** — true here means 'we deployed what was asked for', not 'the traffic looks like it comes from there'. For the observed location, call `/workers/{n}/exit-ip`."}},"type":"object","required":["worker_number","worker_ip","vpn_profile","vpn_status"],"title":"BankWorkerAssignment","description":"Worker assignment details within a bank.\n\nShows VPN profile and connection status for each worker\nassigned to this bank."},"cbvpn__ClusterIdleRequest":{"properties":{"target":{"type":"string","pattern":"^(all|\\d+)$","title":"Target"},"stop_proxy":{"type":"boolean","title":"Stop Proxy","default":false}},"type":"object","required":["target"],"title":"ClusterIdleRequest","description":"Request to idle workers"},"cbvpn__ClusterResumeRequest":{"properties":{"target":{"type":"string","pattern":"^(all|\\d+)$","title":"Target"},"start_proxy":{"type":"boolean","title":"Start Proxy","default":true}},"type":"object","required":["target"],"title":"ClusterResumeRequest","description":"Request to resume workers"},"cbvpn__ClusterState":{"properties":{"master":{"$ref":"#/components/schemas/cbvpn__Master"},"workers":{"items":{"$ref":"#/components/schemas/cbvpn__Worker"},"type":"array","title":"Workers"},"gateway":{"anyOf":[{"$ref":"#/components/schemas/cbvpn__Gateway"},{"type":"null"}]},"banks":{"items":{"$ref":"#/components/schemas/cbvpn__Bank"},"type":"array","title":"Banks"},"total_workers":{"type":"integer","title":"Total Workers","default":16},"available_workers":{"items":{"type":"integer"},"type":"array","title":"Available Workers"},"assigned_workers":{"items":{"type":"integer"},"type":"array","title":"Assigned Workers"},"healthy_workers":{"type":"integer","title":"Healthy Workers"},"unhealthy_workers":{"type":"integer","title":"Unhealthy Workers"},"cluster_health_rate":{"type":"number","title":"Cluster Health Rate"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","required":["master","workers","banks","available_workers","assigned_workers","healthy_workers","unhealthy_workers","cluster_health_rate","timestamp"],"title":"ClusterState","description":"Complete cluster state"},"cbvpn__CommandResult":{"properties":{"device_id":{"type":"integer","title":"Device Id"},"command":{"type":"string","title":"Command"},"exit_code":{"type":"integer","title":"Exit Code"},"stdout":{"type":"string","title":"Stdout","default":""},"stderr":{"type":"string","title":"Stderr","default":""},"success":{"type":"boolean","title":"Success","default":false},"duration_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration Ms"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","required":["device_id","command","exit_code"],"title":"CommandResult","description":"Result of executing a command on a device"},"cbvpn__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbvpn__DaemonStatus":{"type":"string","enum":["stopped","starting","running","stopping"],"title":"DaemonStatus","description":"Overall daemon status."},"cbvpn__DaemonStatusResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbvpn__DaemonStatus"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"last_loop_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Loop At"},"loop_count":{"type":"integer","title":"Loop Count"},"managed_workers":{"type":"integer","title":"Managed Workers"},"active_workers":{"type":"integer","title":"Active Workers"},"cooldown_workers":{"type":"integer","title":"Cooldown Workers"}},"type":"object","required":["status","started_at","last_loop_at","loop_count","managed_workers","active_workers","cooldown_workers"],"title":"DaemonStatusResponse","description":"Response for GET /daemon/."},"cbvpn__DeviceRegistry":{"properties":{"id":{"type":"integer","maximum":16.0,"minimum":1.0,"title":"Id","description":"Unique device ID (1-16)"},"hostname":{"type":"string","title":"Hostname","description":"Device hostname, e.g., 'lazarus-worker-01'"},"lan_ip":{"type":"string","title":"Lan Ip","description":"LAN IP address, e.g., '17.0.0.10'"},"wg_interface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wg Interface","description":"WireGuard interface name, e.g., 'wg01'"},"wg_tunnel_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wg Tunnel Ip","description":"WireGuard tunnel IP, e.g., '10.200.1.2'"},"wg_public_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wg Public Key","description":"WireGuard public key"},"wg_endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wg Endpoint","description":"WireGuard endpoint address"},"vpn_profile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Profile","description":"Active VPN profile filename, e.g., 'es-123.protonvpn.udp.ovpn'"},"vpn_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Country","description":"VPN exit country, e.g., 'Spain'"},"vpn_region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Region","description":"VPN exit region/state, e.g., 'Madrid'"},"vpn_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Provider","description":"VPN provider name, e.g., 'ProtonVPN'"},"external_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Ip","description":"Current external/exit IP address"},"status":{"$ref":"#/components/schemas/cbvpn__DeviceStatus","default":"unknown"},"last_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen"},"uptime":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Uptime","description":"Uptime in seconds"},"load_avg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load Avg","description":"1-minute load average"},"memory_free_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Memory Free Mb","description":"Free memory in MB"},"memory_total_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Memory Total Mb","description":"Total memory in MB"},"disk_free_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Disk Free Mb","description":"Free disk space in MB"},"ping_latency_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ping Latency Ms","description":"Ping latency to device in milliseconds"},"download_speed_mbps":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Download Speed Mbps","description":"Download speed in Mbps"},"upload_speed_mbps":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Upload Speed Mbps","description":"Upload speed in Mbps"},"last_speedtest":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Speedtest","description":"Timestamp of last speed test"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Custom tags for filtering"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Admin notes"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","hostname","lan_ip"],"title":"DeviceRegistry","description":"Complete device registry entry for comprehensive device tracking.\n\nCombines device info, WireGuard tunnel config, VPN connection details,\nand performance metrics into a single unified model.","example":{"download_speed_mbps":95.2,"external_ip":"185.159.157.42","hostname":"lazarus-worker-01","id":1,"lan_ip":"17.0.0.10","last_seen":"2026-01-03T12:00:00Z","load_avg":0.15,"memory_free_mb":128.5,"memory_total_mb":256.0,"notes":"Primary EU exit node","ping_latency_ms":2.5,"status":"online","tags":["production","europe"],"upload_speed_mbps":48.7,"uptime":86400,"vpn_country":"Spain","vpn_profile":"es-mad-001.protonvpn.udp.ovpn","vpn_provider":"ProtonVPN","vpn_region":"Madrid","wg_interface":"wg01","wg_tunnel_ip":"10.200.1.2"}},"cbvpn__DeviceRegistryUpdate":{"properties":{"hostname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hostname"},"wg_interface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wg Interface"},"wg_tunnel_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wg Tunnel Ip"},"wg_public_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wg Public Key"},"wg_endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wg Endpoint"},"vpn_profile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Profile"},"vpn_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Country"},"vpn_region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Region"},"vpn_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Provider"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"DeviceRegistryUpdate","description":"Request model for updating device registry entry"},"cbvpn__DeviceStatus":{"type":"string","enum":["online","offline","degraded","unknown"],"title":"DeviceStatus"},"cbvpn__DeviceType":{"type":"string","enum":["master","worker","gateway"],"title":"DeviceType"},"cbvpn__EgressClass":{"type":"string","enum":["datacenter","residential"],"title":"EgressClass","description":"The kind of address traffic leaves from.\n\nConsumers should *ask for* a class rather than infer one from an exit IP."},"cbvpn__ExternalIPInfo":{"properties":{"device_id":{"type":"integer","title":"Device Id"},"ip":{"type":"string","title":"Ip"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"org":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org"},"asn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asn"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","required":["device_id","ip"],"title":"ExternalIPInfo","description":"External IP information with geolocation","example":{"asn":"AS62014","city":"Madrid","country":"Spain","country_code":"ES","device_id":1,"ip":"185.159.157.42","org":"Proton AG","region":"Madrid"}},"cbvpn__Gateway":{"properties":{"ip":{"type":"string","title":"Ip","default":"17.0.0.2"},"hostname":{"type":"string","title":"Hostname"},"device_type":{"$ref":"#/components/schemas/cbvpn__DeviceType","default":"gateway"},"status":{"$ref":"#/components/schemas/cbvpn__DeviceStatus","default":"unknown"},"last_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen"},"uptime":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Uptime"},"load_avg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load Avg"},"memory_free":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Memory Free"},"memory_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Memory Total"},"wan_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wan Ip"},"wan_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wan Status"},"dhcp_leases":{"type":"integer","title":"Dhcp Leases","default":0}},"type":"object","required":["hostname"],"title":"Gateway","description":"Gateway/router device","example":{"device_type":"worker","hostname":"worker1","ip":"17.0.0.10","load_avg":0.15,"status":"online","uptime":86400}},"cbvpn__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbvpn__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbvpn__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbvpn__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbvpn__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbvpn__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbvpn__LifecycleEvent":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"event":{"type":"string","title":"Event"},"worker_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Worker Number"},"geo_filter":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo Filter"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"success":{"type":"boolean","title":"Success","default":true}},"type":"object","required":["timestamp","event"],"title":"LifecycleEvent","description":"Single lifecycle event for structured logging."},"cbvpn__Master":{"properties":{"ip":{"type":"string","title":"Ip","default":"17.0.0.1"},"hostname":{"type":"string","title":"Hostname"},"device_type":{"$ref":"#/components/schemas/cbvpn__DeviceType","default":"master"},"status":{"$ref":"#/components/schemas/cbvpn__DeviceStatus","default":"unknown"},"last_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen"},"uptime":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Uptime"},"load_avg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load Avg"},"memory_free":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Memory Free"},"memory_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Memory Total"},"haproxy_running":{"type":"boolean","title":"Haproxy Running","default":false},"haproxy_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Haproxy Version"},"haproxy_pid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Haproxy Pid"},"banks_active":{"type":"integer","title":"Banks Active","default":0},"workers_assigned":{"type":"integer","title":"Workers Assigned","default":0},"workers_available":{"type":"integer","title":"Workers Available","default":16},"ports_used":{"items":{"type":"integer"},"type":"array","title":"Ports Used","default":[]},"ports_listening":{"items":{"type":"integer"},"type":"array","title":"Ports Listening","default":[]},"ports_available":{"type":"integer","title":"Ports Available","default":110}},"type":"object","required":["hostname"],"title":"Master","description":"Master node with HAProxy and VPN bank state","example":{"device_type":"worker","hostname":"worker1","ip":"17.0.0.10","load_avg":0.15,"status":"online","uptime":86400}},"cbvpn__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbvpn__NodeInfo":{"properties":{"worker_number":{"type":"integer","title":"Worker Number"},"hostname":{"type":"string","title":"Hostname"},"lan_ip":{"type":"string","title":"Lan Ip"},"vpn_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Ip"},"exit_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Ip"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"org":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org"},"status":{"type":"string","title":"Status"},"vpn_status":{"type":"string","title":"Vpn Status"},"proxy_status":{"type":"string","title":"Proxy Status"},"is_healthy":{"type":"boolean","title":"Is Healthy"},"assignment":{"type":"string","title":"Assignment"},"assigned_bank":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned Bank"},"uptime":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Uptime"},"load_avg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load Avg"}},"type":"object","required":["worker_number","hostname","lan_ip","vpn_ip","exit_ip","country","region","city","org","status","vpn_status","proxy_status","is_healthy","assignment","assigned_bank","uptime","load_avg"],"title":"NodeInfo","description":"Enriched status for a single worker node."},"cbvpn__NodeState":{"type":"string","enum":["enrolling","active","offline","burned","retired"],"title":"NodeState","description":"Lifecycle of a residential node."},"cbvpn__NodeStatusSummary":{"properties":{"total_workers":{"type":"integer","title":"Total Workers"},"workers_online":{"type":"integer","title":"Workers Online"},"workers_offline":{"type":"integer","title":"Workers Offline"},"vpn_up":{"type":"integer","title":"Vpn Up"},"vpn_down":{"type":"integer","title":"Vpn Down"},"proxy_running":{"type":"integer","title":"Proxy Running"},"proxy_stopped":{"type":"integer","title":"Proxy Stopped"},"healthy":{"type":"integer","title":"Healthy"},"unhealthy":{"type":"integer","title":"Unhealthy"},"available":{"type":"integer","title":"Available"},"assigned":{"type":"integer","title":"Assigned"},"health_rate":{"type":"number","title":"Health Rate"},"nodes":{"items":{"$ref":"#/components/schemas/cbvpn__NodeInfo"},"type":"array","title":"Nodes"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"source":{"type":"string","title":"Source","description":"Where this view came from: 'telemetry' (the collector's measured snapshot — normal) or 'probe' (live SSH fallback, no snapshot yet).","default":"telemetry","examples":["telemetry","probe"]},"collected_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Collected At","description":"When the underlying snapshot was measured (ISO-8601 UTC)."},"age_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Age Seconds","description":"Age of the snapshot at response time. Collector runs every 60s."},"stale":{"type":"boolean","title":"Stale","description":"True when the snapshot is too old to base health claims on. Fails closed: while stale, no worker reports healthy, though last-observed exit IPs are still returned for diagnosis.","default":false}},"type":"object","required":["total_workers","workers_online","workers_offline","vpn_up","vpn_down","proxy_running","proxy_stopped","healthy","unhealthy","available","assigned","health_rate","nodes","timestamp"],"title":"NodeStatusSummary","description":"Enriched status list with aggregate summary and per-node details."},"cbvpn__OperationType":{"type":"string","enum":["bank_create","bank_delete","bank_update","bank_list","bank_get","worker_list","worker_get","worker_vpn_start","worker_vpn_stop","worker_proxy_start","worker_proxy_stop","cluster_status","cluster_reset","device_command","job_crawl","job_lazarus","job_screenshot","job_transcript","job_vectl","job_status","job_cancel","fetch","health_check","root","usage_query"],"title":"OperationType","description":"Types of tracked API operations."},"cbvpn__PingResult":{"properties":{"device_id":{"type":"integer","title":"Device Id"},"target":{"type":"string","title":"Target"},"success":{"type":"boolean","title":"Success"},"latency_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latency Ms"},"packets_sent":{"type":"integer","title":"Packets Sent","default":3},"packets_received":{"type":"integer","title":"Packets Received","default":0},"packet_loss_percent":{"type":"number","title":"Packet Loss Percent","default":100.0},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","required":["device_id","target","success"],"title":"PingResult","description":"Result of a ping test","example":{"device_id":1,"latency_ms":2.5,"packet_loss_percent":0.0,"packets_received":3,"packets_sent":3,"success":true,"target":"17.0.0.10"}},"cbvpn__PrimeRequest":{"properties":{"workers":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Workers","description":"Worker numbers to prime. Null = all 16."},"geo_filter":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo Filter","description":"Geographic filter for all workers. Null = each worker's existing geo, or random if it has none. A worker owned by a bank always uses its bank's filter regardless of this."},"force":{"type":"boolean","title":"Force","description":"Re-deploy even to workers that are already healthy and correctly sited. Off by default: priming is a repair action, and rebuilding a working fleet restarts every tunnel it touches (cbvpn#61).","default":false}},"type":"object","title":"PrimeRequest","description":"Request body for POST /daemon/prime."},"cbvpn__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbvpn__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbvpn__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbvpn__ProtonServer":{"properties":{"id":{"type":"string","title":"Id","description":"Proton LogicalID"},"name":{"type":"string","title":"Name","description":"Server name, e.g. US-CA#205","examples":["US-CA#205"]},"entry_country":{"type":"string","title":"Entry Country","description":"ISO-3166 country traffic enters","examples":["US"]},"exit_country":{"type":"string","title":"Exit Country","description":"ISO-3166 country traffic exits","examples":["US"]},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"Exit city, when Proton publishes one"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region","description":"State/region, when published","examples":["CA"]},"tier":{"type":"integer","title":"Tier","description":"Plan tier required: 0 free, 2 plus"},"status":{"type":"integer","title":"Status","description":"1 = up, 0 = down (Proton's own flag)"},"load":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Load","description":"Reported load percentage","default":0},"score":{"type":"number","title":"Score","description":"Proton's selection score; lower is better","default":0.0},"features":{"type":"integer","title":"Features","description":"Feature bitmask — see ServerFeature","default":0},"entry_ips":{"items":{"type":"string"},"type":"array","title":"Entry Ips","description":"Live entry IPs"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain","description":"Server domain name"},"is_secure_core":{"type":"boolean","title":"Is Secure Core","description":"Entry and exit countries differ — check `exit_country` for geography.","readOnly":true},"supports_p2p":{"type":"boolean","title":"Supports P2P","description":"Proton's torrent-permission flag. NOT an IP-reputation property.","readOnly":true},"slug":{"type":"string","title":"Slug","description":"Filename-safe identity, e.g. `US-CA#205` -> `us-ca-205`.","readOnly":true}},"type":"object","required":["id","name","entry_country","exit_country","tier","status","is_secure_core","supports_p2p","slug"],"title":"ProtonServer","description":"One Proton logical server, flattened for our purposes.\n\nA logical server may sit on several physical machines; each contributes an\nentry IP. `entry_ips` holds the ones currently marked up — those are what\nbecome `remote` lines."},"cbvpn__ProxyStatus":{"type":"string","enum":["running","stopped","error"],"title":"ProxyStatus","description":"HTTP proxy service status.\n\nIndicates the current state of TinyProxy/HAProxy on a worker device.\nProxy must be RUNNING for HTTP traffic to route through the worker.\n\n## For LLMs\n\n**Health check logic:**\n```python\nif worker.proxy_status == ProxyStatus.RUNNING:\n    # Ready for HTTP traffic\n    pass\nelif worker.proxy_status == ProxyStatus.STOPPED:\n    # Not running - call POST /workers/{n}/proxy/start\n    pass\nelif worker.proxy_status == ProxyStatus.ERROR:\n    # Failed - restart or investigate\n    pass\n```"},"cbvpn__ResidentialNode":{"properties":{"node_id":{"type":"string","title":"Node Id","description":"Stable slug, unique fleet-wide","examples":["home-gr-01"]},"site":{"type":"string","title":"Site","description":"Location key. THE UNIT OF REPUTATION: every node sharing a site shares one public IP and one fate. Distinct sites are what buy diversity; extra nodes at one site do not.","examples":["home-gr"]},"label":{"type":"string","title":"Label","description":"Human name","examples":["Home — study"]},"wg_ip":{"type":"string","title":"Wg Ip","description":"Address Monroe reaches this node on, over WireGuard","examples":["10.8.0.33"]},"proxy_port":{"type":"integer","title":"Proxy Port","description":"tinyproxy port, wg-bound only","default":3129},"public_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Ip","description":"Last OBSERVED egress address. Dynamic on consumer lines — treat a change as routine, not as an incident."},"asn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asn","description":"Observed ASN","examples":["AS7018"]},"isp":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Isp","description":"Observed ISP name"},"state":{"$ref":"#/components/schemas/cbvpn__NodeState","default":"enrolling"},"enrolled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enrolled At","description":"ISO-8601 UTC"},"last_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen","description":"ISO-8601 UTC, last healthy probe"},"public_ip_seen_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Ip Seen At","description":"ISO-8601 UTC"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free text, e.g. whose house this is and who to call"},"egress_class":{"$ref":"#/components/schemas/cbvpn__EgressClass","description":"Always residential. Present so one shape answers both pools.","readOnly":true},"proxy_url":{"type":"string","title":"Proxy Url","description":"Where a consumer points `http_proxy`. wg-side only, never the WAN.","readOnly":true},"is_stale":{"type":"boolean","title":"Is Stale","description":"Whether the last observation is too old to still count as evidence.\n\n`state` and `public_ip` are both facts recorded at the last probe, and\nneither decays on its own — so without this, \"was working when we last\nlooked\" is served as \"is working\". Unknown age fails closed: never\nobserved and unparseable both count as stale, because the alternative\nis treating absence of evidence as evidence of health.","readOnly":true},"is_selectable":{"type":"boolean","title":"Is Selectable","description":"Whether this node may carry a request *right now*.\n\nDeliberately strict, and deliberately not the same thing as \"the box is\nreachable\": a node with no observed public IP has never proved it\negresses where we think it does, and one nobody has observed lately has\nnot proved it recently. On 2026-08-02 the watcher had been dead for 38\nhours and this still answered True for every node in the registry.","readOnly":true}},"type":"object","required":["node_id","site","label","wg_ip","egress_class","proxy_url","is_stale","is_selectable"],"title":"ResidentialNode","description":"One OpenWRT egress device at a residential site.\n\nNote what is *absent*: there is no VPN profile, no tunnel lifecycle, no\nbank assignment. A residential node has nothing to bring up — its egress is\nits WAN. Modelling it as a `Worker` would put it in a state machine that\nwould immediately try to stage a Proton profile onto it and destroy the one\nproperty we want."},"cbvpn__SiteReputation":{"properties":{"site":{"type":"string","title":"Site"},"burned":{"type":"boolean","title":"Burned","default":false},"burned_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Burned At"},"burn_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Burn Reason"},"burn_evidence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Burn Evidence","description":"What was seen: status code, byte count, challenge marker"}},"type":"object","required":["site"],"title":"SiteReputation","description":"Burn state for a site — the thing a bot-wall actually scores."},"cbvpn__SpeedTestResult":{"properties":{"device_id":{"type":"integer","title":"Device Id"},"download_mbps":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Download Mbps"},"upload_mbps":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Upload Mbps"},"latency_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latency Ms"},"server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Server"},"success":{"type":"boolean","title":"Success","default":false},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","required":["device_id"],"title":"SpeedTestResult","description":"Result of a speed test","example":{"device_id":1,"download_mbps":95.2,"latency_ms":15.3,"server":"speedtest.net - Madrid","success":true,"upload_mbps":48.7}},"cbvpn__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbvpn__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbvpn__SystemInfo":{"properties":{"device_id":{"type":"integer","title":"Device Id"},"hostname":{"type":"string","title":"Hostname"},"kernel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kernel"},"uptime":{"type":"integer","title":"Uptime","default":0},"load_avg_1m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load Avg 1M"},"load_avg_5m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load Avg 5M"},"load_avg_15m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load Avg 15M"},"memory_total_kb":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Memory Total Kb"},"memory_free_kb":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Memory Free Kb"},"memory_buffers_kb":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Memory Buffers Kb"},"disk_total_kb":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Disk Total Kb"},"disk_free_kb":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Disk Free Kb"},"cpu_usage_percent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cpu Usage Percent"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","required":["device_id","hostname"],"title":"SystemInfo","description":"System information from a device"},"cbvpn__TeardownRequest":{"properties":{"workers":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Workers","description":"Worker numbers to tear down. Null = all managed."}},"type":"object","title":"TeardownRequest","description":"Request body for POST /daemon/teardown."},"cbvpn__TelemetryIngestResponse":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true},"stored_at":{"type":"string","title":"Stored At","description":"ISO-8601 UTC store timestamp"},"workers":{"type":"integer","title":"Workers","description":"Worker count in the stored snapshot"},"live":{"type":"integer","title":"Live","description":"Live worker count"},"history_depth":{"type":"integer","title":"History Depth","description":"Snapshots retained in the ring buffer"}},"type":"object","required":["stored_at","workers","live","history_depth"],"title":"TelemetryIngestResponse","description":"Ack for an ingested snapshot."},"cbvpn__TelemetrySnapshot":{"properties":{"collected_at":{"type":"string","title":"Collected At","description":"ISO-8601 UTC collection timestamp"},"source":{"type":"string","title":"Source","description":"Emitter identity","default":"pool-collector"},"workers":{"items":{"$ref":"#/components/schemas/cbvpn__WorkerTelemetry"},"type":"array","title":"Workers"}},"type":"object","required":["collected_at"],"title":"TelemetrySnapshot","description":"A full fleet telemetry snapshot pushed by the collector."},"cbvpn__TrafficStats":{"properties":{"bytes_sent":{"type":"integer","title":"Bytes Sent","default":0},"bytes_received":{"type":"integer","title":"Bytes Received","default":0},"packets_sent":{"type":"integer","title":"Packets Sent","default":0},"packets_received":{"type":"integer","title":"Packets Received","default":0},"interface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interface"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"}},"type":"object","title":"TrafficStats","description":"Network traffic statistics from OpenWRT device."},"cbvpn__UsageRecord":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Unique request identifier (UUID)"},"operation":{"$ref":"#/components/schemas/cbvpn__OperationType"},"endpoint":{"type":"string","title":"Endpoint"},"method":{"type":"string","title":"Method","default":"GET"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"api_key_prefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Prefix"},"source_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Ip"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"latency_ms":{"type":"number","title":"Latency Ms","default":0.0},"request_params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Request Params"},"response_status":{"type":"integer","title":"Response Status","default":200},"success":{"type":"boolean","title":"Success","default":true},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"vpn_info":{"anyOf":[{"$ref":"#/components/schemas/cbvpn__VPNConnectionInfo"},{"type":"null"}]},"traffic":{"anyOf":[{"$ref":"#/components/schemas/cbvpn__TrafficStats"},{"type":"null"}]},"tokens_used":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tokens Used"},"bytes_processed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bytes Processed"}},"type":"object","required":["request_id","operation","endpoint"],"title":"UsageRecord","description":"Individual API usage record.\n\nTracks each API request with operation type, timing, user info,\nand optional VPN/traffic context."},"cbvpn__UsageResponse":{"properties":{"records":{"items":{"$ref":"#/components/schemas/cbvpn__UsageRecord"},"type":"array","title":"Records"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page","default":1},"page_size":{"type":"integer","title":"Page Size","default":50},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["records","total"],"title":"UsageResponse","description":"Paginated usage records response."},"cbvpn__UsageSummary":{"properties":{"period_start":{"type":"string","format":"date-time","title":"Period Start"},"period_end":{"type":"string","format":"date-time","title":"Period End"},"period_type":{"type":"string","title":"Period Type","default":"day"},"total_requests":{"type":"integer","title":"Total Requests","default":0},"successful_requests":{"type":"integer","title":"Successful Requests","default":0},"failed_requests":{"type":"integer","title":"Failed Requests","default":0},"success_rate":{"type":"number","title":"Success Rate","default":0.0},"avg_latency_ms":{"type":"number","title":"Avg Latency Ms","default":0.0},"min_latency_ms":{"type":"number","title":"Min Latency Ms","default":0.0},"max_latency_ms":{"type":"number","title":"Max Latency Ms","default":0.0},"p95_latency_ms":{"type":"number","title":"P95 Latency Ms","default":0.0},"operations":{"additionalProperties":{"type":"integer"},"type":"object","title":"Operations"},"unique_users":{"type":"integer","title":"Unique Users","default":0},"user_breakdown":{"additionalProperties":{"type":"integer"},"type":"object","title":"User Breakdown"},"vpn_requests":{"type":"integer","title":"Vpn Requests","default":0},"vpn_countries":{"additionalProperties":{"type":"integer"},"type":"object","title":"Vpn Countries"},"active_vpn_connections":{"type":"integer","title":"Active Vpn Connections","default":0},"total_bytes_sent":{"type":"integer","title":"Total Bytes Sent","default":0},"total_bytes_received":{"type":"integer","title":"Total Bytes Received","default":0}},"type":"object","required":["period_start","period_end"],"title":"UsageSummary","description":"Aggregated usage statistics."},"cbvpn__UserInfo":{"properties":{"user_id":{"type":"string","title":"User Id"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"api_key_prefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Prefix"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_seen":{"type":"string","format":"date-time","title":"Last Seen"},"total_requests":{"type":"integer","title":"Total Requests","default":0},"total_bytes_transferred":{"type":"integer","title":"Total Bytes Transferred","default":0},"active_vpn_connections":{"type":"integer","title":"Active Vpn Connections","default":0}},"type":"object","required":["user_id"],"title":"UserInfo","description":"API user information for tracking and reporting."},"cbvpn__VPNConnectionInfo":{"properties":{"connected":{"type":"boolean","title":"Connected","default":false},"worker_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Worker Id"},"bank_tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bank Tag"},"vpn_profile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Profile"},"exit_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Ip"},"exit_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Country"},"exit_region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Region"},"exit_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit City"},"vpn_interface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Interface"},"connected_since":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Connected Since"}},"type":"object","title":"VPNConnectionInfo","description":"VPN connection status at time of request."},"cbvpn__VPNStatus":{"type":"string","enum":["up","down","connecting","failed"],"title":"VPNStatus","description":"VPN tunnel connection status.\n\nIndicates the current state of the WireGuard/OpenVPN tunnel\non a worker device.\n\n## For LLMs\n\n**Health check logic:**\n```python\nif worker.vpn_status == VPNStatus.UP:\n    # Healthy - traffic will route through VPN\n    pass\nelif worker.vpn_status == VPNStatus.DOWN:\n    # Stopped - call POST /workers/{n}/vpn/start\n    pass\nelif worker.vpn_status == VPNStatus.CONNECTING:\n    # Wait and check again in a few seconds\n    pass\nelif worker.vpn_status == VPNStatus.FAILED:\n    # Error - check logs, may need restart or config fix\n    pass\n```"},"cbvpn__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbvpn__Worker":{"properties":{"ip":{"type":"string","title":"Ip"},"hostname":{"type":"string","title":"Hostname"},"device_type":{"$ref":"#/components/schemas/cbvpn__DeviceType","default":"worker"},"status":{"$ref":"#/components/schemas/cbvpn__DeviceStatus","default":"unknown"},"last_seen":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen"},"uptime":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Uptime"},"load_avg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load Avg"},"memory_free":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Memory Free"},"memory_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Memory Total"},"worker_number":{"type":"integer","maximum":16.0,"minimum":1.0,"title":"Worker Number","description":"Worker identifier. Workers are numbered 1-15.","examples":[1,5,12]},"assignment_status":{"$ref":"#/components/schemas/cbvpn__WorkerAssignment","description":"Whether worker is available, assigned to a bank, or in maintenance.","default":"available"},"assigned_bank":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned Bank","description":"Bank tag this worker is assigned to. Null if unassigned.","examples":["californiaresearch","germanyprimary"]},"vpn_status":{"$ref":"#/components/schemas/cbvpn__VPNStatus","description":"Current VPN tunnel status.","default":"down"},"vpn_interface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Interface","description":"VPN network interface name.","examples":["tun0","wg0"]},"vpn_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Ip","description":"VPN-assigned IP address (internal).","examples":["10.8.0.2","10.66.66.2"]},"vpn_profile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vpn Profile","description":"VPN profile filename currently in use.","examples":["us-ca-1.ovpn","de-frankfurt-2.conf"]},"vpn_connected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Vpn Connected At","description":"When VPN tunnel was established."},"exit_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Ip","description":"Public IP address traffic exits from. Use to verify geographic routing.","examples":["203.0.113.42","198.51.100.17"]},"exit_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Country","description":"Country of exit IP (from geolocation).","examples":["US","DE","GB"]},"exit_region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Region","description":"State/region of exit IP.","examples":["California","Bavaria","England"]},"exit_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit City","description":"City of exit IP (approximate).","examples":["Los Angeles","Frankfurt","London"]},"exit_org":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Org","description":"ISP/organization of exit IP. Usually VPN provider name.","examples":["Mullvad VPN","NordVPN","ExpressVPN"]},"exit_ip_checked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Exit Ip Checked At","description":"Timestamp of last successful exit_ip probe through the worker's HTTP CONNECT proxy. Use this to decide whether to trust the cached exit_ip — stale values may not reflect the current VPN exit."},"proxy_status":{"$ref":"#/components/schemas/cbvpn__ProxyStatus","description":"Current HTTP proxy status.","default":"stopped"},"proxy_port":{"type":"integer","title":"Proxy Port","description":"Port proxy is listening on.","default":3128,"examples":[3128,8080]},"proxy_connections":{"type":"integer","minimum":0.0,"title":"Proxy Connections","description":"Current active proxy connections.","default":0},"haproxy_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Haproxy Status","description":"HAProxy backend status (UP/DOWN).","examples":["UP","DOWN"]},"haproxy_check_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Haproxy Check Status","description":"Last HAProxy health check result.","examples":["L7OK","L4TOUT"]},"haproxy_last_check":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Haproxy Last Check","description":"Timestamp of last HAProxy health check."},"worker_ip":{"type":"string","title":"Worker Ip","description":"Calculate worker IP from worker number","readOnly":true},"is_healthy":{"type":"boolean","title":"Is Healthy","description":"Worker is **verified to carry traffic** (cbvpn#52 P0).\n\nRequires an observed `exit_ip`, not just live processes. Workers #6/#8\nran a healthy tinyproxy over *no VPN profile* and reported vpn=up,\nproxy=running while egressing as the host's bare IP — traffic would\nhave left raw with every log line saying `egress: proxy`\n(cbintel#213's failure class). A running process is not a working exit.","readOnly":true}},"type":"object","required":["ip","hostname","worker_number","worker_ip","is_healthy"],"title":"Worker","description":"Worker node with VPN and proxy capabilities.\n\nWorkers are OpenWRT devices running WireGuard VPN tunnels and HTTP proxies.\nThey provide geographic exit points for traffic routing.\n\n## For LLMs\n\n**Check if worker is ready for traffic:**\n```python\nif worker.is_healthy:\n    # VPN up AND proxy running - safe to use\n    return worker.exit_ip\nelse:\n    # Needs attention\n    if worker.vpn_status != VPNStatus.UP:\n        return f\"VPN {worker.vpn_status.value} - restart needed\"\n    if worker.proxy_status != ProxyStatus.RUNNING:\n        return f\"Proxy {worker.proxy_status.value} - restart needed\"\n```","example":{"device_type":"worker","hostname":"worker1","ip":"17.0.0.10","load_avg":0.15,"status":"online","uptime":86400}},"cbvpn__WorkerAssignment":{"type":"string","enum":["available","assigned","maintenance"],"title":"WorkerAssignment","description":"Worker allocation status.\n\nIndicates whether a worker is available for assignment to a bank\nor already in use.\n\n## For LLMs\n\n**Allocation logic:**\n- Only AVAILABLE workers can be assigned to new banks\n- ASSIGNED workers are already in a bank\n- MAINTENANCE workers are temporarily unavailable\n\n```python\navailable_count = sum(\n    1 for w in workers if w.assignment_status == WorkerAssignment.AVAILABLE\n)\nif available_count < needed:\n    return \"Not enough workers available\"\n```"},"cbvpn__WorkerDaemonInfo":{"properties":{"worker_number":{"type":"integer","title":"Worker Number"},"phase":{"$ref":"#/components/schemas/cbvpn__WorkerPhase"},"geo_filter":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geo Filter"},"exit_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Ip"},"exit_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Country"},"prime_attempts":{"type":"integer","title":"Prime Attempts"},"consecutive_failures":{"type":"integer","title":"Consecutive Failures"},"cooldown_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cooldown Until"},"connected_since":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Connected Since"},"last_rx_bytes":{"type":"integer","title":"Last Rx Bytes"},"last_tx_bytes":{"type":"integer","title":"Last Tx Bytes"}},"type":"object","required":["worker_number","phase","geo_filter","exit_ip","exit_country","prime_attempts","consecutive_failures","cooldown_until","connected_since","last_rx_bytes","last_tx_bytes"],"title":"WorkerDaemonInfo","description":"Per-worker info for GET /daemon/workers."},"cbvpn__WorkerPhase":{"type":"string","enum":["idle","priming","validating","active","recovering","cooldown","teardown"],"title":"WorkerPhase","description":"Lifecycle phase for a daemon-managed worker.\n\nTracks where each worker is in the prime → validate → active → teardown cycle."},"cbvpn__WorkerTelemetry":{"properties":{"id":{"type":"string","title":"Id","description":"Commodity identity, e.g. dc0.vpn-worker-01"},"dc":{"type":"string","title":"Dc","description":"Datacenter tag: dc0 | dc1"},"num":{"type":"integer","title":"Num","description":"Worker number within its DC"},"proxy":{"type":"string","title":"Proxy","description":"LAN proxy endpoint host:port"},"status":{"type":"string","title":"Status","description":"live | down"},"exit_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exit Ip","description":"Public VPN exit IP"},"loc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Loc","description":"Exit country code (from cf trace)"},"latency_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latency Ms","description":"Ping RTT to the worker LAN IP"},"temp_c":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Temp C","description":"Pi SoC temperature, °C"},"rx_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rx Bytes","description":"Cumulative tun0 rx bytes"},"tx_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tx Bytes","description":"Cumulative tun0 tx bytes"},"rx_bps":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rx Bps","description":"Derived rx throughput, bytes/s"},"tx_bps":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tx Bps","description":"Derived tx throughput, bytes/s"}},"type":"object","required":["id","dc","num","proxy","status"],"title":"WorkerTelemetry","description":"One worker's telemetry at a point in time."},"cbweave__ActionBarBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"action-bar","title":"Type","description":"Discriminator. Always `action-bar`."},"primary":{"anyOf":[{"$ref":"#/components/schemas/cbweave__ActionItem"},{"type":"null"}],"description":"Primary (emphasized) button."},"secondary":{"anyOf":[{"$ref":"#/components/schemas/cbweave__ActionItem"},{"type":"null"}],"description":"Secondary button."}},"additionalProperties":false,"type":"object","required":["type"],"title":"ActionBarBlock","description":"A button bar with an optional primary and secondary action."},"cbweave__ActionItem":{"properties":{"label":{"type":"string","title":"Label","description":"Button text.","examples":["EXPORT REPORT"]},"tone":{"anyOf":[{"type":"string","enum":["act","analyze","monitor","system","alert","bone","bone-dim","bone-low"]},{"type":"null"}],"title":"Tone","description":"Optional button colour tone."}},"additionalProperties":false,"type":"object","required":["label"],"title":"ActionItem","description":"A single button inside an `action-bar` block (primary or secondary).","examples":[{"label":"EXPORT REPORT","tone":"act"}]},"cbweave__AxisLabels":{"properties":{"left":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Left","description":"Caption under the left (oldest) end of the series."},"right":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Right","description":"Caption under the right (newest) end of the series."}},"additionalProperties":false,"type":"object","title":"AxisLabels","description":"Left/right end-cap captions for a `time-series` block's x-axis.","examples":[{"left":"30D AGO","right":"TODAY"}]},"cbweave__CalloutBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"callout","title":"Type","description":"Discriminator. Always `callout`."},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Callout body text.","examples":["Action required."]},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Optional callout heading."},"tone":{"type":"string","enum":["act","analyze","monitor","system","alert","bone","bone-dim","bone-low"],"title":"Tone","description":"Callout accent tone.","default":"bone-dim"}},"additionalProperties":false,"type":"object","required":["type"],"title":"CalloutBlock","description":"A boxed callout / aside — drawing attention to a short message."},"cbweave__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbweave__DeltaSpec":{"properties":{"value":{"type":"string","title":"Value","description":"Formatted delta value. Positive deltas MUST be `+`-prefixed; ASCII `-` and Unicode MINUS SIGN U+2212 both accepted; `nominal` is the canonical no-change sentinel.","examples":["+12%","−0.3 ▼","nominal"]},"mode":{"type":"string","enum":["positive","negative","warning","neutral"],"title":"Mode","description":"Semantic colour of the delta — drives the tone, independent of the numeric sign. `positive` (good), `negative` (bad), `warning` (caution), `neutral` (no judgement)."},"comparison":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comparison","description":"Optional comparison caption rendered beside the delta.","examples":["3.2% vs prior period"]},"direction":{"anyOf":[{"type":"string","enum":["up","down","flat"]},{"type":"null"}],"title":"Direction","description":"Optional explicit arrow direction. When set it must agree with the sign of `value` (a `+` value with `direction='down'` is rejected). Omit to let the sign drive the arrow."}},"additionalProperties":false,"type":"object","required":["value","mode"],"title":"DeltaSpec","description":"A change indicator attached to a stat or headline.\n\nCaptures the formatted delta string plus its semantic colour (`mode`)\nand optional arrow `direction`. Validated at the boundary per cbai E3:\nsign, mode, and direction must agree (see `_validate_sign_mode_direction`\nand the PLANNING.md DeltaSpec table).","examples":[{"comparison":"vs prior","mode":"positive","value":"+12%"},{"direction":"down","mode":"warning","value":"−0.3 ▼"},{"mode":"neutral","value":"nominal"}]},"cbweave__FrontmatterModel":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Document title.","examples":["Q3 Pipeline"]},"session":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session","description":"Opaque session identifier for the document."},"mode":{"anyOf":[{"type":"string","enum":["act","analyze","monitor","system","alert"]},{"type":"null"}],"title":"Mode","description":"Overall document mode / accent."},"tenant":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant","description":"Owning tenant slug, for theming."}},"additionalProperties":false,"type":"object","title":"FrontmatterModel","description":"Document-level metadata carried alongside the block list.\n\nNote: surface selection lives on the request's `surface` field, not\nhere — this carries title/session/mode/tenant context only.","examples":[{"mode":"analyze","title":"Q3 Pipeline"}]},"cbweave__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbweave__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbweave__HeadlineBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"headline","title":"Type","description":"Discriminator. Always `headline`."},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Metric caption above the value.","examples":["ACTIVE SESSIONS · 24H"]},"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value","description":"Formatted hero value.","examples":["12,478"]},"size":{"type":"string","enum":["xl","lg","md"],"title":"Size","description":"Value type size — `xl`, `lg`, or `md`.","default":"xl"},"tone":{"type":"string","enum":["act","analyze","monitor","system","alert","bone","bone-dim","bone-low"],"title":"Tone","description":"Value colour tone.","default":"bone"},"delta":{"anyOf":[{"$ref":"#/components/schemas/cbweave__DeltaSpec"},{"type":"null"}],"description":"Optional change indicator."}},"additionalProperties":false,"type":"object","required":["type"],"title":"HeadlineBlock","description":"A single hero metric — large value with a label and optional delta."},"cbweave__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbweave__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbweave__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbweave__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbweave__MarkdownToBlocksRequest":{"properties":{"markdown":{"type":"string","title":"Markdown","description":"Markdown source. Optional YAML frontmatter at the top; body is the directive tree (leaf + container + fenced).","examples":["---\nsurface: desktop\n---\n\n::headline{label=\"ACTIVE SESSIONS\" value=\"12,478\"}\n"]}},"additionalProperties":false,"type":"object","required":["markdown"],"title":"MarkdownToBlocksRequest","description":"Body of `POST /api/v1/markdown-to-blocks`."},"cbweave__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbweave__MeterItem":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier echoed as `data-block-id`."},"label":{"type":"string","title":"Label","description":"Meter label.","examples":["Send rate"]},"value":{"type":"string","title":"Value","description":"Formatted value shown beside the bar.","examples":["847/s"]},"fraction":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Fraction","description":"Fill fraction in `[0.0, 1.0]` — drives the bar width.","examples":[0.72]},"tone":{"type":"string","enum":["act","analyze","monitor","system","alert","bone","bone-dim","bone-low"],"title":"Tone","description":"Bar colour tone.","default":"monitor"}},"additionalProperties":false,"type":"object","required":["label","value","fraction"],"title":"MeterItem","description":"A single labelled bar inside a `meter-stack` block.","examples":[{"fraction":0.72,"label":"Send rate","tone":"monitor","value":"847/s"}]},"cbweave__MeterStackBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"meter-stack","title":"Type","description":"Discriminator. Always `meter-stack`."},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Optional section heading above the meters.","examples":["REAL-TIME SIGNALS"]},"items":{"items":{"$ref":"#/components/schemas/cbweave__MeterItem"},"type":"array","title":"Items","description":"Meters rendered top to bottom."}},"additionalProperties":false,"type":"object","required":["type"],"title":"MeterStackBlock","description":"A vertical stack of labelled progress bars (`MeterItem`s)."},"cbweave__NarrativeBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"narrative","title":"Type","description":"Discriminator. Always `narrative`."},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Prose body, wrapped to `max_width`.","examples":["Pipeline health is nominal."]},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Optional heading above the prose."},"tone":{"type":"string","enum":["act","analyze","monitor","system","alert","bone","bone-dim","bone-low"],"title":"Tone","description":"Text colour tone.","default":"bone"},"font_size":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Font Size","description":"Optional body font size override (px)."},"max_width":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Width","description":"Readability cap on line width (px). Defaults to 640 (~75 chars at body 15px). Set to 0 to disable the cap and use the full slot width. Per-block override."}},"additionalProperties":false,"type":"object","required":["type"],"title":"NarrativeBlock","description":"A block of wrapped prose / body copy."},"cbweave__PageFooterBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"page-footer","title":"Type","description":"Discriminator. Always `page-footer`."},"tone":{"type":"string","enum":["act","analyze","monitor","system","alert","bone","bone-dim","bone-low"],"title":"Tone","description":"Footer accent tone.","default":"act"}},"additionalProperties":false,"type":"object","required":["type"],"title":"PageFooterBlock","description":"Bottom-of-page accent bar."},"cbweave__PageHeaderBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"page-header","title":"Type","description":"Discriminator. Always `page-header`."},"tone":{"type":"string","enum":["act","analyze","monitor","system","alert","bone","bone-dim","bone-low"],"title":"Tone","description":"Header accent tone.","default":"analyze"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Main header title.","examples":["Q3 Pipeline"]},"kicker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kicker","description":"Small eyebrow text above the title.","examples":["SESSION · 004"]},"sublabel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sublabel","description":"Secondary line under the title.","examples":["Reviewed 6m ago"]}},"additionalProperties":false,"type":"object","required":["type"],"title":"PageHeaderBlock","description":"Top-of-page banner — title, kicker, and optional subtitle."},"cbweave__PillItem":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier echoed as `data-block-id`."},"label":{"type":"string","title":"Label","description":"Pill text.","default":"","examples":["ANALYZE"]},"active":{"type":"boolean","title":"Active","description":"Whether the pill renders in its selected/active state.","default":false},"tone":{"type":"string","enum":["act","analyze","monitor","system","alert","bone","bone-dim","bone-low"],"title":"Tone","description":"Pill colour tone.","default":"analyze"},"size":{"type":"string","enum":["md","sm","icon"],"title":"Size","description":"Pill size — `md`, `sm`, or `icon`-only.","default":"md"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Icon","description":"Optional CDN icon name rendered inside the pill."}},"additionalProperties":false,"type":"object","title":"PillItem","description":"A single chip inside a `pill-row` block.","examples":[{"active":true,"label":"ANALYZE","tone":"analyze"}]},"cbweave__PillRowBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"pill-row","title":"Type","description":"Discriminator. Always `pill-row`."},"items":{"items":{"$ref":"#/components/schemas/cbweave__PillItem"},"type":"array","title":"Items","description":"Pills rendered left to right."}},"additionalProperties":false,"type":"object","required":["type"],"title":"PillRowBlock","description":"A horizontal row of chips / filter pills (`PillItem`s)."},"cbweave__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbweave__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbweave__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbweave__RenderBlocksRequest":{"properties":{"weave_version":{"type":"string","const":"1","title":"Weave Version","description":"Wire-format version. Currently always `1`.","default":"1"},"surface":{"anyOf":[{"type":"string","enum":["desktop","tablet-landscape","tablet-portrait","phone-portrait","print-letter","tv-1080p","tv-4k","tv-ultrawide"]},{"items":{"type":"string","enum":["desktop","tablet-landscape","tablet-portrait","phone-portrait","print-letter","tv-1080p","tv-4k","tv-ultrawide"]},"type":"array"},{"type":"null"}],"title":"Surface","description":"Target surface profile, or a list of profiles for a multi-surface render. Defaults to `desktop` when omitted."},"fonts":{"type":"string","enum":["reference","embed","outline"],"title":"Fonts","description":"Font portability strategy — `reference`, `embed`, or `outline`.","default":"reference"},"theme":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Theme","description":"Optional token overrides; null uses the default LCARS palette."},"frontmatter":{"anyOf":[{"$ref":"#/components/schemas/cbweave__FrontmatterModel"},{"type":"null"}],"description":"Optional document-level metadata (title/mode/tenant)."},"blocks":{"items":{"oneOf":[{"$ref":"#/components/schemas/cbweave__PageHeaderBlock"},{"$ref":"#/components/schemas/cbweave__PageFooterBlock"},{"$ref":"#/components/schemas/cbweave__HeadlineBlock"},{"$ref":"#/components/schemas/cbweave__StatGridBlock"},{"$ref":"#/components/schemas/cbweave__MeterStackBlock"},{"$ref":"#/components/schemas/cbweave__PillRowBlock"},{"$ref":"#/components/schemas/cbweave__ActionBarBlock"},{"$ref":"#/components/schemas/cbweave__TimeSeriesBlock"},{"$ref":"#/components/schemas/cbweave__NarrativeBlock"},{"$ref":"#/components/schemas/cbweave__CalloutBlock"},{"$ref":"#/components/schemas/cbweave__StatusBlock"}],"discriminator":{"propertyName":"type","mapping":{"action-bar":"#/components/schemas/ActionBarBlock","callout":"#/components/schemas/CalloutBlock","headline":"#/components/schemas/HeadlineBlock","meter-stack":"#/components/schemas/MeterStackBlock","narrative":"#/components/schemas/NarrativeBlock","page-footer":"#/components/schemas/PageFooterBlock","page-header":"#/components/schemas/PageHeaderBlock","pill-row":"#/components/schemas/PillRowBlock","stat-grid":"#/components/schemas/StatGridBlock","status":"#/components/schemas/StatusBlock","time-series":"#/components/schemas/TimeSeriesBlock"}}},"type":"array","title":"Blocks","description":"Ordered list of typed blocks to render, top to bottom."}},"additionalProperties":false,"type":"object","required":["blocks"],"title":"RenderBlocksRequest","description":"Body of `POST /api/v1/render-blocks` — the v1.2 JSON ingest shape.\n\nAn ordered list of typed `blocks` plus rendering options. Targets one\nor more surfaces and returns an always-JSON envelope of per-surface SVGs.","examples":[{"blocks":[{"label":"ACTIVE SESSIONS","type":"headline","value":"12,478"}],"fonts":"reference","surface":"desktop","weave_version":"1"}]},"cbweave__RenderReportRequest":{"properties":{"markdown":{"type":"string","title":"Markdown","description":"Ordinary GitHub-Flavored Markdown — a report. Optional YAML frontmatter (`surface:`) is honored. GFM tables become Weave `table` blocks; headings/prose/code/bullets map to the semantic vocabulary.","examples":["# OH PROD segment — audience profile\n\n_Pre-send profile of the loaded segment._\n\n- **Segment size:** 298,860 · **Donors:** 4,447 (1.5%)\n\n## BriteVerify status\n\nDeliverability classification from the verification table.\n\n| BV status | Count | % of segment |\n|---|--:|--:|\n| valid | 175,334 | 58.7% |\n| (no BV record) | 64,496 | 21.6% |\n| unknown | 34,871 | 11.7% |\n"]},"fonts":{"type":"string","enum":["reference","embed","outline"],"title":"Fonts","description":"Font portability strategy (see `/render` for the trade-offs).","default":"reference"},"enhance":{"type":"boolean","title":"Enhance","description":"Append derived charts after recognizable tables — a distribution (percent column) gains a meter-stack; a multi-window numeric table gains sparklines; a geo-keyed table (state / district GEOID) gains a choropleth shaded from cbdistricts boundaries (cbweave#14). The original table is always kept. Set false for a structural-only render. Frontmatter `enhance: false` also disables it.","default":true},"surface":{"anyOf":[{"type":"string","enum":["document","desktop","tablet-landscape","tablet-portrait","phone-portrait","print-letter","tv-1080p","tv-4k","tv-ultrawide"]},{"type":"null"}],"title":"Surface","description":"Target surface. Defaults to `document` — a fixed-width page (820px) whose height grows to fit the whole report. Pass a device profile to render the report into a fixed page instead (content past the bottom edge clips).","default":"document"},"theme":{"anyOf":[{"type":"string","enum":["apex","broadcast","dark","dark-ga","light"]},{"type":"null"}],"title":"Theme","description":"Color theme; one of the registered names. `dark` (default) is the LCARS palette on a near-black ground; `light` renders dark ink on a parchment ground (for light-mode hosts). Null → dark. Derived from the theme registry, so a new theme needs no endpoint edit."}},"type":"object","required":["markdown"],"title":"RenderReportRequest","description":"Body of `POST /api/v1/render-report`.\n\nPlain GFM markdown in, one SVG out. No directive syntax required —\nheadings, paragraphs, `| … |` tables, fenced code, and `- bullet`\nlists are recognized directly.","examples":[{"fonts":"reference","markdown":"# OH PROD segment — audience profile\n\n_Pre-send profile of the loaded segment._\n\n- **Segment size:** 298,860 · **Donors:** 4,447 (1.5%)\n\n## BriteVerify status\n\nDeliverability classification from the verification table.\n\n| BV status | Count | % of segment |\n|---|--:|--:|\n| valid | 175,334 | 58.7% |\n| (no BV record) | 64,496 | 21.6% |\n| unknown | 34,871 | 11.7% |\n"}]},"cbweave__RenderRequest":{"properties":{"markdown":{"type":"string","title":"Markdown","description":"Markdown source. Optional YAML frontmatter at the top (title, canvas dimensions, mode); body is a tree of directives. See `GET /api/v1/examples` for working samples you can copy from.","examples":["---\ntitle: \"Q3 Pipeline\"\ncanvas: { width: 400, height: 280 }\n---\n\n:::grid{cols=2 gap=s3}\n::tile{label=\"THROUGHPUT\" value=\"847\" unit=\"/sec\" delta=\"+12% ▲\" delta-mode=positive}\n::tile{label=\"LATENCY · P95\" value=\"8.4\" unit=\"ms\" delta=\"−0.3 ▼\" delta-mode=positive}\n::tile{label=\"QUEUE DEPTH\" value=\"12.4k\" delta=\"−2.1k draining\" delta-mode=warning}\n::tile{label=\"ERROR RATE\" value=\"0.04\" unit=\"%\" delta=nominal delta-mode=positive}\n:::\n"]},"fonts":{"type":"string","enum":["reference","embed","outline"],"title":"Fonts","description":"Font portability strategy.\n\n- **reference** (default): emit `<text>` with `font-family`; the host must have JetBrains Mono / IBM Plex Sans installed. Smallest output (~1 KB for a small example).\n- **embed**: inject base64 `@font-face` `<style>`; `<text>` stays. Works in browsers without the fonts locally. Large output (~1 MB for the three bundled TTFs).\n- **outline**: replace every `<text>` with `<path>` glyph outlines via fontTools. No font dependency at render time; byte-identical across hosts. Medium-large output.","default":"reference"},"theme":{"anyOf":[{"type":"string","enum":["apex","broadcast","dark","dark-ga","light"]},{"type":"null"}],"title":"Theme","description":"Color theme; one of the registered names (`dark` is the default). Only the color table changes — layout/geometry are identical. The list is derived from the theme registry, so a newly registered theme is reachable here without an endpoint edit."},"surface":{"anyOf":[{"type":"string","enum":["desktop","tablet-landscape","tablet-portrait","phone-portrait","print-letter","tv-1080p","tv-4k","tv-ultrawide"]},{"type":"null"}],"title":"Surface","description":"Optional surface profile. When set, overrides frontmatter `surface:` per D2 (compile arg wins). Drives canvas viewBox + padding + grammar-fraction slot widths per profile. When unset and frontmatter has no `surface:`, falls back to the legacy `canvas: {w, h}` frontmatter block."}},"type":"object","required":["markdown"],"title":"RenderRequest","description":"Body of `POST /api/v1/render`.\n\nMarkdown is the canonical authoring form — frontmatter for canvas\n+ metadata, then `::name{attrs}` leaves and `:::name{attrs}…:::`\ncontainers. See the **DIRECTIVE-SYNTAX** spec under\n`docs/lcars-svg/lcars-svg/` for the full DSL.","examples":[{"fonts":"reference","markdown":"---\ntitle: \"Q3 Pipeline\"\ncanvas: { width: 400, height: 280 }\n---\n\n:::grid{cols=2 gap=s3}\n::tile{label=\"THROUGHPUT\" value=\"847\" unit=\"/sec\" delta=\"+12% ▲\" delta-mode=positive}\n::tile{label=\"LATENCY · P95\" value=\"8.4\" unit=\"ms\" delta=\"−0.3 ▼\" delta-mode=positive}\n::tile{label=\"QUEUE DEPTH\" value=\"12.4k\" delta=\"−2.1k draining\" delta-mode=warning}\n::tile{label=\"ERROR RATE\" value=\"0.04\" unit=\"%\" delta=nominal delta-mode=positive}\n:::\n"},{"fonts":"outline","markdown":"---\ncanvas: { width: 200, height: 60 }\n---\n\n::kicker[ACTIVE SESSIONS · 24H]\n"}]},"cbweave__RenderStoryRequest":{"properties":{"markdown":{"type":"string","title":"Markdown","description":"Directive markdown with beat attrs. Frontmatter `surface:` picks the canvas (broadcast profiles: `tv-1080p`, `tv-4k`, `tv-ultrawide`); `story: {gap, ease}` sets document timing defaults. Fonts are always outlined (glyphs as `<path>`), so the bundle carries no font dependency.","examples":["---\nsurface: tv-1080p\nstory:\n  ease: out\n  gap: 0.35\n---\n\n::kicker[ELECTION NIGHT]{tone=bone-dim enter=draw dur=1.2}\n::value[IA-03 CALLED]{enter=rise at=\"+0.2\" dur=0.8}\n\n:::stat-grid{cols=3 enter=draw stagger=0.25}\n::stat{label=\"MARGIN\" value=\"+4.1\"}\n::stat{label=\"PRECINCTS\" value=\"98%\"}\n::stat{label=\"TURNOUT\" value=\"61%\"}\n:::\n\n::hold{dur=2.5}\n::narrative{enter=fade dur=0.6 text=\"The suburbs moved eleven points.\"}\n"]},"surface":{"anyOf":[{"type":"string","enum":["desktop","tablet-landscape","tablet-portrait","phone-portrait","print-letter","tv-1080p","tv-4k","tv-ultrawide"]},{"type":"null"}],"title":"Surface","description":"Optional surface profile; overrides frontmatter `surface:` per D2. When unset and frontmatter has none, falls back to `canvas:{w,h}` or a 1280x720 default."},"theme":{"anyOf":[{"type":"string","enum":["apex","broadcast","dark","dark-ga","light"]},{"type":"null"}],"title":"Theme","description":"Color theme; one of the registered names (`dark` is the default). Only the color table changes. Derived from the theme registry, so a new theme needs no endpoint edit."},"final":{"type":"boolean","title":"Final","description":"When true, also compile the static Base-channel final frame and return it as `final_svg` (plus its hash on the bundle). Use it to degrade a story to any non-animated panel with no authoring change.","default":false}},"type":"object","required":["markdown"],"title":"RenderStoryRequest","description":"Body of `POST /api/v1/render/story`.\n\nSame authoring markdown as `/render`, plus **beat attrs** for timing:\n`enter` (`draw|fade|rise|pop`), `exit` (`fade|wipe`), `at` (absolute\nseconds, or a quoted relative offset like `at=\"+0.2\"`), `dur`, `ease`,\n`stagger` (containers), and the `::hold{dur=N}` timeline spacer.","examples":[{"final":true,"markdown":"---\nsurface: tv-1080p\nstory:\n  ease: out\n  gap: 0.35\n---\n\n::kicker[ELECTION NIGHT]{tone=bone-dim enter=draw dur=1.2}\n::value[IA-03 CALLED]{enter=rise at=\"+0.2\" dur=0.8}\n\n:::stat-grid{cols=3 enter=draw stagger=0.25}\n::stat{label=\"MARGIN\" value=\"+4.1\"}\n::stat{label=\"PRECINCTS\" value=\"98%\"}\n::stat{label=\"TURNOUT\" value=\"61%\"}\n:::\n\n::hold{dur=2.5}\n::narrative{enter=fade dur=0.6 text=\"The suburbs moved eleven points.\"}\n"}]},"cbweave__RenderStoryResponse":{"properties":{"bundle":{"additionalProperties":true,"type":"object","title":"Bundle","description":"The scene bundle: `story` (schema version), `surface {width,height}`, `background`, `nodes[]` (id/x/y/w/h/svg/paths), `timeline[]` (t-sorted keyframes), `duration`, and `hash`."},"final_svg":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Final Svg","description":"Static Base-channel final frame (only when `final=true`)."}},"type":"object","required":["bundle"],"title":"RenderStoryResponse","description":"The compiled scene bundle (+ optional static final frame).\n\n**Use these endpoints when...** a host needs an *animated* slide for the\ncbcast Enhanced channel (draw-on strokes, fades, moves), not a static SVG.\nFor a still image use `POST /api/v1/render` instead.\n\n**Response interpretation for hosts/LLMs:** `bundle` is a versioned,\ncontent-hashed object — cache on `bundle.hash`; a repeat compile of the\nsame markdown returns the identical hash (determinism is guaranteed).\n`bundle.duration` is the play length in seconds. `final_svg` is present\nonly when `final=true` was requested."},"cbweave__RenderTemplateRequest":{"properties":{"template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template","description":"Inline directive-markdown template. Uses the normal DSL plus `{{ dotted.path }}` interpolation and `:::each{in=PATH as=VAR}…:::` iteration, bound against `data`."},"template_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Ref","description":"Named template to load from the tenant template store. Not yet available (501) — use `template` for now."},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"JSON payload bound into the template's `{{paths}}` / `:::each`."},"strict":{"type":"boolean","title":"Strict","description":"If true, an unresolved `{{path}}` is a 422 instead of blanking.","default":false},"fonts":{"type":"string","enum":["reference","embed","outline"],"title":"Fonts","description":"Font portability strategy (see `/render` for the trade-offs).","default":"reference"},"theme":{"anyOf":[{"type":"string","enum":["apex","broadcast","dark","dark-ga","light"]},{"type":"null"}],"title":"Theme","description":"Color theme; one of the registered names. Null → dark. Derived from the theme registry, so a new theme needs no endpoint edit."},"surface":{"anyOf":[{"type":"string","enum":["desktop","tablet-landscape","tablet-portrait","phone-portrait","print-letter","tv-1080p","tv-4k","tv-ultrawide"]},{"type":"null"}],"title":"Surface","description":"Optional surface profile. Overrides frontmatter `surface:` when set; falls back to the template's frontmatter / legacy canvas block when null."}},"type":"object","title":"RenderTemplateRequest","description":"Body of ``POST /api/v1/render-template``. Provide exactly one of\n``template`` (inline) or ``template_ref`` (named).","examples":[{"data":{"events":[{"date":"2026-06-09","kind":"primary","state":"SC"}],"today":"2026-06-05"},"fonts":"reference","template":"---\nsurface: tv-1080p\n---\n:::board\n:::left\n::value[CALENDAR · {{today}}]{size=md}\n:::each{in=events as=ev}\n::value[{{ev.state}} {{ev.kind}} · {{ev.date}}]{size=sm}\n:::\n:::\n:::\n","theme":"broadcast"}]},"cbweave__StatGridBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"stat-grid","title":"Type","description":"Discriminator. Always `stat-grid`."},"items":{"items":{"$ref":"#/components/schemas/cbweave__StatItem"},"type":"array","title":"Items","description":"Metrics laid out in the grid."}},"additionalProperties":false,"type":"object","required":["type"],"title":"StatGridBlock","description":"A responsive grid of small labelled metrics (`StatItem`s)."},"cbweave__StatItem":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier echoed as `data-block-id`."},"label":{"type":"string","title":"Label","description":"Metric label / caption.","examples":["THROUGHPUT"]},"value":{"type":"string","title":"Value","description":"Formatted metric value.","examples":["847"]},"unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit","description":"Optional unit suffix shown after the value.","examples":["/sec"]},"delta":{"anyOf":[{"$ref":"#/components/schemas/cbweave__DeltaSpec"},{"type":"null"}],"description":"Optional change indicator for this metric."}},"additionalProperties":false,"type":"object","required":["label","value"],"title":"StatItem","description":"A single labelled metric inside a `stat-grid` block.","examples":[{"delta":{"mode":"positive","value":"+12%"},"label":"THROUGHPUT","unit":"/sec","value":"847"}]},"cbweave__StatusBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"status","title":"Type","description":"Discriminator. Always `status`."},"label":{"type":"string","title":"Label","description":"Status text.","default":"STATUS","examples":["LIVE"]},"tone":{"type":"string","enum":["act","analyze","monitor","system","alert","bone","bone-dim","bone-low"],"title":"Tone","description":"Status colour tone.","default":"monitor"},"pulse":{"type":"boolean","title":"Pulse","description":"Whether the indicator dot animates a pulse.","default":true}},"additionalProperties":false,"type":"object","required":["type"],"title":"StatusBlock","description":"A live status pill with an optional pulsing indicator."},"cbweave__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbweave__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbweave__TimeSeriesBlock":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Optional opaque identifier. When present, renderer emits `data-block-id` on the block's outer `<g>` and stamps `weave.block.id` as an OTel attribute."},"type":{"type":"string","const":"time-series","title":"Type","description":"Discriminator. Always `time-series`."},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Chart caption.","examples":["PIPELINE THROUGHPUT · 30D"]},"delta":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delta","description":"Optional summary delta shown beside the label.","examples":["+18.4%"]},"axis":{"anyOf":[{"$ref":"#/components/schemas/cbweave__AxisLabels"},{"type":"null"}],"description":"Optional left/right x-axis end captions."},"tone":{"type":"string","enum":["act","analyze","monitor","system","alert","bone","bone-dim","bone-low"],"title":"Tone","description":"Line colour tone.","default":"analyze"},"emphasis":{"type":"string","enum":["tail","none"],"title":"Emphasis","description":"Where to emphasize the line — `tail` (default) or `none`.","default":"tail"},"highlight_last":{"type":"boolean","title":"Highlight Last","description":"Whether to mark the final data point with a dot.","default":true},"data":{"items":{"type":"number"},"type":"array","title":"Data","description":"Ordered numeric samples plotted oldest → newest.","examples":[[32,44,38,52,42,58]]}},"additionalProperties":false,"type":"object","required":["type"],"title":"TimeSeriesBlock","description":"A sparkline-style line chart over a numeric series."},"cbweave__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbwebhook__BoldSignWebhookResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Outcome of ingestion.","examples":["received","duplicate"]},"event_id":{"type":"string","title":"Event Id","description":"Deterministic event ID derived from the payload for dedup.","examples":["b3f1c0d2-1a2b-5c6d-8e9f-0a1b2c3d4e5f"]},"message":{"type":"string","title":"Message","description":"Human-readable confirmation message.","examples":["Webhook b3f1c0d2-... received successfully"]}},"type":"object","required":["status","event_id","message"],"title":"BoldSignWebhookResponse","description":"Response returned after receiving a BoldSign webhook.\n\nAcknowledgement returned to BoldSign. `status` is `received` for newly\npersisted events or `duplicate` when the derived event ID was already\nseen.","examples":[{"event_id":"b3f1c0d2-1a2b-5c6d-8e9f-0a1b2c3d4e5f","message":"Webhook b3f1c0d2-... received successfully","status":"received"}]},"cbwebhook__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbwebhook__ConsumerCreateRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Human-readable consumer name (e.g. the downstream service).","examples":["cbpulse","perry-campaign-crm"]},"allowed_form_ids":{"items":{"type":"string"},"type":"array","title":"Allowed Form Ids","description":"Typeform form IDs this consumer is scoped to read.","examples":[["AbC12345","XyZ98765"]]}},"type":"object","required":["name","allowed_form_ids"],"title":"ConsumerCreateRequest","description":"Request body for creating a new API consumer.\n\nDefines a named consumer and the set of Typeform form IDs it is allowed\nto read. Only events whose `form_id` is in `allowed_form_ids` are visible\nto the resulting credential.","examples":[{"allowed_form_ids":["AbC12345","XyZ98765"],"name":"cbpulse"}]},"cbwebhook__ConsumerCreateResponse":{"properties":{"id":{"type":"integer","title":"Id","description":"Auto-assigned consumer record ID.","examples":[1,42]},"name":{"type":"string","title":"Name","description":"Human-readable consumer name.","examples":["cbpulse"]},"api_key":{"type":"string","title":"Api Key","description":"Raw API key — shown ONCE here, never retrievable again. Pass as the `X-API-Key` header on data endpoints.","examples":["cbwh_8s2Kd0fP...Qz9"]},"allowed_form_ids":{"items":{"type":"string"},"type":"array","title":"Allowed Form Ids","description":"Typeform form IDs this consumer is scoped to read.","examples":[["AbC12345","XyZ98765"]]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when the consumer was created.","examples":["2026-06-21T14:30:00Z"]}},"type":"object","required":["id","name","api_key","allowed_form_ids","created_at"],"title":"ConsumerCreateResponse","description":"Response after creating a consumer, includes the raw API key.\n\nThe `api_key` field is the only time the raw, un-hashed key is returned —\nthe service stores only its SHA-256 hash and cannot re-derive it. Capture\nit now or rotate the consumer.","examples":[{"allowed_form_ids":["AbC12345","XyZ98765"],"api_key":"cbwh_8s2Kd0fP...Qz9","created_at":"2026-06-21T14:30:00Z","id":42,"name":"cbpulse"}]},"cbwebhook__ConsumerDeleteResponse":{"properties":{"id":{"type":"integer","title":"Id","description":"Consumer record ID that was deactivated.","examples":[42]},"name":{"type":"string","title":"Name","description":"Human-readable consumer name.","examples":["cbpulse"]},"is_active":{"type":"boolean","title":"Is Active","description":"Active flag after the operation — always false here.","examples":[false]},"message":{"type":"string","title":"Message","description":"Human-readable confirmation of the deactivation.","examples":["Consumer 'cbpulse' has been deactivated"]}},"type":"object","required":["id","name","is_active","message"],"title":"ConsumerDeleteResponse","description":"Response after deactivating a consumer.\n\nReturned by the soft-delete endpoint. The record is retained for audit;\nonly `is_active` flips to false.","examples":[{"id":42,"is_active":false,"message":"Consumer 'cbpulse' has been deactivated","name":"cbpulse"}]},"cbwebhook__ConsumerInfo":{"properties":{"id":{"type":"integer","title":"Id","description":"Consumer record ID.","examples":[42]},"name":{"type":"string","title":"Name","description":"Human-readable consumer name.","examples":["cbpulse"]},"allowed_form_ids":{"items":{"type":"string"},"type":"array","title":"Allowed Form Ids","description":"Typeform form IDs this consumer is scoped to read.","examples":[["AbC12345","XyZ98765"]]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when the consumer was created.","examples":["2026-06-21T14:30:00Z"]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the consumer's API key currently authenticates. A deactivated consumer is preserved but its key is rejected.","examples":[true,false]}},"type":"object","required":["id","name","allowed_form_ids","created_at","is_active"],"title":"ConsumerInfo","description":"Public consumer info (excludes API key hash).\n\nRead-only view of a consumer used in list responses. Never exposes the\nAPI key or its hash.","examples":[{"allowed_form_ids":["AbC12345","XyZ98765"],"created_at":"2026-06-21T14:30:00Z","id":42,"is_active":true,"name":"cbpulse"}]},"cbwebhook__ConsumerListResponse":{"properties":{"consumers":{"items":{"$ref":"#/components/schemas/cbwebhook__ConsumerInfo"},"type":"array","title":"Consumers","description":"All registered API consumers."}},"type":"object","required":["consumers"],"title":"ConsumerListResponse","description":"List of all API consumers.\n\nWraps every registered consumer (active and deactivated) in a single\n`consumers` array.","examples":[{"consumers":[{"allowed_form_ids":["AbC12345"],"created_at":"2026-06-21T14:30:00Z","id":42,"is_active":true,"name":"cbpulse"}]}]},"cbwebhook__EndpointCreateRequest":{"properties":{"tenant":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9][a-z0-9-]*$","title":"Tenant","description":"Tenant slug the endpoint belongs to (cbtenant `slug`).","examples":["victory"]},"slug":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9][a-z0-9-]*$","title":"Slug","description":"Short name for this integration, unique within the tenant.","examples":["text-chat","orders"]},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description","description":"What calls this endpoint, for whoever reads the list later.","examples":["Victory Text chat transcript callbacks"]}},"type":"object","required":["tenant","slug"],"title":"EndpointCreateRequest","description":"Request body for registering a new inbound endpoint.","examples":[{"description":"Victory Text chat transcript callbacks","slug":"text-chat","tenant":"victory"}]},"cbwebhook__EndpointCreateResponse":{"properties":{"id":{"type":"integer","title":"Id","description":"Internal auto-increment row ID.","examples":[1]},"tenant":{"type":"string","title":"Tenant","description":"Tenant slug.","examples":["victory"]},"slug":{"type":"string","title":"Slug","description":"Integration slug.","examples":["text-chat"]},"url":{"type":"string","title":"Url","description":"Full URL the caller POSTs to.","examples":["https://webhook.campaignbrain.dev/api/v1/hooks/t/victory/text-chat"]},"provider_key":{"type":"string","title":"Provider Key","description":"`provider` value events from this endpoint carry in the log.","examples":["t:victory:text-chat"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Free-text note."},"is_active":{"type":"boolean","title":"Is Active","description":"False once deactivated — the token stops authenticating.","examples":[true]},"event_count":{"type":"integer","title":"Event Count","description":"Events received through this endpoint so far.","default":0,"examples":[42]},"last_received_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Received At","description":"UTC timestamp of the most recent delivery."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp of registration."},"token":{"type":"string","title":"Token","description":"Raw token for the X-Webhook-Token header. Shown once.","examples":["cbwt_5Kq2…"]}},"type":"object","required":["id","tenant","slug","url","provider_key","is_active","created_at","token"],"title":"EndpointCreateResponse","description":"Registration response — the ONLY time the token is returned.\n\ncbwebhook stores a SHA-256 hash, so a lost token cannot be recovered; it is\nre-issued by registering a replacement endpoint."},"cbwebhook__EndpointDeleteResponse":{"properties":{"id":{"type":"integer","title":"Id","description":"Endpoint row ID."},"tenant":{"type":"string","title":"Tenant","description":"Tenant slug."},"slug":{"type":"string","title":"Slug","description":"Integration slug."},"is_active":{"type":"boolean","title":"Is Active","description":"Always false in this response."},"message":{"type":"string","title":"Message","description":"Human-readable confirmation."}},"type":"object","required":["id","tenant","slug","is_active","message"],"title":"EndpointDeleteResponse","description":"Deactivation result."},"cbwebhook__EndpointInfo":{"properties":{"id":{"type":"integer","title":"Id","description":"Internal auto-increment row ID.","examples":[1]},"tenant":{"type":"string","title":"Tenant","description":"Tenant slug.","examples":["victory"]},"slug":{"type":"string","title":"Slug","description":"Integration slug.","examples":["text-chat"]},"url":{"type":"string","title":"Url","description":"Full URL the caller POSTs to.","examples":["https://webhook.campaignbrain.dev/api/v1/hooks/t/victory/text-chat"]},"provider_key":{"type":"string","title":"Provider Key","description":"`provider` value events from this endpoint carry in the log.","examples":["t:victory:text-chat"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Free-text note."},"is_active":{"type":"boolean","title":"Is Active","description":"False once deactivated — the token stops authenticating.","examples":[true]},"event_count":{"type":"integer","title":"Event Count","description":"Events received through this endpoint so far.","default":0,"examples":[42]},"last_received_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Received At","description":"UTC timestamp of the most recent delivery."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp of registration."}},"type":"object","required":["id","tenant","slug","url","provider_key","is_active","created_at"],"title":"EndpointInfo","description":"A registered endpoint, without its token."},"cbwebhook__EndpointListResponse":{"properties":{"endpoints":{"items":{"$ref":"#/components/schemas/cbwebhook__EndpointInfo"},"type":"array","title":"Endpoints","description":"Registered endpoints."}},"type":"object","required":["endpoints"],"title":"EndpointListResponse","description":"All registered endpoints, optionally filtered to one tenant."},"cbwebhook__EndpointReceiveResponse":{"properties":{"status":{"type":"string","title":"Status","description":"`received` for a new event, `duplicate` when the event ID was already seen.","examples":["received","duplicate"]},"event_id":{"type":"string","title":"Event Id","description":"Event ID stored for this delivery."},"message":{"type":"string","title":"Message","description":"Human-readable confirmation."}},"type":"object","required":["status","event_id","message"],"title":"EndpointReceiveResponse","description":"Acknowledgement returned to the caller of a registered endpoint."},"cbwebhook__EventDetail":{"properties":{"id":{"type":"integer","title":"Id","description":"Internal auto-increment row ID.","examples":[1024]},"event_id":{"type":"string","title":"Event Id","description":"Stable, provider-derived event ID used for deduplication.","examples":["01HA2B3C4D5E6F7G8H9J0K1L2M"]},"provider":{"type":"string","title":"Provider","description":"Source integration that delivered the event.","examples":["typeform","boldsign","gdrive"]},"event_type":{"type":"string","title":"Event Type","description":"Provider event type string.","examples":["form_response","Completed","pubsub.push"]},"form_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Form Id","description":"Typeform form ID extracted from the payload, if present.","examples":["AbC12345"]},"received_at":{"type":"string","format":"date-time","title":"Received At","description":"UTC timestamp when CBWebhook persisted the event.","examples":["2026-06-21T14:30:00Z"]},"payload":{"additionalProperties":true,"type":"object","title":"Payload","description":"Complete raw provider payload exactly as received.","examples":[{"event_id":"01HA2B3C","event_type":"form_response"}]}},"type":"object","required":["id","event_id","provider","event_type","received_at","payload"],"title":"EventDetail","description":"Full webhook event detail including raw payload.\n\nExtends :class:`EventSummary` with the complete, as-received provider\npayload under `payload`.","examples":[{"event_id":"01HA2B3C4D5E6F7G8H9J0K1L2M","event_type":"form_response","form_id":"AbC12345","id":1024,"provider":"typeform","received_at":"2026-06-21T14:30:00Z"}]},"cbwebhook__EventDetailResponse":{"properties":{"event":{"$ref":"#/components/schemas/cbwebhook__EventDetail","description":"The requested event, including its full raw payload."}},"type":"object","required":["event"],"title":"EventDetailResponse","description":"Single event detail response wrapper."},"cbwebhook__EventListResponse":{"properties":{"events":{"items":{"$ref":"#/components/schemas/cbwebhook__EventSummary"},"type":"array","title":"Events","description":"Event summaries for the current page."},"pagination":{"$ref":"#/components/schemas/cbwebhook__PaginationMeta","description":"Pagination cursor and in-scope total."}},"type":"object","required":["events","pagination"],"title":"EventListResponse","description":"Paginated list of webhook event summaries.","examples":[{"events":[{"event_id":"01HA2B3C4D5E6F7G8H9J0K1L2M","event_type":"form_response","form_id":"AbC12345","id":1024,"provider":"typeform","received_at":"2026-06-21T14:30:00Z"}],"pagination":{"limit":50,"offset":0,"total":1}}]},"cbwebhook__EventSummary":{"properties":{"id":{"type":"integer","title":"Id","description":"Internal auto-increment row ID.","examples":[1024]},"event_id":{"type":"string","title":"Event Id","description":"Stable, provider-derived event ID used for deduplication.","examples":["01HA2B3C4D5E6F7G8H9J0K1L2M"]},"provider":{"type":"string","title":"Provider","description":"Source integration that delivered the event.","examples":["typeform","boldsign","gdrive"]},"event_type":{"type":"string","title":"Event Type","description":"Provider event type string.","examples":["form_response","Completed","pubsub.push"]},"form_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Form Id","description":"Typeform form ID extracted from the payload, if present.","examples":["AbC12345"]},"received_at":{"type":"string","format":"date-time","title":"Received At","description":"UTC timestamp when CBWebhook persisted the event.","examples":["2026-06-21T14:30:00Z"]}},"type":"object","required":["id","event_id","provider","event_type","received_at"],"title":"EventSummary","description":"Summary of a webhook event without full payload.\n\nLightweight projection used in list responses — identifiers, provider,\ntype, and timestamps but not the raw payload body.","examples":[{"event_id":"01HA2B3C4D5E6F7G8H9J0K1L2M","event_type":"form_response","form_id":"AbC12345","id":1024,"provider":"typeform","received_at":"2026-06-21T14:30:00Z"}]},"cbwebhook__FlattenedAnswer":{"properties":{"field_id":{"type":"string","title":"Field Id","description":"Typeform field ID the answer belongs to.","examples":["nELEZ1L8wByj"]},"field_title":{"type":"string","title":"Field Title","description":"Human-readable field title (question text).","examples":["What is your email?"]},"field_type":{"type":"string","title":"Field Type","description":"Typeform field/answer type.","examples":["email","short_text","multiple_choice"]},"value":{"title":"Value","description":"Extracted answer value — type depends on `field_type` (string, number, boolean, or list of choice labels).","examples":["volunteer@example.com",42,["Phone","Email"]]}},"type":"object","required":["field_id","field_title","field_type"],"title":"FlattenedAnswer","description":"Single answer extracted and flattened from a Typeform response.\n\nOne field/value pair lifted out of the nested Typeform answer structure\ninto a flat shape that's trivial to map into a downstream table.","examples":[{"field_id":"nELEZ1L8wByj","field_title":"What is your email?","field_type":"email","value":"volunteer@example.com"}]},"cbwebhook__FlattenedResponse":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"Originating webhook event ID.","examples":["01HA2B3C4D5E6F7G8H9J0K1L2M"]},"token":{"type":"string","title":"Token","description":"Typeform response token (unique per submission).","examples":["a3f9c0d1e2b4"]},"submitted_at":{"type":"string","title":"Submitted At","description":"Submission timestamp as reported by Typeform (ISO 8601).","examples":["2026-06-21T14:29:55Z"]},"answers":{"items":{"$ref":"#/components/schemas/cbwebhook__FlattenedAnswer"},"type":"array","title":"Answers","description":"Flattened answers for this submission."}},"type":"object","required":["event_id","token","submitted_at","answers"],"title":"FlattenedResponse","description":"Flattened form response with extracted answer values.\n\nA single submission, identified by its event and Typeform token, with its\nanswers projected into a flat `FlattenedAnswer` list.","examples":[{"answers":[{"field_id":"nELEZ1L8wByj","field_title":"What is your email?","field_type":"email","value":"volunteer@example.com"}],"event_id":"01HA2B3C4D5E6F7G8H9J0K1L2M","submitted_at":"2026-06-21T14:29:55Z","token":"a3f9c0d1e2b4"}]},"cbwebhook__FormInfo":{"properties":{"form_id":{"type":"string","title":"Form Id","description":"Typeform form ID.","examples":["AbC12345"]},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Form title from the Typeform definition, if available.","examples":["Volunteer Sign-Up"]},"event_count":{"type":"integer","title":"Event Count","description":"Number of in-scope events received for this form.","examples":[42]},"latest_received_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Latest Received At","description":"UTC timestamp of the most recent event for this form.","examples":["2026-06-21T14:30:00Z"]}},"type":"object","required":["form_id","event_count"],"title":"FormInfo","description":"Form metadata with event count and latest timestamp.\n\nSummarizes one Typeform form the consumer can access: its ID, title (when\nderivable from a payload definition), how many events it has produced, and\nwhen the most recent one arrived.","examples":[{"event_count":42,"form_id":"AbC12345","latest_received_at":"2026-06-21T14:30:00Z","title":"Volunteer Sign-Up"}]},"cbwebhook__FormListResponse":{"properties":{"forms":{"items":{"$ref":"#/components/schemas/cbwebhook__FormInfo"},"type":"array","title":"Forms","description":"Forms within the consumer's scope that have received events."}},"type":"object","required":["forms"],"title":"FormListResponse","description":"List of accessible forms for the authenticated consumer.","examples":[{"forms":[{"event_count":42,"form_id":"AbC12345","latest_received_at":"2026-06-21T14:30:00Z","title":"Volunteer Sign-Up"}]}]},"cbwebhook__FormResponsesResponse":{"properties":{"form_id":{"type":"string","title":"Form Id","description":"Form ID the responses belong to.","examples":["AbC12345"]},"responses":{"items":{"$ref":"#/components/schemas/cbwebhook__FlattenedResponse"},"type":"array","title":"Responses","description":"Flattened responses for the current page."},"pagination":{"$ref":"#/components/schemas/cbwebhook__PaginationMeta","description":"Pagination cursor and in-scope total."}},"type":"object","required":["form_id","responses","pagination"],"title":"FormResponsesResponse","description":"Paginated list of flattened form responses.","examples":[{"form_id":"AbC12345","pagination":{"limit":50,"offset":0,"total":1},"responses":[{"answers":[{"field_id":"nELEZ1L8wByj","field_title":"What is your email?","field_type":"email","value":"volunteer@example.com"}],"event_id":"01HA2B3C4D5E6F7G8H9J0K1L2M","submitted_at":"2026-06-21T14:29:55Z","token":"a3f9c0d1e2b4"}]}]},"cbwebhook__GDriveWebhookResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Outcome of ingestion.","examples":["received","duplicate"]},"event_id":{"type":"string","title":"Event Id","description":"Deterministic event ID derived from the Pub/Sub messageId.","examples":["c4f2d1e3-2b3c-5d6e-9f0a-1b2c3d4e5f6a"]},"message_id":{"type":"string","title":"Message Id","description":"Echoed Pub/Sub message ID.","examples":["11920979543"]}},"type":"object","required":["status","event_id","message_id"],"title":"GDriveWebhookResponse","description":"Response returned after handling a GDrive Pub/Sub push.\n\nAcknowledgement returned to Pub/Sub. `status` is `received` once the event\nis forwarded and persisted, or `duplicate` when the `messageId` was\nalready seen. A non-2xx (raised elsewhere) causes Pub/Sub to retry.","examples":[{"event_id":"c4f2d1e3-2b3c-5d6e-9f0a-1b2c3d4e5f6a","message_id":"11920979543","status":"received"}]},"cbwebhook__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbwebhook__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbwebhook__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbwebhook__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbwebhook__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbwebhook__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbwebhook__IterableWebhookResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Overall outcome: `received` when at least one event was persisted, otherwise `ignored`.","examples":["received","ignored"]},"accepted":{"type":"integer","title":"Accepted","description":"Click events persisted and queued for mesh broadcast.","examples":[1]},"ignored":{"type":"integer","title":"Ignored","description":"Events skipped because their eventName is not processed (anything other than emailClick).","examples":[0]},"duplicates":{"type":"integer","title":"Duplicates","description":"Events skipped because an identical event was already persisted (Iterable redelivery).","examples":[0]}},"type":"object","required":["status","accepted","ignored","duplicates"],"title":"IterableWebhookResponse","description":"Acknowledgement returned to Iterable for a webhook delivery.\n\nAlways paired with HTTP 200 once authentication passes — Iterable\nretries non-2xx responses, and a retry can't fix a payload we chose to\nignore. The counters say what happened to the delivered event(s).","example":{"accepted":1,"duplicates":0,"ignored":0,"status":"received"}},"cbwebhook__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbwebhook__PaginationMeta":{"properties":{"offset":{"type":"integer","title":"Offset","description":"Zero-based index of the first item in this page.","examples":[0,50]},"limit":{"type":"integer","title":"Limit","description":"Maximum number of items returned in this page.","examples":[50,100]},"total":{"type":"integer","title":"Total","description":"Total in-scope records matching the query (across all pages).","examples":[137]}},"type":"object","required":["offset","limit","total"],"title":"PaginationMeta","description":"Pagination metadata for list responses.\n\nOffset/limit cursor describing the current page and the total number of\nin-scope records (the total is the count *after* scope filtering).","examples":[{"limit":50,"offset":0,"total":137}]},"cbwebhook__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbwebhook__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbwebhook__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbwebhook__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbwebhook__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbwebhook__TypeformWebhookResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Outcome of ingestion.","examples":["received","duplicate"]},"event_id":{"type":"string","title":"Event Id","description":"Echoed Typeform event ID.","examples":["01HA2B3C4D5E6F7G8H9J0K1L2M"]},"message":{"type":"string","title":"Message","description":"Human-readable confirmation message.","examples":["Webhook 01HA2B3C... received successfully"]}},"type":"object","required":["status","event_id","message"],"title":"TypeformWebhookResponse","description":"Response returned after receiving a Typeform webhook.\n\nAcknowledgement returned to Typeform. `status` is `received` for newly\npersisted events or `duplicate` when the `event_id` was already seen.","examples":[{"event_id":"01HA2B3C4D5E6F7G8H9J0K1L2M","message":"Webhook 01HA2B3C... received successfully","status":"received"}]},"cbwebhook__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbworkflow__ActionConfigField":{"properties":{"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"field_type":{"$ref":"#/components/schemas/cbworkflow__FieldType","default":"text"},"required":{"type":"boolean","title":"Required","default":true},"default":{"title":"Default"},"help_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help Text"},"placeholder":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Placeholder"},"options":{"anyOf":[{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array"},{"type":"null"}],"title":"Options"},"template_vars":{"type":"boolean","title":"Template Vars","default":false}},"type":"object","required":["name","label"],"title":"ActionConfigField","description":"Describes a configuration parameter for an action."},"cbworkflow__ActionExecuteRequest":{"properties":{"action":{"type":"string","title":"Action"},"connector_type":{"$ref":"#/components/schemas/cbworkflow__ConnectorType"},"instance_id":{"type":"string","title":"Instance Id"},"contact_id":{"type":"string","title":"Contact Id"},"node_id":{"type":"string","title":"Node Id"},"config":{"additionalProperties":true,"type":"object","title":"Config"},"contact_data":{"additionalProperties":true,"type":"object","title":"Contact Data"},"instance_state":{"additionalProperties":true,"type":"object","title":"Instance State"},"async_mode":{"type":"boolean","title":"Async Mode","default":false}},"type":"object","required":["action","connector_type","instance_id","contact_id","node_id"],"title":"ActionExecuteRequest","description":"Request to execute an action."},"cbworkflow__ActionExecuteResponse":{"properties":{"request_id":{"type":"string","title":"Request Id"},"status":{"type":"string","title":"Status"},"outcome":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Outcome"},"state_updates":{"additionalProperties":true,"type":"object","title":"State Updates"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["request_id","status"],"title":"ActionExecuteResponse","description":"Response from action execution."},"cbworkflow__ActionResult":{"properties":{"request_id":{"type":"string","title":"Request Id"},"status":{"$ref":"#/components/schemas/cbworkflow__ActionResultStatus"},"outcome":{"type":"string","title":"Outcome"},"state_updates":{"additionalProperties":true,"type":"object","title":"State Updates"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"},"completed_at":{"type":"string","format":"date-time","title":"Completed At"}},"type":"object","required":["request_id","status","outcome"],"title":"ActionResult","description":"Result of an action execution."},"cbworkflow__ActionResultStatus":{"type":"string","enum":["success","failure","pending","deferred"],"title":"ActionResultStatus","description":"Status of an action execution."},"cbworkflow__ActionSpec":{"properties":{"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"config_fields":{"items":{"$ref":"#/components/schemas/cbworkflow__ActionConfigField"},"type":"array","title":"Config Fields"},"outcomes":{"items":{"type":"string"},"type":"array","title":"Outcomes"}},"type":"object","required":["name","label"],"title":"ActionSpec","description":"Describes an action that a connector can perform."},"cbworkflow__CleanupResponse":{"properties":{"message":{"type":"string","title":"Message"},"removed":{"type":"integer","title":"Removed"}},"type":"object","required":["message","removed"],"title":"CleanupResponse","description":"Response for cleanup operation."},"cbworkflow__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbworkflow__ConnectorCapability":{"properties":{"actions":{"items":{"type":"string"},"type":"array","title":"Actions"},"supports_webhooks":{"type":"boolean","title":"Supports Webhooks","default":false},"supports_batch":{"type":"boolean","title":"Supports Batch","default":false},"max_batch_size":{"type":"integer","title":"Max Batch Size","default":100},"rate_limit_per_second":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate Limit Per Second"}},"type":"object","required":["actions"],"title":"ConnectorCapability","description":"Describes what a connector can do."},"cbworkflow__ConnectorHealth":{"properties":{"status":{"$ref":"#/components/schemas/cbworkflow__ConnectorStatus"},"last_check":{"type":"string","format":"date-time","title":"Last Check"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"metrics":{"additionalProperties":true,"type":"object","title":"Metrics"}},"type":"object","required":["status"],"title":"ConnectorHealth","description":"Health status report from a connector."},"cbworkflow__ConnectorInfo":{"properties":{"connector_type":{"$ref":"#/components/schemas/cbworkflow__ConnectorType"},"name":{"type":"string","title":"Name"},"capabilities":{"$ref":"#/components/schemas/cbworkflow__ConnectorCapability"},"health":{"$ref":"#/components/schemas/cbworkflow__ConnectorHealth"},"registered_at":{"type":"string","format":"date-time","title":"Registered At"},"last_health_check":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Health Check"}},"type":"object","required":["connector_type","name","capabilities"],"title":"ConnectorInfo","description":"Information about a registered connector."},"cbworkflow__ConnectorListResponse":{"properties":{"connectors":{"items":{"$ref":"#/components/schemas/cbworkflow__ConnectorInfo"},"type":"array","title":"Connectors"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["connectors","total"],"title":"ConnectorListResponse","description":"Response for listing connectors."},"cbworkflow__ConnectorSpec":{"properties":{"connector_type":{"$ref":"#/components/schemas/cbworkflow__ConnectorType"},"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"icon":{"type":"string","title":"Icon","default":"plug"},"color":{"type":"string","title":"Color","default":"gray"},"credential_schema":{"$ref":"#/components/schemas/cbworkflow__CredentialSchema"},"actions":{"items":{"$ref":"#/components/schemas/cbworkflow__ActionSpec"},"type":"array","title":"Actions"},"supports_webhooks":{"type":"boolean","title":"Supports Webhooks","default":false},"supports_batch":{"type":"boolean","title":"Supports Batch","default":false},"max_batch_size":{"type":"integer","title":"Max Batch Size","default":100},"rate_limit_per_second":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate Limit Per Second"},"test_connection_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Test Connection Action"}},"type":"object","required":["connector_type","name","description","credential_schema"],"title":"ConnectorSpec","description":"Full specification for a connector - used by UI and workflow engine."},"cbworkflow__ConnectorStatus":{"type":"string","enum":["healthy","degraded","unhealthy","unknown"],"title":"ConnectorStatus","description":"Health status of a connector."},"cbworkflow__ConnectorType":{"type":"string","enum":["email","sms","voice","survey","mail","door","ai","intel","mailchimp_transactional","i360","i360_advanced"],"title":"ConnectorType","description":"Types of connectors."},"cbworkflow__Contact":{"properties":{"id":{"type":"string","title":"Id","description":"Unique 12-character hexadecimal identifier","examples":["a1b2c3d4e5f6"]},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant organization ID for data isolation. Contacts are scoped to their tenant.","default":"default","examples":["tenant-abc123","default"]},"name":{"type":"string","title":"Name","description":"Full name of the contact","examples":["Jane Smith","John Doe"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Email address (validated format). Used for email outreach workflows.","examples":["jane.smith@example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Phone number in E.164 or local format. Used for SMS/call workflows.","examples":["+15551234567","555-123-4567"]},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County","description":"Geographic county/region for voter targeting and geographic filtering","examples":["Harris County","Los Angeles"]},"organization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization","description":"Associated organization, employer, or affiliation","examples":["Acme Corp","Local Union 123"]},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Job title or role within organization","examples":["Regional Director","Volunteer Coordinator"]},"workflow_instance_ids":{"items":{"type":"string"},"type":"array","title":"Workflow Instance Ids","description":"IDs of active workflow instances attached to this contact. Non-empty means contact is being actively worked.","examples":[["inst-abc123","inst-def456"]]},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Labels for filtering and segmentation (e.g., 'supporter', 'volunteer', 'donor', 'priority')","examples":[["volunteer","district-5","phone-banked"]]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Flexible key-value data storage for campaign-specific fields","examples":[{"last_canvass_date":"2024-01-15","precinct":"PCT-42","voter_id":"V12345"}]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the contact was created (UTC)"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Timestamp when the contact was last modified (UTC)"}},"type":"object","required":["name"],"title":"Contact","description":"A contact in the CRM system.\n\nContacts represent individuals being managed through workflow processes.\nEach contact can have multiple active workflow instances and can be\nfiltered/segmented using tags and metadata.\n\n---\n\n### Response Interpretation for LLMs\n\n**Key indicators:**\n- `workflow_instance_ids`: Non-empty list = contact has active workflows in progress\n- `tags`: Use for filtering/segmentation (\"supporter\", \"volunteer\", \"donor\", \"priority\")\n- `metadata`: Flexible storage - check keys based on campaign context\n\n**Common patterns:**\n```python\nif contact.workflow_instance_ids:\n    # Contact is being actively worked\n    return f\"Contact has {len(contact.workflow_instance_ids)} active workflows\"\nif \"volunteer\" in contact.tags:\n    return \"Contact is a registered volunteer\"\n```","examples":[{"county":"Harris County","created_at":"2024-01-15T10:30:00Z","email":"jane.smith@example.com","id":"a1b2c3d4e5f6","metadata":{"voter_id":"V12345"},"name":"Jane Smith","organization":"Acme Corp","phone":"+15551234567","role":"Regional Director","tags":["volunteer","priority"],"tenant_id":"tenant-abc123","updated_at":"2024-01-16T14:45:00Z","workflow_instance_ids":["inst-abc123"]}]},"cbworkflow__ContactCreate":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant organization ID. Defaults to 'default' for single-tenant usage.","default":"default","examples":["tenant-abc123"]},"name":{"type":"string","title":"Name","description":"Full name of the contact (required)","examples":["Jane Smith"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Email address for email outreach workflows","examples":["jane.smith@example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Phone number for SMS/call workflows","examples":["+15551234567"]},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County","description":"Geographic county/region","examples":["Harris County"]},"organization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization","description":"Organization or employer","examples":["Acme Corp"]},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Job title or role","examples":["Regional Director"]},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Initial tags for segmentation","examples":[["volunteer","priority"]]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Custom key-value data","examples":[{"voter_id":"V12345"}]}},"type":"object","required":["name"],"title":"ContactCreate","description":"Schema for creating a new contact.\n\nProvide at minimum a `name`. All other fields are optional but\nrecommended for full workflow functionality.","examples":[{"email":"jane.smith@example.com","name":"Jane Smith","phone":"+15551234567","tags":["volunteer"]}]},"cbworkflow__ContactUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Updated full name","examples":["Jane A. Smith"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Updated email address","examples":["jane.a.smith@example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Updated phone number","examples":["+15559876543"]},"county":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"County","description":"Updated county/region","examples":["Dallas County"]},"organization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization","description":"Updated organization","examples":["New Corp"]},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"Updated role/title","examples":["Senior Director"]},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"Replace all tags with this list","examples":[["volunteer","priority","contacted"]]},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Replace all metadata with this dict","examples":[{"notes":"Very engaged","voter_id":"V12345"}]}},"type":"object","title":"ContactUpdate","description":"Schema for updating an existing contact.\n\nAll fields are optional. Only provided fields will be updated.\nTo clear a field, set it to null explicitly.","examples":[{"name":"Jane A. Smith","tags":["volunteer","priority","contacted"]}]},"cbworkflow__CredentialCreateRequest":{"properties":{"connector_type":{"$ref":"#/components/schemas/cbworkflow__ConnectorType","description":"Type of connector this credential is for"},"credential_type":{"$ref":"#/components/schemas/cbworkflow__CredentialType","description":"Authentication type"},"label":{"type":"string","title":"Label","description":"Human-readable label","default":"Default","examples":["Production Twilio"]},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Credential data to encrypt. Structure depends on credential_type.","examples":[{"api_key":"sk_live_abc123..."},{"password":"pass","username":"user"}]},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"For OAuth: when the token expires"}},"type":"object","required":["connector_type","credential_type","data"],"title":"CredentialCreateRequest","description":"Request model for creating a credential.\n\nProvide the connector type, credential type, and the actual\ncredential data. The data will be encrypted before storage.","examples":[{"connector_type":"sms","credential_type":"api_key","data":{"api_key":"sk_live_abc123..."},"label":"Production Twilio"}]},"cbworkflow__CredentialField":{"properties":{"name":{"type":"string","title":"Name"},"label":{"type":"string","title":"Label"},"field_type":{"$ref":"#/components/schemas/cbworkflow__FieldType","default":"text"},"required":{"type":"boolean","title":"Required","default":true},"help_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help Text"},"placeholder":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Placeholder"},"options":{"anyOf":[{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array"},{"type":"null"}],"title":"Options"}},"type":"object","required":["name","label"],"title":"CredentialField","description":"Describes a single credential input field."},"cbworkflow__CredentialSchema":{"properties":{"type":{"type":"string","title":"Type"},"fields":{"items":{"$ref":"#/components/schemas/cbworkflow__CredentialField"},"type":"array","title":"Fields"},"help_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help Url"},"test_on_save":{"type":"boolean","title":"Test On Save","default":true}},"type":"object","required":["type"],"title":"CredentialSchema","description":"Describes what credentials a connector needs."},"cbworkflow__CredentialSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Credential identifier","examples":["cred-abc12345"]},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant this credential belongs to","examples":["tenant-abc12345"]},"connector_type":{"$ref":"#/components/schemas/cbworkflow__ConnectorType","description":"Connector type"},"credential_type":{"$ref":"#/components/schemas/cbworkflow__CredentialType","description":"Credential type"},"label":{"type":"string","title":"Label","description":"Human-readable label","examples":["Production Twilio"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When created"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When last modified"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When credential expires (OAuth only)"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"When last used"},"use_count":{"type":"integer","title":"Use Count","description":"Usage count"}},"type":"object","required":["id","tenant_id","connector_type","credential_type","label","created_at","updated_at","expires_at","last_used_at","use_count"],"title":"CredentialSummary","description":"Public credential info (no secrets exposed).\n\nThis is the model returned by API endpoints. It includes all\nmetadata but excludes the actual credential values.","examples":[{"connector_type":"sms","created_at":"2024-01-15T10:30:00Z","credential_type":"api_key","id":"cred-abc12345","label":"Production Twilio","last_used_at":"2024-01-16T14:00:00Z","tenant_id":"tenant-abc12345","updated_at":"2024-01-15T10:30:00Z","use_count":42}]},"cbworkflow__CredentialType":{"type":"string","enum":["api_key","oauth2","basic_auth"],"title":"CredentialType","description":"Types of credentials supported for connector authentication.\n\n- **API_KEY**: Simple API key authentication (most common)\n- **OAUTH2**: OAuth 2.0 tokens with refresh capability\n- **BASIC_AUTH**: Username/password combination"},"cbworkflow__DeadLetterActionResponse":{"properties":{"message":{"type":"string","title":"Message"},"dead_letter":{"$ref":"#/components/schemas/cbworkflow__DeadLetterMessage"}},"type":"object","required":["message","dead_letter"],"title":"DeadLetterActionResponse","description":"Response for dead letter actions."},"cbworkflow__DeadLetterDeleteResponse":{"properties":{"message":{"type":"string","title":"Message"},"deleted":{"type":"boolean","title":"Deleted"}},"type":"object","required":["message","deleted"],"title":"DeadLetterDeleteResponse","description":"Response for dead letter deletion."},"cbworkflow__DeadLetterDiscardRequest":{"properties":{"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Optional notes about why the message was discarded","examples":["Spam message - promotional content"]}},"type":"object","title":"DeadLetterDiscardRequest","description":"Request to discard a dead letter message.","examples":[{"notes":"Spam message"}]},"cbworkflow__DeadLetterMessage":{"properties":{"id":{"type":"string","title":"Id","description":"Unique dead letter identifier","examples":["dl-abc12345"]},"sender_phone":{"type":"string","title":"Sender Phone","description":"Original sender phone number","examples":["+15551234567"]},"normalized_sender":{"type":"string","title":"Normalized Sender","description":"Normalized phone number (digits only)","examples":["5551234567"]},"message":{"type":"string","title":"Message","description":"SMS message content","examples":["Yes, I'm interested!"]},"received_at":{"type":"string","format":"date-time","title":"Received At","description":"When the message was originally received"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the dead letter entry was created"},"reason":{"$ref":"#/components/schemas/cbworkflow__DeadLetterReason","description":"Why the message couldn't be routed"},"status":{"$ref":"#/components/schemas/cbworkflow__DeadLetterStatus","description":"Current status of the dead letter","default":"pending"},"resolved_tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved Tenant Id","description":"Tenant ID if manually assigned","examples":["tenant-abc12345"]},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At","description":"When the dead letter was resolved"},"resolved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved By","description":"User or system that resolved the dead letter","examples":["admin@example.com","system"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Admin notes about the resolution","examples":["Assigned to Acme Campaign - confirmed as their contact"]},"raw_data":{"additionalProperties":true,"type":"object","title":"Raw Data","description":"Original message data from the gateway","examples":[{"num_segments":1,"twilio_sid":"SM123..."}]}},"type":"object","required":["sender_phone","normalized_sender","message","received_at","reason"],"title":"DeadLetterMessage","description":"An SMS message that could not be routed to a tenant.\n\nStored in the dead letter queue for manual review and resolution.\nAdmins can either assign it to a tenant or discard it as spam.\n\n---\n\n### Response Interpretation for LLMs\n\n- `status == \"pending\"`: Needs admin attention\n- `reason`: Why routing failed (helps determine resolution)\n- `resolved_tenant_id`: Set after manual assignment","examples":[{"id":"dl-abc12345","message":"Yes, I'm interested!","normalized_sender":"5551234567","reason":"no_routing_match","received_at":"2024-01-15T10:35:00Z","sender_phone":"+15551234567","status":"pending"}]},"cbworkflow__DeadLetterReason":{"type":"string","enum":["no_routing_match","tenant_not_verified","tenant_disabled","tenant_not_found","processing_error"],"title":"DeadLetterReason","description":"Reason why an SMS message was sent to the dead letter queue.\n\n---\n\n### Reasons Explained\n\n- **NO_ROUTING_MATCH**: Sender not in routing table (never received outbound SMS)\n- **TENANT_NOT_VERIFIED**: Tenant found but SMS not verified\n- **TENANT_DISABLED**: Tenant's SMS explicitly disabled\n- **TENANT_NOT_FOUND**: Routing entry points to deleted/invalid tenant\n- **PROCESSING_ERROR**: Unexpected error during message processing"},"cbworkflow__DeadLetterResolveRequest":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant ID to assign this message to","examples":["tenant-abc12345"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Optional notes about the resolution","examples":["Confirmed as Acme Campaign contact via phone lookup"]}},"type":"object","required":["tenant_id"],"title":"DeadLetterResolveRequest","description":"Request to resolve a dead letter message by assigning to a tenant.","examples":[{"notes":"Confirmed as Acme Campaign contact","tenant_id":"tenant-abc12345"}]},"cbworkflow__DeadLetterStatus":{"type":"string","enum":["pending","resolved","discarded"],"title":"DeadLetterStatus","description":"Status of a dead letter message.\n\n---\n\n### Status Lifecycle\n\n```\nPENDING → RESOLVED (assigned to tenant)\n    ↓\nDISCARDED (marked as spam/irrelevant)\n```"},"cbworkflow__DeadLetterSummary":{"properties":{"total":{"type":"integer","title":"Total","description":"Total number of dead letter messages","default":0},"pending":{"type":"integer","title":"Pending","description":"Messages awaiting review","default":0},"resolved":{"type":"integer","title":"Resolved","description":"Messages assigned to tenants","default":0},"discarded":{"type":"integer","title":"Discarded","description":"Messages marked as spam/irrelevant","default":0},"by_reason":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Reason","description":"Count of messages by reason code","examples":[{"no_routing_match":15,"tenant_not_verified":3}]}},"type":"object","title":"DeadLetterSummary","description":"Summary statistics for dead letters.\n\nProvides an overview of the dead letter queue for monitoring dashboards.","examples":[{"by_reason":{"no_routing_match":20,"tenant_not_verified":5},"discarded":3,"pending":7,"resolved":15,"total":25}]},"cbworkflow__DecisionRule":{"properties":{"type":{"$ref":"#/components/schemas/cbworkflow__DecisionRuleType","description":"Type of decision rule: 'heuristic' (code), 'ai' (Claude), or 'manual' (user input)"},"expression":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expression","description":"Python expression for heuristic rules. Evaluated against instance state. Example: 'state.attempts >= 3'","examples":["state.score > 80","state.response == 'yes'","len(state.notes) > 0"]},"prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt","description":"Prompt template for AI rules. Use {state.xxx} placeholders for state values.","examples":["Based on the response '{state.response}', is this person interested? Answer: yes/no"]},"user_prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Prompt","description":"Prompt text shown to user for manual decisions","examples":["Did the contact answer the phone?","Select the outcome of the conversation"]}},"type":"object","required":["type"],"title":"DecisionRule","description":"Rule for evaluating a decision point in a workflow.\n\nDefines how to determine which transition to take at a DECISION node.\nExactly one of `expression`, `prompt`, or `user_prompt` should be set\nbased on the rule type.","examples":[{"expression":"state.attempts >= 3","type":"heuristic"},{"prompt":"Is {state.response} positive? Answer: yes/no","type":"ai"},{"type":"manual","user_prompt":"Did the contact answer?"}]},"cbworkflow__DecisionRuleType":{"type":"string","enum":["heuristic","ai","manual"],"title":"DecisionRuleType","description":"Types of decision rules for branching logic.\n\n- **HEURISTIC**: Python expression evaluated against instance state (e.g., `state.score > 80`)\n- **AI**: Claude API call with prompt template using `{state.xxx}` placeholders\n- **MANUAL**: Waits for user input to select an outcome\n\n---\n\n### Usage Examples\n\n**Heuristic rule:**\n```json\n{\"type\": \"heuristic\", \"expression\": \"state.attempts >= 3\"}\n```\n\n**AI rule:**\n```json\n{\"type\": \"ai\", \"prompt\": \"Based on {state.response}, is this person interested? Answer: yes/no\"}\n```\n\n**Manual rule:**\n```json\n{\"type\": \"manual\", \"user_prompt\": \"Did the contact answer the phone?\"}\n```"},"cbworkflow__EmailCleanupResponse":{"properties":{"message":{"type":"string","title":"Message"},"removed":{"type":"integer","title":"Removed"}},"type":"object","required":["message","removed"],"title":"EmailCleanupResponse","description":"Response for cleanup operation."},"cbworkflow__EmailDeadLetterActionResponse":{"properties":{"message":{"type":"string","title":"Message"},"dead_letter":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterMessage"}},"type":"object","required":["message","dead_letter"],"title":"EmailDeadLetterActionResponse","description":"Response for email dead letter actions."},"cbworkflow__EmailDeadLetterDeleteResponse":{"properties":{"message":{"type":"string","title":"Message"},"deleted":{"type":"boolean","title":"Deleted"}},"type":"object","required":["message","deleted"],"title":"EmailDeadLetterDeleteResponse","description":"Response for email dead letter deletion."},"cbworkflow__EmailDeadLetterDiscardRequest":{"properties":{"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Optional notes about why the email was discarded","examples":["Spam email - promotional newsletter"]}},"type":"object","title":"EmailDeadLetterDiscardRequest","description":"Request to discard an email dead letter message.","examples":[{"notes":"Spam email - promotional newsletter"}]},"cbworkflow__EmailDeadLetterMessage":{"properties":{"id":{"type":"string","title":"Id","description":"Unique email dead letter identifier","examples":["edl-abc12345"]},"sender_email":{"type":"string","title":"Sender Email","description":"Original sender email address","examples":["voter@example.com"]},"normalized_sender":{"type":"string","title":"Normalized Sender","description":"Normalized email address (lowercase, trimmed)","examples":["voter@example.com"]},"subject":{"type":"string","title":"Subject","description":"Email subject line","examples":["Re: Volunteer Opportunity"]},"body_preview":{"type":"string","title":"Body Preview","description":"First 500 characters of email body","examples":["Thank you for reaching out! I would love to help with..."]},"received_at":{"type":"string","format":"date-time","title":"Received At","description":"When the email was originally received"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the dead letter entry was created"},"reason":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterReason","description":"Why the email couldn't be routed"},"status":{"$ref":"#/components/schemas/cbworkflow__EmailDeadLetterStatus","description":"Current status of the dead letter","default":"pending"},"resolved_tenant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved Tenant Id","description":"Tenant ID if manually assigned","examples":["tenant-abc12345"]},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At","description":"When the dead letter was resolved"},"resolved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved By","description":"User or system that resolved the dead letter","examples":["admin@example.com","system"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Admin notes about the resolution","examples":["Assigned to Acme Campaign - confirmed as their contact"]},"raw_data":{"additionalProperties":true,"type":"object","title":"Raw Data","description":"Original message data including full body, headers, attachments info","examples":[{"has_attachments":false,"message_id":"<abc123@mail.example.com>"}]}},"type":"object","required":["sender_email","normalized_sender","subject","body_preview","received_at","reason"],"title":"EmailDeadLetterMessage","description":"An email message that could not be routed to a tenant.\n\nStored in the dead letter queue for manual review and resolution.\nAdmins can either assign it to a tenant or discard it as spam.\n\n---\n\n### Response Interpretation for LLMs\n\n- `status == \"pending\"`: Needs admin attention\n- `reason`: Why routing failed (helps determine resolution)\n- `body_preview`: First 500 chars of email body for quick review\n- `resolved_tenant_id`: Set after manual assignment","examples":[{"body_preview":"Thank you for reaching out! I would love to help...","id":"edl-abc12345","normalized_sender":"voter@example.com","reason":"no_routing_match","received_at":"2024-01-15T10:35:00Z","sender_email":"voter@example.com","status":"pending","subject":"Re: Volunteer Opportunity"}]},"cbworkflow__EmailDeadLetterReason":{"type":"string","enum":["no_routing_match","tenant_not_verified","tenant_disabled","tenant_not_found","session_expired","processing_error"],"title":"EmailDeadLetterReason","description":"Reason why an email was sent to the dead letter queue.\n\n---\n\n### Reasons Explained\n\n- **NO_ROUTING_MATCH**: Sender not in routing table (never received outbound email)\n- **TENANT_NOT_VERIFIED**: Tenant found but email not verified\n- **TENANT_DISABLED**: Tenant's email explicitly disabled\n- **TENANT_NOT_FOUND**: Routing entry points to deleted/invalid tenant\n- **SESSION_EXPIRED**: POBox session expired, needs re-authentication\n- **PROCESSING_ERROR**: Unexpected error during message processing"},"cbworkflow__EmailDeadLetterResolveRequest":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant ID to assign this email to","examples":["tenant-abc12345"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Optional notes about the resolution","examples":["Confirmed as Acme Campaign contact via email signature"]}},"type":"object","required":["tenant_id"],"title":"EmailDeadLetterResolveRequest","description":"Request to resolve an email dead letter by assigning to a tenant.","examples":[{"notes":"Confirmed as Acme Campaign contact","tenant_id":"tenant-abc12345"}]},"cbworkflow__EmailDeadLetterStatus":{"type":"string","enum":["pending","resolved","discarded"],"title":"EmailDeadLetterStatus","description":"Status of an email dead letter message.\n\n---\n\n### Status Lifecycle\n\n```\nPENDING → RESOLVED (assigned to tenant)\n    ↓\nDISCARDED (marked as spam/irrelevant)\n```"},"cbworkflow__EmailDeadLetterSummary":{"properties":{"total":{"type":"integer","title":"Total","description":"Total number of email dead letter messages","default":0},"pending":{"type":"integer","title":"Pending","description":"Emails awaiting review","default":0},"resolved":{"type":"integer","title":"Resolved","description":"Emails assigned to tenants","default":0},"discarded":{"type":"integer","title":"Discarded","description":"Emails marked as spam/irrelevant","default":0},"by_reason":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Reason","description":"Count of emails by reason code","examples":[{"no_routing_match":10,"session_expired":2}]}},"type":"object","title":"EmailDeadLetterSummary","description":"Summary statistics for email dead letters.\n\nProvides an overview of the email dead letter queue for monitoring dashboards.","examples":[{"by_reason":{"no_routing_match":15,"session_expired":5},"discarded":3,"pending":5,"resolved":12,"total":20}]},"cbworkflow__EmailDeleteResponse":{"properties":{"message":{"type":"string","title":"Message"},"deleted":{"type":"boolean","title":"Deleted"}},"type":"object","required":["message","deleted"],"title":"EmailDeleteResponse","description":"Response for email config deletion."},"cbworkflow__EmailOptInStatus":{"type":"string","enum":["pending","verified","expired","disabled"],"title":"EmailOptInStatus","description":"Status of email opt-in verification.\n\nTenants must verify their email before sending/receiving messages.\n\n---\n\n### Verification Flow\n\n```\nPENDING → (verify code) → VERIFIED\n    ↓                         ↓\nEXPIRED                   DISABLED (manual)\n```\n\n- **PENDING**: Verification code sent, awaiting confirmation\n- **VERIFIED**: Email verified, can send/receive\n- **EXPIRED**: Verification code expired, must restart\n- **DISABLED**: Manually disabled by tenant or admin"},"cbworkflow__EmailProvider":{"type":"string","enum":["gmail","outlook","yahoo","custom"],"title":"EmailProvider","description":"Supported email providers.\n\n- **GMAIL**: Google Gmail (uses specific IMAP/SMTP settings)\n- **OUTLOOK**: Microsoft Outlook/Office 365\n- **YAHOO**: Yahoo Mail\n- **CUSTOM**: Custom IMAP/SMTP servers (requires server configuration)"},"cbworkflow__EmailSetupResponse":{"properties":{"message":{"type":"string","title":"Message"},"config":{"$ref":"#/components/schemas/cbworkflow__TenantEmailConfigResponse"}},"type":"object","required":["message","config"],"title":"EmailSetupResponse","description":"Response for email setup."},"cbworkflow__EmailVerifyResponse":{"properties":{"message":{"type":"string","title":"Message"},"verified":{"type":"boolean","title":"Verified"},"config":{"$ref":"#/components/schemas/cbworkflow__TenantEmailConfigResponse"}},"type":"object","required":["message","verified","config"],"title":"EmailVerifyResponse","description":"Response for email verification."},"cbworkflow__ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail","description":"Human-readable error message explaining what went wrong","examples":["Contact not found","Invalid workflow template structure"]}},"type":"object","required":["detail"],"title":"ErrorResponse","description":"Standard error response format.\n\nReturned for 4xx and 5xx HTTP errors with a human-readable message.","examples":[{"detail":"Contact not found"},{"detail":"Workflow template not found"},{"detail":"Instance not found"}]},"cbworkflow__Event":{"properties":{"id":{"type":"string","title":"Id","description":"Unique 12-character hexadecimal event identifier","examples":["e1b2c3d4e5f6"]},"type":{"$ref":"#/components/schemas/cbworkflow__EventType","description":"Type of event from the EventType enum"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp","description":"When the event occurred (UTC)"},"node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Node Id","description":"ID of the node where this event occurred (if applicable)","examples":["call_step","decision_1","end_success"]},"actor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor","description":"Who/what caused this event: user ID, 'system', 'ai', connector name","examples":["user-123","system","ai","sms-connector"]},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Event-specific data payload. Structure varies by event type.","examples":[{"outcome":"interested","result":"success"},{"changes":{"score":85}}]},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Human-readable message or note about the event","examples":["Contact answered and was very interested","Retrying after timeout"]}},"type":"object","required":["type"],"title":"Event","description":"A single event in a workflow instance timeline.\n\nEvents form an immutable audit trail of everything that happens\nduring workflow execution. Each event captures:\n- What happened (type)\n- When it happened (timestamp)\n- Where in the workflow (node_id)\n- Who/what caused it (actor)\n- Additional context (data, message)\n\n---\n\n### Response Interpretation for LLMs\n\n**Key patterns:**\n```python\n# Find latest action result\naction_events = [e for e in timeline if e.type.startswith(\"action_\")]\nif action_events:\n    latest = action_events[-1]\n    return f\"Last action: {latest.type} at {latest.timestamp}\"\n\n# Check if user has added notes\nnotes = [e for e in timeline if e.type == \"user_note\"]\nreturn f\"User has added {len(notes)} notes\"\n```\n\n**Common data dict structures:**\n- `action_completed`: `{\"result\": \"success\", \"duration_ms\": 1234}`\n- `decision_evaluated`: `{\"outcome\": \"yes\", \"rule_type\": \"ai\"}`\n- `state_updated`: `{\"changes\": {\"score\": 85}}`","examples":[{"actor":"sms-connector","data":{"message_id":"msg-123","result":"delivered"},"id":"e1b2c3d4e5f6","message":"SMS delivered successfully","node_id":"call_step","timestamp":"2024-01-15T10:35:00Z","type":"action_completed"},{"actor":"user-123","id":"e2b3c4d5e6f7","message":"Contact requested follow-up next week","timestamp":"2024-01-15T10:40:00Z","type":"user_note"}]},"cbworkflow__EventDeliver":{"properties":{"event":{"type":"string","title":"Event"},"contact_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Id"},"payload":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payload"},"instance_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instance Id"}},"type":"object","required":["event"],"title":"EventDeliver"},"cbworkflow__EventType":{"type":"string","enum":["instance_created","instance_started","instance_paused","instance_resumed","instance_completed","instance_failed","node_entered","node_exited","action_started","action_completed","action_failed","decision_evaluated","decision_manual_input","external_trigger","webhook_received","state_updated","tag_added","tag_removed","user_note","user_action"],"title":"EventType","description":"Types of events that can occur in a workflow timeline.\n\nEvents are grouped into categories:\n\n---\n\n### Lifecycle Events\nTrack the overall instance state changes:\n- `instance_created`: Instance was created from template\n- `instance_started`: Instance execution began (start() called)\n- `instance_paused`: Instance was manually paused\n- `instance_resumed`: Instance was resumed from pause\n- `instance_completed`: Instance reached successful END node\n- `instance_failed`: Instance reached failure END node or errored\n\n### Node Events\nTrack movement through the workflow graph:\n- `node_entered`: Execution moved to a new node\n- `node_exited`: Execution left a node (transition taken)\n\n### Action Events\nTrack action node execution:\n- `action_started`: Action (call, SMS, email) began\n- `action_completed`: Action finished successfully\n- `action_failed`: Action encountered an error\n\n### Decision Events\nTrack decision node evaluation:\n- `decision_evaluated`: Rule was evaluated (heuristic or AI)\n- `decision_manual_input`: User provided manual input\n\n### External Events\nTrack external system interactions:\n- `external_trigger`: External event triggered workflow change\n- `webhook_received`: Webhook callback was received\n\n### State Events\nTrack instance state changes:\n- `state_updated`: State dict was modified\n- `tag_added`: Tag was added to instance\n- `tag_removed`: Tag was removed from instance\n\n### User Events\nTrack user interactions:\n- `user_note`: User added a note to timeline\n- `user_action`: User performed a manual action"},"cbworkflow__FieldType":{"type":"string","enum":["text","password","email","url","number","textarea","select","checkbox"],"title":"FieldType","description":"Types of form fields for credential/config input."},"cbworkflow__GuideDetail":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"content":{"type":"string","title":"Content"},"path":{"type":"string","title":"Path"}},"type":"object","required":["slug","title","content","path"],"title":"GuideDetail","description":"Full guide content."},"cbworkflow__GuideListItem":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"blurb":{"type":"string","title":"Blurb"},"path":{"type":"string","title":"Path"}},"type":"object","required":["slug","title","blurb","path"],"title":"GuideListItem","description":"Summary of a guide for listing."},"cbworkflow__HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/cbworkflow__ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"cbworkflow__HealthCheckResponse":{"properties":{"overall_status":{"$ref":"#/components/schemas/cbworkflow__ConnectorStatus"},"connectors":{"additionalProperties":{"$ref":"#/components/schemas/cbworkflow__ConnectorHealth"},"type":"object","title":"Connectors"}},"type":"object","required":["overall_status","connectors"],"title":"HealthCheckResponse","description":"Health check response."},"cbworkflow__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbworkflow__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbworkflow__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbworkflow__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbworkflow__InstanceStatus":{"type":"string","enum":["pending","active","paused","waiting","completed","failed","cancelled"],"title":"InstanceStatus","description":"Status of a workflow instance lifecycle.\n\n---\n\n### Status Lifecycle\n\n```\nPENDING → ACTIVE ↔ PAUSED\n            ↓\n        WAITING → ACTIVE\n            ↓\n        COMPLETED / FAILED / CANCELLED\n```\n\n- **PENDING**: Created but `start()` not called yet\n- **ACTIVE**: Currently executing, ready for transitions\n- **PAUSED**: Manually paused, can be resumed\n- **WAITING**: At a WAIT node, will auto-resume when condition met\n- **COMPLETED**: Successfully reached an END node\n- **FAILED**: Encountered an error or reached a failure END node\n- **CANCELLED**: Manually cancelled before completion"},"cbworkflow__JourneyCreate":{"properties":{"template_id":{"type":"string","title":"Template Id"},"contact_id":{"type":"string","title":"Contact Id"},"params":{"additionalProperties":true,"type":"object","title":"Params"}},"type":"object","required":["template_id","contact_id"],"title":"JourneyCreate"},"cbworkflow__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbworkflow__Node-Input":{"properties":{"type":{"$ref":"#/components/schemas/cbworkflow__NodeType","description":"Node type determining behavior: 'action', 'decision', 'wait', 'webhook', or 'end'"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Human-readable label for UI display","examples":["Initial Call","Wait 1 Day","Classify Response"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Detailed description of what this node does","examples":["Call the contact to introduce the campaign"]},"action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action","description":"Action type for ACTION nodes: 'phone_call', 'sms', 'email', 'door_canvass'","examples":["phone_call","sms","email","door_canvass"]},"action_config":{"additionalProperties":true,"type":"object","title":"Action Config","description":"Action-specific configuration (e.g., message template, call script)","examples":[{"caller_id":"+15551234567","template":"Hi {name}, this is..."}]},"rule":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__DecisionRule"},{"type":"null"}],"description":"Decision rule for DECISION nodes (heuristic, AI, or manual)"},"wait_condition":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__WaitCondition"},{"type":"null"}],"description":"Wait condition for WAIT nodes (duration, until, or event)"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url","description":"URL to call for WEBHOOK nodes","examples":["https://api.example.com/callback"]},"webhook_method":{"type":"string","title":"Webhook Method","description":"HTTP method for webhook calls","default":"POST","examples":["POST","GET","PUT"]},"end_status":{"type":"string","title":"End Status","description":"Final status for END nodes: 'completed', 'failed', 'cancelled'","default":"completed","examples":["completed","failed","cancelled","opted_out"]},"transitions":{"additionalProperties":{"type":"string"},"type":"object","title":"Transitions","description":"Map of outcome strings to next node IDs. END nodes cannot have transitions.","examples":[{"completed":"next_step","no_answer":"retry","refused":"end_refused"}]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Custom metadata for this node","examples":[{"priority":"high","requires_training":true}]}},"type":"object","required":["type"],"title":"Node","description":"A node in a workflow template graph.\n\nNodes represent steps in the workflow. Each node type has specific\nrequired fields and behavior:\n\n| Type | Required Fields | Behavior |\n|------|----------------|----------|\n| ACTION | `action` | Executes an action, then follows transition |\n| DECISION | `rule` | Evaluates rule, branches to outcome transition |\n| WAIT | `wait_condition` | Pauses execution until condition met |\n| WEBHOOK | `webhook_url` | Calls external endpoint, branches on response |\n| END | none | Terminates workflow with `end_status` |\n\n---\n\n### Common Action Types\n\n- `phone_call`: Trigger outbound call\n- `sms`: Send SMS message\n- `email`: Send email\n- `door_canvass`: Schedule door knock\n\n### Transitions\n\nThe `transitions` dict maps outcome strings to next node IDs:\n```json\n{\"completed\": \"next_step\", \"no_answer\": \"retry_node\", \"refused\": \"end_node\"}\n```","examples":[{"action":"phone_call","action_config":{"script":"Hi, I'm calling about..."},"label":"Initial Call","transitions":{"completed":"classify","no_answer":"wait_retry"},"type":"action"},{"label":"Classify Response","rule":{"type":"manual","user_prompt":"How did the call go?"},"transitions":{"interested":"schedule","not_interested":"end_no"},"type":"decision"},{"end_status":"completed","label":"Success","type":"end"}]},"cbworkflow__Node-Output":{"properties":{"type":{"$ref":"#/components/schemas/cbworkflow__NodeType","description":"Node type determining behavior: 'action', 'decision', 'wait', 'webhook', or 'end'"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Human-readable label for UI display","examples":["Initial Call","Wait 1 Day","Classify Response"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Detailed description of what this node does","examples":["Call the contact to introduce the campaign"]},"action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action","description":"Action type for ACTION nodes: 'phone_call', 'sms', 'email', 'door_canvass'","examples":["phone_call","sms","email","door_canvass"]},"action_config":{"additionalProperties":true,"type":"object","title":"Action Config","description":"Action-specific configuration (e.g., message template, call script)","examples":[{"caller_id":"+15551234567","template":"Hi {name}, this is..."}]},"rule":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__DecisionRule"},{"type":"null"}],"description":"Decision rule for DECISION nodes (heuristic, AI, or manual)"},"wait_condition":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__WaitCondition"},{"type":"null"}],"description":"Wait condition for WAIT nodes (duration, until, or event)"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url","description":"URL to call for WEBHOOK nodes","examples":["https://api.example.com/callback"]},"webhook_method":{"type":"string","title":"Webhook Method","description":"HTTP method for webhook calls","default":"POST","examples":["POST","GET","PUT"]},"end_status":{"type":"string","title":"End Status","description":"Final status for END nodes: 'completed', 'failed', 'cancelled'","default":"completed","examples":["completed","failed","cancelled","opted_out"]},"transitions":{"additionalProperties":{"type":"string"},"type":"object","title":"Transitions","description":"Map of outcome strings to next node IDs. END nodes cannot have transitions.","examples":[{"completed":"next_step","no_answer":"retry","refused":"end_refused"}]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Custom metadata for this node","examples":[{"priority":"high","requires_training":true}]}},"type":"object","required":["type"],"title":"Node","description":"A node in a workflow template graph.\n\nNodes represent steps in the workflow. Each node type has specific\nrequired fields and behavior:\n\n| Type | Required Fields | Behavior |\n|------|----------------|----------|\n| ACTION | `action` | Executes an action, then follows transition |\n| DECISION | `rule` | Evaluates rule, branches to outcome transition |\n| WAIT | `wait_condition` | Pauses execution until condition met |\n| WEBHOOK | `webhook_url` | Calls external endpoint, branches on response |\n| END | none | Terminates workflow with `end_status` |\n\n---\n\n### Common Action Types\n\n- `phone_call`: Trigger outbound call\n- `sms`: Send SMS message\n- `email`: Send email\n- `door_canvass`: Schedule door knock\n\n### Transitions\n\nThe `transitions` dict maps outcome strings to next node IDs:\n```json\n{\"completed\": \"next_step\", \"no_answer\": \"retry_node\", \"refused\": \"end_node\"}\n```","examples":[{"action":"phone_call","action_config":{"script":"Hi, I'm calling about..."},"label":"Initial Call","transitions":{"completed":"classify","no_answer":"wait_retry"},"type":"action"},{"label":"Classify Response","rule":{"type":"manual","user_prompt":"How did the call go?"},"transitions":{"interested":"schedule","not_interested":"end_no"},"type":"decision"},{"end_status":"completed","label":"Success","type":"end"}]},"cbworkflow__NodeType":{"type":"string","enum":["action","decision","wait","webhook","end"],"title":"NodeType","description":"Types of nodes in a workflow graph.\n\nEach node type has specific behavior and required configuration:\n\n- **ACTION**: Executes an action (call, email, SMS, door knock). Requires `action` field.\n- **DECISION**: Branches based on a rule (heuristic, AI, or manual). Requires `rule` field.\n- **WAIT**: Pauses until a duration, datetime, or event. Requires `wait_condition` field.\n- **WEBHOOK**: Calls an external HTTP endpoint. Requires `webhook_url` field.\n- **END**: Terminal state marking workflow completion. Cannot have transitions."},"cbworkflow__NoteRequest":{"properties":{"note":{"type":"string","title":"Note","description":"Note text to add to the timeline","examples":["Contact requested callback tomorrow afternoon"]},"actor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor","description":"User adding the note","examples":["user-123"]}},"type":"object","required":["note"],"title":"NoteRequest","description":"Request body for adding notes."},"cbworkflow__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbworkflow__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbworkflow__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbworkflow__SMSDeleteResponse":{"properties":{"message":{"type":"string","title":"Message"},"deleted":{"type":"boolean","title":"Deleted"}},"type":"object","required":["message","deleted"],"title":"SMSDeleteResponse","description":"Response for SMS config deletion."},"cbworkflow__SMSOptInStatus":{"type":"string","enum":["pending","verified","expired","disabled"],"title":"SMSOptInStatus","description":"Status of SMS opt-in verification.\n\nTenants must verify their phone number before sending/receiving SMS.\n\n---\n\n### Verification Flow\n\n```\nPENDING → (verify code) → VERIFIED\n    ↓                         ↓\nEXPIRED                   DISABLED (manual)\n```\n\n- **PENDING**: Verification code sent, awaiting confirmation\n- **VERIFIED**: Phone number verified, can send/receive\n- **EXPIRED**: Verification code expired, must restart\n- **DISABLED**: Manually disabled by tenant or admin"},"cbworkflow__SMSSetupResponse":{"properties":{"message":{"type":"string","title":"Message"},"config":{"$ref":"#/components/schemas/cbworkflow__TenantSMSConfigResponse"}},"type":"object","required":["message","config"],"title":"SMSSetupResponse","description":"Response for SMS setup."},"cbworkflow__SMSVerifyResponse":{"properties":{"message":{"type":"string","title":"Message"},"verified":{"type":"boolean","title":"Verified"},"config":{"$ref":"#/components/schemas/cbworkflow__TenantSMSConfigResponse"}},"type":"object","required":["message","verified","config"],"title":"SMSVerifyResponse","description":"Response for SMS verification."},"cbworkflow__StateUpdateRequest":{"properties":{"updates":{"additionalProperties":true,"type":"object","title":"Updates","description":"Key-value pairs to merge into instance state","examples":[{"last_response":"interested","score":85}]},"actor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor","description":"User or system making the update","examples":["user-123","system"]}},"type":"object","required":["updates"],"title":"StateUpdateRequest","description":"Request body for state updates."},"cbworkflow__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbworkflow__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}},"cbworkflow__TaskStatusResponse":{"properties":{"task_id":{"type":"string","title":"Task Id"},"status":{"type":"string","title":"Status"},"result":{"anyOf":[{"$ref":"#/components/schemas/cbworkflow__ActionResult"},{"type":"null"}]},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["task_id","status"],"title":"TaskStatusResponse","description":"Response for task status query."},"cbworkflow__Tenant":{"properties":{"id":{"type":"string","title":"Id","description":"Unique tenant identifier in format 'tenant-{8 hex chars}'","examples":["tenant-abc12345","tenant-def67890"]},"name":{"type":"string","title":"Name","description":"Human-readable organization name","examples":["Acme Campaign","District 5 Office","State Party HQ"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when tenant was created (UTC)"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Timestamp when tenant was last modified (UTC)"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Custom key-value metadata for tenant configuration","examples":[{"contact_email":"admin@example.com","plan":"enterprise","region":"us-west"}]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether tenant is active. False = soft-deleted, data preserved but inaccessible.","default":true}},"type":"object","required":["name"],"title":"Tenant","description":"A tenant represents an organization using the workflow system.\n\nEach tenant has isolated:\n- Contacts\n- Workflow templates\n- Workflow instances\n- Connector credentials\n- SMS/Email configurations\n\n---\n\n### Multi-Tenant Architecture\n\nAll data in cbworkflow is scoped by tenant_id. When using tenant-scoped\nendpoints (`/api/tenants/{tenant_id}/...`), operations are automatically\nfiltered to that tenant's data.\n\n### Response Interpretation for LLMs\n\n- `is_active`: False means tenant is soft-deleted and should not be used\n- `metadata`: Use for custom tenant-level configuration","examples":[{"created_at":"2024-01-15T10:30:00Z","id":"tenant-abc12345","is_active":true,"metadata":{"plan":"enterprise"},"name":"Acme Campaign","updated_at":"2024-01-15T10:30:00Z"}]},"cbworkflow__TenantCreate":{"properties":{"name":{"type":"string","title":"Name","description":"Human-readable organization name (required)","examples":["Acme Campaign"]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Optional custom metadata","examples":[{"plan":"starter","region":"us-east"}]}},"type":"object","required":["name"],"title":"TenantCreate","description":"Request model for creating a tenant.\n\nOnly `name` is required. The tenant ID will be auto-generated.","examples":[{"metadata":{"plan":"starter"},"name":"Acme Campaign"}]},"cbworkflow__TenantEmailConfigResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Email config identifier"},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant ID"},"email_address":{"type":"string","title":"Email Address","description":"Email address"},"normalized_email":{"type":"string","title":"Normalized Email","description":"Normalized email"},"provider":{"$ref":"#/components/schemas/cbworkflow__EmailProvider","description":"Email provider"},"opt_in_status":{"$ref":"#/components/schemas/cbworkflow__EmailOptInStatus","description":"Verification status"},"session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id","description":"POBox session ID"},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"When verified"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When created"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When last modified"},"messages_sent":{"type":"integer","title":"Messages Sent","description":"Total emails sent"},"messages_received":{"type":"integer","title":"Messages Received","description":"Total emails received"}},"type":"object","required":["id","tenant_id","email_address","normalized_email","provider","opt_in_status","session_id","verified_at","created_at","updated_at","messages_sent","messages_received"],"title":"TenantEmailConfigResponse","description":"Response model for tenant email configuration.\n\nExcludes sensitive fields like verification_code and password.","examples":[{"created_at":"2024-01-15T10:30:00Z","email_address":"campaign@example.com","id":"email-config-abc12345","messages_received":75,"messages_sent":250,"normalized_email":"campaign@example.com","opt_in_status":"verified","provider":"gmail","session_id":"email-session-123","tenant_id":"tenant-abc12345","updated_at":"2024-01-15T10:35:00Z","verified_at":"2024-01-15T10:35:00Z"}]},"cbworkflow__TenantEmailSetupRequest":{"properties":{"email_address":{"type":"string","title":"Email Address","description":"Email address to configure","examples":["campaign@example.com"]},"password":{"type":"string","title":"Password","description":"Email password or app password (recommended for security)"},"provider":{"$ref":"#/components/schemas/cbworkflow__EmailProvider","description":"Email provider","default":"gmail"},"imap_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Imap Server","description":"Custom IMAP server (required for 'custom' provider)","examples":["imap.example.com"]},"smtp_server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Smtp Server","description":"Custom SMTP server (required for 'custom' provider)","examples":["smtp.example.com"]},"imap_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Imap Port","description":"Custom IMAP port (default: 993 for SSL)"},"smtp_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Smtp Port","description":"Custom SMTP port (default: 587 for TLS)"}},"type":"object","required":["email_address","password"],"title":"TenantEmailSetupRequest","description":"Request model for setting up tenant email.\n\nInitiates the email verification flow. For Gmail/Outlook/Yahoo,\nuse an app password rather than the main account password.","examples":[{"email_address":"campaign@gmail.com","password":"app-password-here","provider":"gmail"}]},"cbworkflow__TenantEmailVerifyRequest":{"properties":{"code":{"type":"string","title":"Code","description":"6-digit verification code","examples":["123456"]}},"type":"object","required":["code"],"title":"TenantEmailVerifyRequest","description":"Request model for verifying tenant email.\n\nComplete the verification by providing the 6-digit code sent\nto the email address.","examples":[{"code":"123456"}]},"cbworkflow__TenantSMSConfigResponse":{"properties":{"id":{"type":"string","title":"Id","description":"SMS config identifier"},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant ID"},"phone_number":{"type":"string","title":"Phone Number","description":"Phone number in E.164 format"},"normalized_phone":{"type":"string","title":"Normalized Phone","description":"Normalized phone (digits only)"},"opt_in_status":{"$ref":"#/components/schemas/cbworkflow__SMSOptInStatus","description":"Verification status"},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At","description":"When verified"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When created"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When last modified"},"messages_sent":{"type":"integer","title":"Messages Sent","description":"Total messages sent"},"messages_received":{"type":"integer","title":"Messages Received","description":"Total messages received"}},"type":"object","required":["id","tenant_id","phone_number","normalized_phone","opt_in_status","verified_at","created_at","updated_at","messages_sent","messages_received"],"title":"TenantSMSConfigResponse","description":"Response model for tenant SMS configuration.\n\nExcludes sensitive fields like verification_code.","examples":[{"created_at":"2024-01-15T10:30:00Z","id":"sms-config-abc12345","messages_received":45,"messages_sent":150,"normalized_phone":"5551234567","opt_in_status":"verified","phone_number":"+15551234567","tenant_id":"tenant-abc12345","updated_at":"2024-01-15T10:35:00Z","verified_at":"2024-01-15T10:35:00Z"}]},"cbworkflow__TenantSMSSetupRequest":{"properties":{"phone_number":{"type":"string","title":"Phone Number","description":"Phone number in E.164 format (e.g., +15551234567)","examples":["+15551234567"]}},"type":"object","required":["phone_number"],"title":"TenantSMSSetupRequest","description":"Request model for setting up tenant SMS.\n\nInitiates the SMS verification flow by sending a 6-digit code\nto the provided phone number.","examples":[{"phone_number":"+15551234567"}]},"cbworkflow__TenantSMSVerifyRequest":{"properties":{"code":{"type":"string","title":"Code","description":"6-digit verification code","examples":["123456"]}},"type":"object","required":["code"],"title":"TenantSMSVerifyRequest","description":"Request model for verifying tenant SMS.\n\nComplete the verification by providing the 6-digit code sent\nto the phone number.","examples":[{"code":"123456"}]},"cbworkflow__TenantUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Updated organization name","examples":["Acme Campaign 2024"]},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Updated metadata (replaces all existing metadata)","examples":[{"plan":"enterprise","upgraded_at":"2024-06-01"}]},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Set to false to soft-delete the tenant"}},"type":"object","title":"TenantUpdate","description":"Request model for updating a tenant.\n\nAll fields are optional. Only provided fields will be updated.","examples":[{"metadata":{"plan":"enterprise"},"name":"Acme Campaign 2024"}]},"cbworkflow__TimelineEntry":{"properties":{"event":{"$ref":"#/components/schemas/cbworkflow__Event","description":"The timeline event"},"instance_id":{"type":"string","title":"Instance Id","description":"ID of the workflow instance this event belongs to","examples":["i1b2c3d4e5f6"]},"template_id":{"type":"string","title":"Template Id","description":"ID of the workflow template","examples":["t1b2c3d4e5f6"]},"template_name":{"type":"string","title":"Template Name","description":"Human-readable name of the workflow template","examples":["Voter Outreach","Volunteer Onboarding"]},"instance_status":{"type":"string","title":"Instance Status","description":"Current status of the workflow instance","examples":["active","completed","paused"]},"current_node":{"type":"string","title":"Current Node","description":"Current node of the workflow instance","examples":["call_step","end_success"]}},"type":"object","required":["event","instance_id","template_id","template_name","instance_status","current_node"],"title":"TimelineEntry","description":"An event with workflow context for unified timelines.\n\nUsed when building a contact's unified timeline across multiple\nworkflow instances. Includes both the event and context about\nwhich workflow instance it belongs to.\n\n---\n\n### Response Interpretation for LLMs\n\nWhen displaying a contact's full history:\n```python\nfor entry in timeline_entries:\n    print(f\"[{entry.template_name}] {entry.event.type}: {entry.event.message}\")\n    print(f\"  Instance: {entry.instance_id} ({entry.instance_status})\")\n```","examples":[{"current_node":"decision_1","event":{"actor":"system","id":"e1b2c3d4e5f6","message":"Call completed successfully","node_id":"call_step","timestamp":"2024-01-15T10:35:00Z","type":"action_completed"},"instance_id":"i1b2c3d4e5f6","instance_status":"active","template_id":"t1b2c3d4e5f6","template_name":"Voter Outreach"}]},"cbworkflow__TransitionRequest":{"properties":{"outcome":{"type":"string","title":"Outcome","description":"The transition outcome to follow. Must match a key in the current node's transitions dict.","examples":["completed","no_answer","interested","not_interested"]},"state_updates":{"additionalProperties":true,"type":"object","title":"State Updates","description":"State updates to apply along with the transition","examples":[{"notes":"Very engaged","response":"interested"}]},"actor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor","description":"ID of the user or system performing the transition","examples":["user-123","system","ai-evaluator"]},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Optional note to add to the timeline event","examples":["Contact requested callback tomorrow"]}},"type":"object","required":["outcome"],"title":"TransitionRequest","description":"Request to manually transition an instance to the next node.\n\nUsed for manual decision nodes or to override automatic transitions.\nThe `outcome` must match a key in the current node's `transitions` dict.","examples":[{"actor":"user-123","message":"Contact agreed to volunteer","outcome":"interested","state_updates":{"response":"interested"}}]},"cbworkflow__ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"cbworkflow__WaitCondition":{"properties":{"type":{"type":"string","enum":["duration","until","event"],"title":"Type","description":"Type of wait condition: 'duration' (fixed time), 'until' (specific datetime), or 'event' (external trigger)"},"duration":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Duration","description":"ISO 8601 duration string (e.g., 'PT1H' for 1 hour, 'P1D' for 1 day, 'P1W' for 1 week)","examples":["PT1H","P1D","PT30M","P1W"]},"until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Until","description":"Specific datetime to wait until (UTC)","examples":["2024-12-01T09:00:00Z"]},"event_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Type","description":"Event type to wait for (e.g., 'sms_reply', 'email_response')","examples":["sms_reply","email_response","webhook_callback"]}},"type":"object","required":["type"],"title":"WaitCondition","description":"Condition for a wait node that pauses workflow execution.\n\nDefines when the workflow should resume:\n- **duration**: Wait for a fixed time (ISO 8601 duration)\n- **until**: Wait until a specific datetime\n- **event**: Wait until a specific event type is received\n\n---\n\n### Duration Format (ISO 8601)\n\n- `PT1H` = 1 hour\n- `P1D` = 1 day\n- `P1W` = 1 week\n- `PT30M` = 30 minutes","examples":[{"duration":"P1D","type":"duration"},{"type":"until","until":"2024-12-01T09:00:00Z"},{"event_type":"sms_reply","type":"event"}]},"cbworkflow__WebhookPayload":{"properties":{"event_type":{"type":"string","title":"Event Type"},"external_id":{"type":"string","title":"External Id"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"}},"type":"object","required":["event_type","external_id"],"title":"WebhookPayload","description":"Incoming webhook payload."},"cbworkflow__WorkflowInstance":{"properties":{"id":{"type":"string","title":"Id","description":"Unique 12-character hexadecimal identifier","examples":["i1b2c3d4e5f6"]},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant organization ID for data isolation","default":"default","examples":["tenant-abc123","default"]},"contact_id":{"type":"string","title":"Contact Id","description":"ID of the contact this workflow instance is attached to","examples":["c1b2c3d4e5f6"]},"template_id":{"type":"string","title":"Template Id","description":"ID of the template this instance was created from","examples":["t1b2c3d4e5f6"]},"template_version":{"type":"string","title":"Template Version","description":"Version of the template at creation time (frozen)","examples":["1.0.0","2.1.0"]},"current_node":{"type":"string","title":"Current Node","description":"ID of the node currently being processed","examples":["start","call_step","decision_1"]},"status":{"$ref":"#/components/schemas/cbworkflow__InstanceStatus","description":"Current lifecycle status of the instance","default":"pending"},"state":{"additionalProperties":true,"type":"object","title":"State","description":"Mutable key-value data accumulated during execution. Used in decision rules via `state.xxx` syntax.","examples":[{"attempts":2,"last_response":"interested","score":85}]},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tags for filtering and categorization","examples":[["priority","follow-up-needed"]]},"timeline":{"items":{"$ref":"#/components/schemas/cbworkflow__Event"},"type":"array","title":"Timeline","description":"Chronological list of all events in this instance's lifecycle"},"nodes":{"additionalProperties":{"$ref":"#/components/schemas/cbworkflow__Node-Output"},"type":"object","title":"Nodes","description":"Frozen copy of template nodes at creation time. Won't change if template is updated."},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Custom key-value metadata","examples":[{"assigned_to":"user-123","priority":"high"}]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the instance was created (UTC)"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Timestamp when the instance was last modified (UTC)"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At","description":"Timestamp when start() was called (UTC). Null if still pending."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"Timestamp when workflow reached terminal state (UTC). Null if still running."}},"type":"object","required":["contact_id","template_id","template_version","current_node"],"title":"WorkflowInstance","description":"A live workflow instance assigned to a contact.\n\nInstances are created from templates and track the execution state\nas the contact moves through the workflow. Each instance has:\n- A frozen copy of the template's nodes (won't change if template is updated)\n- Mutable state dict for storing data during execution\n- Append-only timeline of all events\n\n---\n\n### Response Interpretation for LLMs\n\n**Key indicators:**\n- `status`: Current lifecycle state (see InstanceStatus enum)\n- `current_node`: ID of the node currently being processed\n- `state`: Mutable data accumulated during workflow execution\n- `timeline`: Chronological audit trail of all events\n\n**Common patterns:**\n```python\nif instance.status == \"active\":\n    current = instance.nodes[instance.current_node]\n    return f\"Instance at {current.label or current.type} node\"\nif instance.status in (\"completed\", \"failed\", \"cancelled\"):\n    return f\"Instance finished with status: {instance.status}\"\n```","examples":[{"contact_id":"c1b2c3d4e5f6","created_at":"2024-01-15T10:30:00Z","current_node":"call_step","id":"i1b2c3d4e5f6","started_at":"2024-01-15T10:31:00Z","state":{"attempts":1},"status":"active","tags":["priority"],"template_id":"t1b2c3d4e5f6","template_version":"1.0.0","tenant_id":"tenant-abc123","timeline":[],"updated_at":"2024-01-15T10:35:00Z"}]},"cbworkflow__WorkflowInstanceCreate":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant organization ID","default":"default","examples":["tenant-abc123"]},"contact_id":{"type":"string","title":"Contact Id","description":"ID of the contact to attach this workflow to","examples":["c1b2c3d4e5f6"]},"template_id":{"type":"string","title":"Template Id","description":"ID of the workflow template to instantiate","examples":["t1b2c3d4e5f6"]},"initial_state":{"additionalProperties":true,"type":"object","title":"Initial State","description":"Initial state data to populate at creation","examples":[{"priority":"high","source":"phone_bank"}]},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Initial tags for the instance","examples":[["priority","batch-2024-01"]]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Custom metadata","examples":[{"assigned_to":"user-123"}]}},"type":"object","required":["contact_id","template_id"],"title":"WorkflowInstanceCreate","description":"Schema for creating a workflow instance.\n\nRequires `contact_id` and `template_id`. The instance will be\ncreated in PENDING status and must be started with POST /instances/{id}/start.","examples":[{"contact_id":"c1b2c3d4e5f6","initial_state":{"source":"phone_bank"},"tags":["priority"],"template_id":"t1b2c3d4e5f6"}]},"cbworkflow__WorkflowTemplate":{"properties":{"id":{"type":"string","title":"Id","description":"Unique 12-character hexadecimal identifier","examples":["t1b2c3d4e5f6"]},"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant organization ID for data isolation","default":"default","examples":["tenant-abc123","default"]},"name":{"type":"string","title":"Name","description":"Human-readable name for the workflow template","examples":["Voter Outreach","Volunteer Onboarding","Donation Follow-up"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Detailed description of the workflow's purpose and behavior","examples":["Three-touch voter outreach with call, SMS, and door knock"]},"version":{"type":"string","title":"Version","description":"Semantic version string for tracking template changes","default":"1.0.0","examples":["1.0.0","2.1.0","1.0.0-beta"]},"nodes":{"additionalProperties":{"$ref":"#/components/schemas/cbworkflow__Node-Output"},"type":"object","title":"Nodes","description":"Map of node IDs to Node objects defining the workflow graph"},"start_node":{"type":"string","title":"Start Node","description":"ID of the node where workflow execution begins","examples":["start","initial_call","welcome"]},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tags for filtering and categorization","examples":[["outreach","phone","high-priority"]]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Custom key-value metadata","examples":[{"campaign":"2024-general","target_audience":"likely-voters"}]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the template was created (UTC)"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Timestamp when the template was last modified (UTC)"}},"type":"object","required":["name","nodes","start_node"],"title":"WorkflowTemplate","description":"A workflow template - the blueprint for workflow instances.\n\nTemplates define a directed graph of nodes that workflow instances\nexecute. Each template must have:\n- At least one node in the `nodes` dict\n- A `start_node` that exists in nodes\n- At least one END node\n- All transitions pointing to valid node IDs\n\n---\n\n### Graph Structure\n\n```\nnodes = {\n    \"start\": Node(type=\"action\", action=\"phone_call\", transitions={\"completed\": \"decision1\"}),\n    \"decision1\": Node(\n        type=\"decision\", rule=..., transitions={\"yes\": \"end_success\", \"no\": \"end_failed\"}\n    ),\n    \"end_success\": Node(type=\"end\", end_status=\"completed\"),\n    \"end_failed\": Node(type=\"end\", end_status=\"failed\"),\n}\nstart_node = \"start\"\n```\n\n### Response Interpretation for LLMs\n\n- `version`: Use for tracking template changes across instances\n- `nodes`: Keys are node IDs, values are Node objects\n- `start_node`: Always the entry point for new instances","examples":[{"description":"Basic phone call workflow","id":"t1b2c3d4e5f6","name":"Simple Outreach","nodes":{"call":{"action":"phone_call","label":"Make Call","transitions":{"completed":"end_success"},"type":"action"},"end_success":{"end_status":"completed","type":"end"}},"start_node":"call","tags":["outreach","phone"],"tenant_id":"tenant-abc123","version":"1.0.0"}]},"cbworkflow__WorkflowTemplateCreate":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id","description":"Tenant organization ID","default":"default","examples":["tenant-abc123"]},"name":{"type":"string","title":"Name","description":"Human-readable name for the workflow","examples":["Voter Outreach"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Description of the workflow's purpose","examples":["Three-touch voter outreach campaign"]},"version":{"type":"string","title":"Version","description":"Version string for the template","default":"1.0.0","examples":["1.0.0"]},"nodes":{"additionalProperties":{"$ref":"#/components/schemas/cbworkflow__Node-Input"},"type":"object","title":"Nodes","description":"Map of node IDs to Node objects"},"start_node":{"type":"string","title":"Start Node","description":"ID of the starting node","examples":["start"]},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tags for categorization","examples":[["outreach","phone"]]},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","description":"Custom metadata","examples":[{"campaign":"2024-general"}]}},"type":"object","required":["name","nodes","start_node"],"title":"WorkflowTemplateCreate","description":"Schema for creating a workflow template.\n\nProvide a `name`, `nodes` dict, and `start_node` at minimum.\nThe nodes dict must include at least one END node and all\ntransitions must point to valid node IDs."},"cbworkflow__WorkflowTemplateUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Updated name","examples":["Voter Outreach v2"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Updated description"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version","description":"Updated version string","examples":["2.0.0"]},"nodes":{"anyOf":[{"additionalProperties":{"$ref":"#/components/schemas/cbworkflow__Node-Input"},"type":"object"},{"type":"null"}],"title":"Nodes","description":"Updated nodes dict (replaces all nodes)"},"start_node":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Node","description":"Updated start node ID"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"Updated tags (replaces all tags)"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Updated metadata (replaces all metadata)"}},"type":"object","title":"WorkflowTemplateUpdate","description":"Schema for updating a workflow template.\n\nAll fields are optional. If `nodes` or `start_node` are provided,\nthey will be validated for consistency."},"cbworkspaces__ComponentHealth":{"properties":{"name":{"type":"string","title":"Name"},"healthy":{"type":"boolean","title":"Healthy"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["name","healthy"],"title":"ComponentHealth","description":"Health status for a single component."},"cbworkspaces__HealthResponse":{"properties":{"status":{"$ref":"#/components/schemas/cbworkspaces__HealthStatus"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"timestamp":{"type":"string","title":"Timestamp"},"uptime_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Seconds"},"components":{"items":{"$ref":"#/components/schemas/cbworkspaces__ComponentHealth"},"type":"array","title":"Components","default":[]}},"type":"object","required":["status","service","version","timestamp"],"title":"HealthResponse","description":"Response from health check endpoints.","example":{"components":[{"healthy":true,"name":"database"},{"healthy":true,"name":"redis"}],"service":"CB Workflow Engine","status":"healthy","timestamp":"2024-01-15T12:00:00Z","uptime_seconds":3600.5,"version":"0.3.0"}},"cbworkspaces__HealthStatus":{"type":"string","enum":["healthy","degraded","unhealthy"],"title":"HealthStatus","description":"Health status levels."},"cbworkspaces__MeshStatus":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"connected":{"type":"boolean","title":"Connected"},"gateway_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gateway Url"}},"type":"object","required":["enabled","connected"],"title":"MeshStatus","description":"Mesh connectivity status."},"cbworkspaces__PrimeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"routes_checked":{"type":"integer","title":"Routes Checked"},"models_validated":{"type":"integer","title":"Models Validated"},"dependencies_resolved":{"type":"integer","title":"Dependencies Resolved"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"tasks":{"items":{"$ref":"#/components/schemas/cbworkspaces__PrimeTaskResult"},"type":"array","title":"Tasks","default":[]}},"type":"object","required":["ok","duration_ms","routes_checked","models_validated","dependencies_resolved","errors"],"title":"PrimeResponse","description":"Response from /prime endpoint.","example":{"dependencies_resolved":7,"duration_ms":45.23,"errors":[],"models_validated":18,"ok":true,"routes_checked":24,"tasks":[{"detail":"loaded 3 models","duration_ms":312.7,"name":"warm_models","ok":true}]}},"cbworkspaces__PrimeTaskResult":{"properties":{"name":{"type":"string","title":"Name"},"ok":{"type":"boolean","title":"Ok"},"duration_ms":{"type":"number","title":"Duration Ms"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["name","ok","duration_ms"],"title":"PrimeTaskResult","description":"Result of one bespoke prime task in the /prime response."},"cbworkspaces__StatusResponse":{"properties":{"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"environment":{"type":"string","title":"Environment"},"started_at":{"type":"string","title":"Started At"},"uptime_seconds":{"type":"number","title":"Uptime Seconds"},"features":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Features"},"mesh":{"$ref":"#/components/schemas/cbworkspaces__MeshStatus"},"routes":{"type":"integer","title":"Routes"},"health_components":{"items":{"type":"string"},"type":"array","title":"Health Components"},"dependencies":{"items":{"type":"string"},"type":"array","title":"Dependencies","default":[]},"boot_after":{"items":{"type":"string"},"type":"array","title":"Boot After","default":[]},"prime_tasks":{"items":{"type":"string"},"type":"array","title":"Prime Tasks","default":[]}},"type":"object","required":["service","version","environment","started_at","uptime_seconds","features","mesh","routes","health_components"],"title":"StatusResponse","description":"Operational overview of a Campaign Brain service.","example":{"environment":"production","features":{"mcp":true,"mesh":true,"priming":true},"health_components":["database","redis"],"mesh":{"connected":true,"enabled":true,"gateway_url":"wss://mesh.campaignbrain.dev/ws/worker"},"routes":24,"service":"CB Workflow Engine","started_at":"2026-02-28T12:00:00+00:00","uptime_seconds":3600.5,"version":"0.3.0"}}},"securitySchemes":{"cbapp__OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"scopes":{},"tokenUrl":"api/auth/token"}}},"cbemail__ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for service-to-service authentication"},"cbemail__BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT token obtained from POST /api/sessions"},"cbfiles__ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authentication. Format: `cbfiles_{43-char-token}`"},"cbfiles__BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Use your API key as the token."},"cblinks__ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authentication. Format: `cblinks_{43-char-token}`"},"cblinks__BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Use your API key as the token."},"cbpayments__OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"scopes":{},"tokenUrl":"/auth/token"}}},"cbpulse__HTTPBearer":{"type":"http","scheme":"bearer"},"cbradio-qa__OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"scopes":{},"tokenUrl":"api/auth/token"}}},"cbradio__OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"scopes":{},"tokenUrl":"api/auth/token"}}},"cbsurveys__ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authentication. Format: `yasp_xxxxx`"},"cbsurveys__BearerAuth":{"type":"http","scheme":"bearer","description":"Bearer token authentication. Use your API key as the token."},"cbtenant__OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"scopes":{},"tokenUrl":"/api/auth/login"}}}}},"tags":[{"name":"cbai","description":"\n# CBAI - Campaign Brain AI\n\n**Unified API for multiple AI providers** - Access Claude, Ollama, Mistral, and Unstructured through a single, consistent interface.\n\n---\n\n## Overview\n\nCBAI abstracts multiple AI providers behind a unified REST API, enabling:\n\n- **Provider Fallback**: Automatic fallback from Claude → Ollama when API keys unavailable\n- **Streaming Support**: Real-time streaming responses for chat operations\n- **Local-First**: Ollama runs locally for fast, free inference\n- **Specialized Routing**: OCR routes to Mistral, embeddings to Ollama, reasoning to Claude\n\n---\n\n## Providers\n\n| Provider | Use Cases | Requirements |\n|----------|-----------|--------------|\n| **Ollama** | Chat, embeddings, vision, code | Local Ollama server |\n| **Claude** | Complex reasoning, summarization | `CLAUDE_API_KEY` |\n| **Mistral** | PDF OCR, document vision | `MISTRAL_API_KEY` |\n| **Unstructured** | Document parsing, element extraction | Unstructured API endpoint |\n\n---\n\n## Quick Start\n\n### Chat with Ollama (default)\n\n```bash\ncurl -X POST https://ai.campaignbrain.dev/api/v1/chat \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"messages\": [{\"role\": \"user\", \"content\": \"Hello, how are you?\"}]\n  }'\n```\n\n### Generate Embeddings\n\n```bash\ncurl -X POST https://ai.campaignbrain.dev/api/v1/embed \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"text\": \"Your text to embed\"}'\n```\n\n### Process PDF with OCR\n\n```bash\ncurl -X POST https://ai.campaignbrain.dev/api/v1/ocr \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\": \"https://example.com/document.pdf\"}'\n```\n\n---\n\n## Response Formats\n\nAll endpoints return JSON with consistent structure:\n\n- **Success**: Returns the response model directly\n- **Error**: Returns `{\"detail\": \"error message\"}` with appropriate HTTP status\n\n---\n\n## Streaming\n\nChat endpoints support streaming via `stream: true`. Streaming responses:\n\n- Return `text/plain` content type\n- Send chunks as they're generated\n- Are useful for real-time UI updates\n\n---\n\n## Rate Limits\n\nRate limits depend on the underlying provider:\n\n- **Ollama**: No rate limits (local)\n- **Claude**: Subject to Anthropic API limits\n- **Mistral**: Subject to Mistral API limits\n- **Unstructured**: Subject to your instance configuration\n","x-displayName":"AI API"},{"name":"cbairtable","description":"\n# CBAirtable\n\nMulti-tenant Airtable API wrapper with local DuckDB synchronization.\n\n## Overview\n\nCBAirtable provides tenant-scoped access to Airtable bases, tables, and records.\nIt can sync Airtable data into local DuckDB databases for fast, SQL-based querying\nwithout hitting Airtable rate limits.\n\n## Quick Start\n\n```bash\n# Check service health\ncurl https://airtable.campaignbrain.dev/health\n\n# Create a tenant\ncurl -X POST https://airtable.campaignbrain.dev/api/tenants \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"My Org\"}'\n\n# Store an Airtable credential\ncurl -X POST https://airtable.campaignbrain.dev/api/tenants/{tenant_id}/credentials \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"pat\": \"patXXX.YYYY\", \"base_id\": \"appABC\"}'\n```\n\n## Authentication\n\nAll tenant-scoped routes require a valid `tenant_id` in the path. The tenant must\nexist and be active. Airtable operations additionally require a stored credential.\n\n## Rate Limits\n\nAirtable API calls are subject to Airtable's own rate limits (5 requests/sec per base).\nUse the Sync + Query workflow to avoid repeated Airtable API hits.\n","x-displayName":"Airtable API Wrapper"},{"name":"cbapp","description":"\n# CampaignBrain API\n\nCampaign management platform for political field operations.\n\n## Features\n\n- **Contact Management**: Full CRUD for campaign contacts with tagging and custom fields\n- **Voter Database**: Search and filter i360 voter file with 80+ fields\n- **AI Chat**: Natural language queries using Claude AI\n- **Field Operations**: Work queues, segment assignments, and contact logging\n- **Surveys**: Create and deploy surveys with YASP integration\n- **Events**: Campaign event management with registration tracking\n\n## Authentication\n\nMost endpoints require JWT authentication:\n\n```\nAuthorization: Bearer <access_token>\n```\n\nObtain a token via `POST /api/auth/token` with username/password.\n\n## Quick Links\n\n- [Full API Documentation](https://github.com/Nominate-AI/cbapp/blob/main/docs/API.md)\n- [AI Chat Ecosystem](https://github.com/Nominate-AI/cbapp/blob/main/docs/AI-CHAT-ECOSYSTEM.md)\n","x-displayName":"Campaign Management Platform"},{"name":"cbauth","description":"\n## Overview\n\nCBAuth is the authentication and authorization service for the Campaign Brain\necosystem (`*.campaignbrain.dev`). It provides user-based login with JWT tokens,\ngroup-based permissions with hierarchical dot-notation, and Redis-cached\npermission lookups.\n\n## Authentication Flow\n\n```\nClient Request → NGINX → auth_request → CBAuth (/auth/verify)\n                                              ↓\n                              200 (valid JWT) → proxy to backend\n                              401 (no token)  → redirect to /auth/login\n                              403 (no perm)   → forbidden page\n```\n\n1. **User visits protected site** (e.g., `https://docs.campaignbrain.dev`)\n2. **NGINX intercepts** and calls `/auth/verify` to check authentication\n3. **No valid JWT** → User is redirected to `/auth/login`\n4. **User logs in** → JWT cookie set on `.campaignbrain.dev`\n5. **Cookie grants access** to all `*.campaignbrain.dev` subdomains\n\n## Permission System\n\nPermissions use dot-notation: `service.resource.action`\n\nExamples:\n- `campaigns.voters.read` — Read voter data\n- `campaigns.*.*` — Full access to campaigns service\n- `*.*.*` — Superadmin wildcard\n\n## API Keys\n\nAPI keys bypass cookie auth for machine-to-machine access:\n```\nX-API-Key: cbauth_xxx...\nAuthorization: Bearer cbauth_xxx...\n```\n\nKeys inherit the creating user's group permissions.\n","x-displayName":"Authentication Platform"},{"name":"cbbanking","description":"\n## Overview\n\ncbbanking collects the daily files a partner bank drops on its hosted SFTP,\narchives the raw bytes, parses them (BAI2 balance and transaction files, NACHA\nACH-return files), and serves the result as filterable JSON feeds. Today the\nonly partner is Huntington National Bank via OpenText's hosted SFTP. The\n`huntington_prod` profile is the live account; `huntington_prep` is the bank's\nsandbox, where test files land.\n\n## Key Capabilities\n\n### Collection (SFTP)\nMailbox-first pickup from every provisioned receive directory, idempotent on\nthe bank's `log_number`. Each file is archived with its sha256 and the\ndirectory it came from before anything else happens to it.\n\n### Parsing\nBAI2 files become per-account balance summaries and per-transaction rows;\nNACHA return files become per-return rows. Every envelope carries a\n`parse_status` (`ok`, `unknown-format`, `error`) and can be replayed from its\narchived bytes once a parser exists.\n\n### Export feeds\nFlat, paginated, filterable feeds with stable idempotency keys (`txn_uid`,\n`balance_uid`, `return_uid`), plus a server-side `/summary` rollup that splits\nevery figure into external cash flow, internal transfers, and gross.\n\n### Fabric datasets\nSelf-describing datasets (`huntington.txns`, `huntington.balances`) streamed\nas NDJSON for the cbmodels federation registry.\n\n## How to Use This API\n\n**Authentication.** cbauth JWT via the NGINX `auth_request` gate on\n`https://banking.campaignbrain.dev`; permission scope `cbbanking.*.*`. From\nanother Campaign Brain service or an Apex app, call over the mesh instead:\n`meshProxy(\"cbbanking\", \"/api/v1/export/summary?profile=huntington_prod\")`.\nEvery GET here is also exposed as an MCP tool.\n\n**Quick start.**\n\n```bash\n# What did the bank send, and did it parse?\ncurl -s \"$BANKING/api/v1/inbound/envelopes?profile=huntington_prod&limit=5\"\n\n# Cash in and out for one business date, external vs internal\ncurl -s \"$BANKING/api/v1/export/summary?profile=huntington_prod&as_of_from=2026-09-09&as_of_to=2026-09-09\"\n\n# That day's transactions\ncurl -s \"$BANKING/api/v1/export/txns?profile=huntington_prod&as_of_from=2026-09-09&as_of_to=2026-09-09\"\n```\n\n**Money is integer cents.** Every `*_cents` field is an integer. The only\nfloats on this API are the Fabric `/datasets` rows, which convert to dollars\non the way out for that one consumer. `as_of_date` is the bank's business\ndate, always one calendar day before the file was delivered.\n\n**Prefer `/summary` to summing `/txns`.** Totals are computed over the whole\nfiltered set on the server. A client that sums one page reports a confident\nwrong number.\n\n## Typical Workflow\n\n```\n08:05 ET  POST /api/v1/sftp/huntington_prod/pickup     cbcron   new envelopes archived\n08:10 ET  POST /api/v1/parsing/run                     cbcron   parse_status stamped\nany time  GET  /api/v1/inbound/envelopes?parse_status=error     anything to chase?\nany time  GET  /api/v1/export/summary?as_of_from=..&group_by=account\nany time  GET  /api/v1/export/txns?since_parsed=<cursor>        incremental sync\n```\n\n## Data Sources\n\nOne BAI2 file per delivery day from Huntington (Tuesday through Saturday,\ncarrying the prior business date), three accounts, roughly 10 to 60\ntransactions a day: about 90 envelopes and 1,400 transactions since\n2026-05-11. ACH return files arrive separately and so far only as the bank's\nTEST samples, which `/export/returns` flags on every row.\n\n## Rate Limits\n\nNone enforced by this service. Page size is capped at 5,000 rows on the\nexport feeds and 1,000 on `/inbound/envelopes`. The SFTP endpoints open a\nconnection to the bank per call: do not poll them, and never schedule\nanything that touches the bank before 08:00 ET (Huntington's request,\n2026-09-14).\n","x-displayName":"Banking Integration"},{"name":"cbbilling","description":"\n# CBBILLING\n\nUsage + entitlement billing for the Campaign Brain platform. Assembles\nper-tenant **invoices** from live fleet data against a single rate card, and\npersists the generated invoices to a ledger.\n\nTwo billing axes, read-only from the fleet:\n\n- **Structural** — platform base by `cbtenant` app_type, per-seat from\n  `cbauth.user_tenants`, and canonical **App-Bundle** grants\n  (`cbtenant.tenant_apps` + the local grant ledger).\n- **Usage** — AI cost/tokens from **cbdatabase**'s `ai_usage` catalog over the\n  mesh (infrastructure#239; falls back to cbai's usage.db), plus metered SKUs\n  (API calls / SMS / storage) from the mesh tap where the tenant appears, else a\n  flagged deterministic **sample**.\n\n> The rate card (`ratecard.yaml`) is **illustrative, not contractual** — this is\n> an invoice generator, not a payment processor.\n\n## Quick Start\n\n```bash\ncurl https://billing.campaignbrain.dev/health\ncurl https://billing.campaignbrain.dev/api/v1/ratecard\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/ratecard` | The rate card |\n| `/api/v1/tenants` | Known tenants |\n| `/api/v1/bundles` · `/api/v1/grants` | App-Bundle catalog + grant ledger |\n| `/api/v1/invoices/preview` | Build an invoice (no persist) |\n| `/api/v1/invoices` | Generate + persist / list invoices |\n| `/api/v1/invoices/{id}` | Fetch / soft-delete a persisted invoice |\n| `/health` · `/status` · `/docs` | Standard CBApp surface |\n","x-displayName":"Billing API"},{"name":"cbcalendar","description":"\n# CBCALENDAR\n\nMulti-provider, multi-tenant calendar service for the Campaign Brain fleet.\ncbcalendar is the single seam other CB services (cbgreenroom, CBPulse, future\nCBApps) write calendar events through — instead of each integrating Google /\nApple / Outlook directly, they POST a provider-agnostic `EventDraft` here and\ncbcalendar handles provider routing, credential storage, and the audit trail.\n\n## What This Service Does\n\n- **Provider abstraction.** One event shape (`EventDraft` → `Event`) regardless\n  of whether it lands on Google Workspace (Phase 1), Apple CalDAV / Outlook\n  (Phase 2), or the in-process MockProvider (demo + tests).\n- **Demo / live safety.** Every tenant starts in `demo` mode with\n  `outbound_locked=true`. In demo — or while locked — calls are transparently\n  suppressed to MockProvider: the response shape is identical; only the `mode`\n  field distinguishes a real provider write from a suppressed one. Promotion to\n  `live` is a deliberate, evidence-gated operation.\n- **Hash-chained audit.** Every create / update / cancel / connection / mode\n  change appends a tamper-evident, per-tenant, append-only audit record.\n  cbgreenroom polls `/api/v1/audit` to mirror these into its legal-wall log.\n- **Source linkage.** Each event carries `source_system` + `source_uri` so it\n  traces back to the originating record (e.g. a cbgreenroom booking).\n\n## Tenancy & Auth\n\nTenant-scoped endpoints require the `X-CB-Tenant` header (slug form, e.g.\n`perry-2026`). Mesh callers also send `X-CB-Source-Service`, which labels audit\nevents. Full cbauth integration (Bearer JWT, permission gating) lands in\nPhase 1; the dependency seam already exists.\n\n## Quick Start\n\n```bash\ncurl https://calendar.campaignbrain.dev/health\n```\n\n## Operational Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Liveness check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Warm cold-start resources |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n\nAll domain endpoints are mounted under `/api/v1`.\n","x-displayName":"Calendar"},{"name":"cbcampaign","description":"\n# CBCAMPAIGN\n\nConsolidated campaign-management API (CRM, field ops, comms) — supersedes cbapp\n\n## Quick Start\n\n```bash\ncurl https://campaign.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Campaign API"},{"name":"cbcaptive","description":"\n# CBCAPTIVE\n\nGuest-capture captive portal for VI properties. The public portal page\n(`/p/{property}`) collects contact info into cbdatabase, then authorizes the\nguest device through cbunifi's guarded guests surface over the mesh.\n\n## Quick Start\n\n```bash\ncurl https://captive.campaignbrain.dev/api/v1/config\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/p/{property}` | Captive portal page (public) |\n| `/api/portal/{property}/connect` | Contact-info connect (public) |\n| `/api/portal/{property}/voucher` | Voucher connect (public) |\n| `/api/v1/config` | Non-secret wiring status |\n| `/health` | Health check |\n| `/docs` | Swagger UI |\n","x-displayName":"cbcaptive"},{"name":"cbcast","description":"\n# CBCAST — Campaign Brain Casting / Display Layer\n\nCBCAST drives **physical signage panels** (the \"mosaic kiosk\" boxes) and\n**server-side video streams** from a single routing core. It serves\ncbweave-rendered SVG *scenes* to the device agents that paint TVs, and it can\nrender any bound channel to HLS / YouTube Live without a box.\n\n## Mental Model\n\nThe data model is four nested concepts:\n\n| Concept | What it is |\n|---------|-----------|\n| **Scene** | A renderable unit — a cbweave markdown spec, a pre-rendered SVG, or (Enhanced channels) a JSON data snapshot. Content-addressed: identical content → identical id. |\n| **Channel** | A feed devices bind to. Carries a *current scene* and can run a *slideshow* playlist. `kind` is `base` (SVG kiosk) or `enhanced` (a web app / URL rendered by the box's browser). |\n| **Program** | An ordered bundle of channels (\"boards\") a device sequences through. `Program : Channel :: Channel : Scene`. Devices on one program share a cursor, so a whole room moves together (the server-driven sequencer). |\n| **Device** | A box. `physical` boxes poll for their channel and paint a panel; `virtual` devices are stream workers that broadcast their bound channel to HLS/YouTube. |\n\n## How a Box Gets Its Picture\n\n1. A box **claims** itself (`POST /api/v1/devices/claim`) and receives an opaque\n   device **token**.\n2. It long-polls **`GET /api/v1/channels/_self/current`** (the gate-friendly\n   self path) with its token, an `If-None-Match` ETag, and `?wait=`.\n3. cbcast resolves the box's *effective channel* (its program's current board,\n   or its static binding), renders the current scene to an SVG sized for the\n   box's reported resolution, and returns it — or `304` (unchanged), or `204`\n   (standby). Pending device commands ride back on response headers.\n\n## Multitenancy\n\nControl routes are tenant-scoped (defense in depth — see DESIGN §11): a\ntenant-scoped caller may only touch resources in its own tenant. The boundary is\nread from the edge-injected `X-User-Tenant` header; an empty tenant on either\nside (legacy / unassigned / loopback admin) is permitted, so the single-tenant\nfleet is unaffected.\n\n## Quick Start\n\n```bash\ncurl https://cast.campaignbrain.dev/health\n```\n\n## Service Routes (framework-provided)\n\n| Path | Description |\n|------|-------------|\n| `/health` | Liveness check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Warm cold-start resources + resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Casting / Display Layer"},{"name":"cbconvo","description":"\n# CBCONVO\n\nAI conversation layer for Victory Text — goal-directed voter texting on cbea's mechanics.\n\n**THE GATE:** nothing is sent to any number not in `CONVO_ALLOWED_CONTACTS`,\nand nothing at all unless `CONVO_LIVE=1`. Both default closed. See\n`docs/ENGINE.md`.\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `GET /api/v1/programmes` | Loaded programmes and their goal keys |\n| `POST /api/v1/programmes/{id}/start` | Open a claim, compose the opening (gated; 403 when refused) |\n| `GET /api/v1/drafts` | Drafts awaiting a human click (`?status=`, default `pending`) |\n| `POST /api/v1/drafts/{id}/approve` | The human click — send one draft (gated) |\n| `POST /api/v1/drafts/{id}/reject` | Discard one draft |\n| `POST /api/v1/webhook/sms` | Inbound from cbsms (`sender`, `message`) — the one unauthenticated route |\n| `GET /api/v1/claims` | Claims, `?status=` |\n| `GET /api/v1/transcript?contact=` | Turns in `vt_response` shape |\n| `GET /api/v1/audit` | Every attempted send with its gate verdict |\n| `GET /api/v1/optouts` | Revocations, for Victory Text suppression |\n| `GET /api/v1/gate` | Live flag + allowlist COUNT + transport/model names |\n| `/health`, `/status`, `/prime`, `/docs` | CBApp standard |\n\nPer-endpoint error contracts (401/403/404/409/502/503) are declared on the\nroutes themselves and appear in this schema — a caller must read them, because\na 403 from THE GATE is final and auditing it is not the same as retrying it.\n","x-displayName":"cbconvo"},{"name":"cbcrm","description":"\n# CBCRM\n\nCRM service — canonical person/contact golden records, custom fields, whip status, interactions, dedup/merge (on cbdatabase)\n\n## Quick Start\n\n```bash\ncurl https://crm.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/persons` | Create / search canonical persons |\n| `/api/v1/persons/upsert` | Create-or-merge (the cblist promote seam) |\n| `/api/v1/persons/{id}/interactions` | Log / list interactions |\n| `/api/v1/custom-fields` | Define / list tenant custom fields |\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"CRM API"},{"name":"cbcron","description":"\n# CBCRON - Configurable Scheduled Task Engine\n\n---\n\n## Overview\n\nCBCRON provides:\n\n- **Job Registry**: Create and manage scheduled HTTP dispatch jobs\n- **Cron Scheduling**: 5-field cron expressions with timezone support\n- **HTTP Dispatch**: Configurable method, headers, payload, timeout\n- **Retry Logic**: Automatic retries with configurable delay\n- **Run History**: Execution log with status, response, timing\n- **Overlap Protection**: Prevents concurrent execution of the same job\n\n---\n\n## Authentication\n\nAll endpoints require an API key via the `X-API-Key` header.\n","x-displayName":"Scheduled Task Engine"},{"name":"cbdatabase","description":"\n# cbdatabase - DuckDB Control Plane\n\n**Mint, query, and back up DuckDB databases through one safe owner.**\n\n---\n\n## Overview\n\ncbdatabase is the persistence control plane for Campaign Brain. It is the single\nprocess that owns every managed DuckDB file, exposing them over HTTP so the rest\nof the platform never has to open a file directly or fight over a lock.\n\n- **Single-connection by construction**: each database has one connection, pinned\n  to one worker thread for its whole life. The single-connection rule becomes\n  structural, not a convention you have to remember.\n- **Serialized I/O with backpressure**: every database has a bounded request\n  queue. Work runs in order; a full queue returns a clean busy signal instead of\n  growing without limit.\n- **Schema-driven minting**: hand it a declarative schema and it materializes the\n  file, tables, and indexes in one queued script, then registers it.\n- **Backups and restore**: consistent file-level snapshots and in-place restore,\n  taken as queued jobs so no write is mid-flight.\n\n---\n\n## Authentication\n\nPass a cbauth API key via the `X-API-Key` header; it must hold `cbdatabase.*.*`\n(the fleet `svc:agent` key qualifies). Health and docs endpoints are open.\nWhether a missing/invalid key is refused (401/403) or merely logged depends on\nthe service's `CBDATABASE_AUTH_MODE` — send the key either way.\n\n---\n\n## Deployment\n\nBare metal, on premise. Set CB_DATA_ROOT to the directory that should hold the\ndatabase files and backups.\n","x-displayName":"DuckDB Control Plane"},{"name":"cbdistricts","description":"\n# Congressional Districts API\n\nRESTful API providing comprehensive data for all **441 U.S. Congressional Districts**\n(119th Congress, 2025-2027). Combines Census Bureau demographics with geospatial\nboundaries and Wikipedia-sourced political information.\n\n---\n\n## Quick Start\n\n### Get all districts\n```bash\nGET /api/v1/districts?limit=50&format=json\n```\n\n### Get a specific district by GEOID\n```bash\nGET /api/v1/districts/0601  # California's 1st District\n```\n\n### Get district demographics\n```bash\nGET /api/v1/demographics/0601\n```\n\n### Get district geometry (GeoJSON)\n```bash\nGET /api/v1/districts/0601/geometry\n```\n\n---\n\n## Core Concepts\n\n### GEOID Format\nDistricts are identified by a **4-5 character GEOID**:\n- First 2 digits: State FIPS code (e.g., `06` = California)\n- Last 2 digits: District number (e.g., `01` = 1st District)\n- At-large districts use `00` (e.g., `5000` = Vermont At-Large)\n\n**Examples:**\n| GEOID | Description |\n|-------|-------------|\n| `0601` | California's 1st District |\n| `3614` | New York's 14th District |\n| `4800` | Texas At-Large (if applicable) |\n| `5000` | Vermont At-Large |\n\n### State FIPS Codes\nCommon state FIPS codes for reference:\n| State | FIPS | State | FIPS |\n|-------|------|-------|------|\n| California | `06` | Texas | `48` |\n| Florida | `12` | New York | `36` |\n| Pennsylvania | `42` | Illinois | `17` |\n| Ohio | `39` | Georgia | `13` |\n| Michigan | `26` | North Carolina | `37` |\n\n---\n\n## Data Sources\n\n| Source | Data Type | Update Frequency |\n|--------|-----------|------------------|\n| [Census TIGER/Line](https://www.census.gov/geographies/mapping-files/time-series/geo/tiger-line-file.html) | Geographic boundaries | Every 2 years |\n| [American Community Survey (ACS)](https://www.census.gov/programs-surveys/acs) | Demographics (DP02-DP05) | Annual (1-year estimates) |\n| [Wikipedia](https://en.wikipedia.org/wiki/List_of_United_States_congressional_districts) | Representatives, party, Cook PVI | Live cache (48hr TTL) |\n| [FCC](https://www.fcc.gov/media/radio/am-query) | Radio station coverage | Quarterly |\n\n---\n\n## Response Formats\n\n### JSON (default)\nStandard paginated JSON response with metadata:\n```json\n{\n  \"data\": [...],\n  \"meta\": {\n    \"total\": 441,\n    \"limit\": 50,\n    \"offset\": 0,\n    \"has_more\": true\n  }\n}\n```\n\n### GeoJSON\nRFC 7946 compliant GeoJSON for mapping applications:\n```json\n{\n  \"type\": \"FeatureCollection\",\n  \"features\": [\n    {\n      \"type\": \"Feature\",\n      \"id\": \"0601\",\n      \"properties\": {...},\n      \"geometry\": {\"type\": \"Polygon\", \"coordinates\": [...]}\n    }\n  ]\n}\n```\n\n---\n\n## Authentication\n\n**No authentication required.** This API is public and rate-limited to prevent abuse.\n\n---\n\n## Rate Limits\n\n| Endpoint Type | Rate Limit |\n|---------------|------------|\n| List endpoints | 100 req/min |\n| Detail endpoints | 200 req/min |\n| GeoJSON endpoints | 50 req/min |\n\n---\n\n## Caching\n\nResponses are cached in Redis for performance:\n| Data Type | Cache TTL |\n|-----------|-----------|\n| Census data | 24 hours |\n| Wikipedia data | 48 hours |\n| GeoJSON polygons | 7 days |\n| Radio coverage | 1 hour |\n\nUse `DELETE /api/v1/cache` to clear cache (admin only).\n\n---\n\n## Error Responses\n\nAll errors follow a consistent format:\n```json\n{\n  \"error\": \"not_found\",\n  \"detail\": \"District 9999 not found\",\n  \"status_code\": 404\n}\n```\n\n| Status Code | Meaning |\n|-------------|---------|\n| `400` | Invalid request parameters |\n| `404` | Resource not found |\n| `422` | Validation error |\n| `500` | Internal server error |\n\n---\n\n## SDK & Integration Examples\n\n### Python\n```python\nimport requests\n\nresponse = requests.get(\n    \"https://districts.campaignbrain.dev/api/v1/districts\",\n    params={\"state_fips\": \"06\", \"format\": \"json\"}\n)\ndistricts = response.json()[\"data\"]\n```\n\n### JavaScript\n```javascript\nconst response = await fetch(\n  \"https://districts.campaignbrain.dev/api/v1/districts?state_fips=06\"\n);\nconst { data: districts } = await response.json();\n```\n\n### cURL\n```bash\ncurl -X GET \"https://districts.campaignbrain.dev/api/v1/districts?state_fips=06&limit=10\"\n```\n\n---\n\n## Related Resources\n\n- **Interactive Map**: [districts.campaignbrain.dev](https://districts.campaignbrain.dev)\n- **Campaign Brain Platform**: [campaignbrain.dev](https://campaignbrain.dev)\n- **GitHub Repository**: [github.com/campaignbrain/cbdistricts](https://github.com/campaignbrain/cbdistricts)\n","x-displayName":"Congressional Districts"},{"name":"cbea","description":"\n# CBEA — Campaign Brain Executive Assistant\n\nA conversational AI agent for Campaign Brain executives. CBEA owns its own\n**tool-use loop** over the `cbai` chat endpoint and reaches the rest of the\nfleet over the ZVR mesh — `cbpulse` (portfolio data), `cbfront` (email /\nconversations), and `cbscout` (adaptive memory).\n\n## Capabilities\n\n- **Conversational Q&A** against portfolio data via `cbpulse`\n- **Email & conversation management** via `cbfront`\n- **Adaptive behavior** — per-contact memory persisted in `cbscout`\n- **Multi-transport** — WhatsApp and SMS webhooks plus a direct HTTP/SSE\n  chat API for MCP and web consumers\n- **Deterministic workflows** — Cat 3 scheduled jobs (e.g. the daily GitHub\n  activity digest) triggered by `cbcron`\n- **Distributed tracing** — every inbound message produces an OTel trace tree,\n  inspectable over the Traces API\n\n## Visible-Plan UX (the agent loop)\n\nBefore executing tool calls, the loop classifies the first batch of\n`tool_use` blocks into one of three modes:\n\n1. **Mode 1 — direct execute:** a single read tool runs immediately.\n2. **Mode 2 — plan, execute, summarize:** 2+ read tools — a one-line plan is\n   synthesized and prepended to the final answer.\n3. **Mode 3 — present & await approval:** any *write* tool halts the loop and\n   waits for the user's explicit confirmation before firing.\n\n## Transports\n\n| Transport | Inbound | Notes |\n|-----------|---------|-------|\n| WhatsApp  | `POST /api/v1/webhook/whatsapp` | Meta/Twilio handshake on `GET` |\n| SMS       | `POST /api/v1/webhook/sms`      | Twilio form-encoded or JSON; unknown senders ignored |\n| MCP / Web | `POST /api/v1/chat`             | Synchronous; SSE variant at `/chat/stream` |\n\n## Tracing\n\nEach inbound message produces one trace rooted at `cbea.handle_message`, with\nchild spans for identity resolution, context load, the `cbai` tool-use call\n(and a span per tool), render, and transport send. Spans are stored in the same\nSQLite file as conversation history and are exposed read-only under\n`/api/v1/traces`.\n","x-displayName":"Executive Assistant"},{"name":"cbelections","description":"\n# CBELECTIONS\n\nElection Night engine: live results, broadcast boards, and the any-race registry (backend of the cbloom Election Night bundle)\n\n## Quick Start\n\n```bash\ncurl https://elections.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Elections API"},{"name":"cbemail","description":"\n## Overview\n\n**cbemail** is a headless email client API that enables external applications to manage\nIMAP/SMTP mailboxes through a clean REST interface. Built for automation, AI agents,\nand programmatic email access without the complexity of direct protocol handling.\n\n```\n┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐\n│   Your App /    │────▶│   cbemail API   │────▶│  IMAP / SMTP    │\n│   AI Agent      │◀────│   (REST)        │◀────│  Mail Server    │\n└─────────────────┘     └─────────────────┘     └─────────────────┘\n                               │\n                               ▼\n                        ┌─────────────────┐\n                        │   DuckDB        │\n                        │  (sessions,     │\n                        │   credentials)  │\n                        └─────────────────┘\n```\n\n## Key Features\n\n- **Session-Based Authentication**: Register email credentials once, receive a JWT token for all subsequent operations\n- **Provider Auto-Detection**: Automatically configures IMAP/SMTP servers for Gmail, Outlook, Yahoo, and iCloud\n- **Secure Credential Storage**: Passwords encrypted at rest using Fernet symmetric encryption\n- **Full IMAP Support**: List folders, read/search/move/delete messages, manage flags\n- **SMTP Integration**: Send single or batch emails with HTML support\n- **Stateless Operations**: Each API call creates fresh mail server connections (no connection pooling)\n\n## Authentication\n\n### Creating a Session\n\nFirst, register your email credentials to create a session:\n\n```bash\ncurl -X POST https://email.campaignbrain.dev/api/sessions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"email\": \"user@gmail.com\",\n    \"password\": \"your-app-password\"\n  }'\n```\n\n**Response:**\n```json\n{\n  \"session_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"token\": \"eyJhbGciOiJIUzI1NiIs...\",\n  \"email\": \"user@gmail.com\",\n  \"provider\": \"gmail\",\n  \"expires_in\": 1800\n}\n```\n\n### Using the Token\n\nInclude the JWT token in all subsequent requests:\n\n```bash\ncurl https://email.campaignbrain.dev/api/mailbox/messages \\\n  -H \"Authorization: Bearer eyJhbGciOiJIUzI1NiIs...\"\n```\n\n### API Key Authentication\n\nFor service-to-service communication, use an API key:\n\n```bash\ncurl https://email.campaignbrain.dev/api/mailbox/folders \\\n  -H \"X-API-Key: your-api-key\"\n```\n\n## Provider Detection\n\nThe API automatically detects and configures mail servers based on your email domain:\n\n| Domain Pattern | Provider | IMAP Server | SMTP Server |\n|----------------|----------|-------------|-------------|\n| `gmail.com`, `googlemail.com` | Gmail | `imap.gmail.com:993` | `smtp.gmail.com:587` |\n| `outlook.com`, `hotmail.com`, `live.com` | Outlook | `outlook.office365.com:993` | `smtp.office365.com:587` |\n| `yahoo.com`, `ymail.com` | Yahoo | `imap.mail.yahoo.com:993` | `smtp.mail.yahoo.com:587` |\n| `icloud.com`, `me.com`, `mac.com` | iCloud | `imap.mail.me.com:993` | `smtp.mail.me.com:587` |\n| Other domains | Custom | *Must specify* | *Must specify* |\n\nFor custom domains, provide server configuration explicitly in the session creation request.\n\n## Quick Start Examples\n\n### Read Latest Emails\n\n```python\nimport requests\n\n# Create session\nsession = requests.post(\n    \"https://email.campaignbrain.dev/api/sessions\",\n    json={\"email\": \"user@gmail.com\", \"password\": \"app-password\"}\n).json()\n\n# List messages\nmessages = requests.get(\n    \"https://email.campaignbrain.dev/api/mailbox/messages\",\n    headers={\"Authorization\": f\"Bearer {session['token']}\"},\n    params={\"folder\": \"INBOX\", \"limit\": 10}\n).json()\n\nfor msg in messages[\"messages\"]:\n    print(f\"{msg['from_address']}: {msg['subject']}\")\n```\n\n### Send an Email\n\n```python\nrequests.post(\n    \"https://email.campaignbrain.dev/api/send\",\n    headers={\"Authorization\": f\"Bearer {token}\"},\n    json={\n        \"to\": \"recipient@example.com\",\n        \"subject\": \"Hello from cbemail\",\n        \"body\": \"This email was sent via the cbemail API!\"\n    }\n)\n```\n\n## Error Handling\n\nThe API returns standard HTTP status codes:\n\n| Code | Meaning |\n|------|---------|\n| `200` | Success |\n| `201` | Created (new session/folder) |\n| `204` | Success (no content returned) |\n| `400` | Bad request (invalid credentials, missing fields) |\n| `401` | Unauthorized (invalid/expired token) |\n| `403` | Forbidden (accessing another session's resources) |\n| `404` | Not found (session, message, folder) |\n| `422` | Validation error (invalid email format, etc.) |\n| `500` | Server error (mail server connection failed) |\n\n## LLM Integration Notes\n\nThis API is designed for programmatic access by AI agents and automation tools:\n\n- **Idempotent Operations**: GET requests are safe to retry\n- **Predictable Responses**: Consistent JSON schemas across all endpoints\n- **Detailed Errors**: Error messages include actionable information\n- **Batch Support**: Send multiple emails in a single request to reduce API calls\n","x-displayName":"Headless Email Client"},{"name":"cbenrichment","description":"\n# CBENRICHMENT\n\nRecord enrichment service — appends and normalizes attributes on records the fleet already owns\n\n## Quick Start\n\n```bash\ncurl https://enrichment.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/jobs/probe` | POST — probe a contact list: parse, map columns, aggregate profile |\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"cbenrichment"},{"name":"cbesig","description":"\n# CBESIG — E-Signature Service\n\nCBESIG is the Campaign Brain fleet's e-signature gateway. It wraps the\n[BoldSign](https://boldsign.com) REST API behind a stable, mesh-friendly\nsurface so other services (notably **cbpulse** for HR onboarding,\nwrite-ups, and overtime authorizations) can send documents for signature,\npoll signing status, and retrieve completed PDFs without ever holding a\nBoldSign credential themselves.\n\n## What it does\n\n- **Send for signature** — fire a document from a reusable BoldSign\n  *template* (the common case) or from a raw, base64-encoded PDF.\n- **Track signing** — poll a document's status and per-signer progress.\n- **Nudge signers** — re-email outstanding signers without invalidating\n  signatures already collected.\n- **Download** — pull the completed, signed PDF.\n- **Archive** — a read-only, unified view across both BoldSign and\n  legacy DocuSign envelopes, served from an in-memory manifest with\n  presigned download URLs minted via cbfiles.\n\n## Quick start\n\n```bash\ncurl https://esig.campaignbrain.dev/health\ncurl https://esig.campaignbrain.dev/api/v1/templates\n```\n\n## How sending works\n\n1. `GET /api/v1/templates/detailed` — discover a template's role slots and\n   form fields.\n2. `POST /api/v1/documents/send-from-template` — map each role index to a\n   real signer (name + email) and send.\n3. `GET /api/v1/documents/{id}/status` — poll until every signer completes.\n4. `GET /api/v1/documents/{id}/download` — retrieve the signed PDF.\n\n## Authentication & dependencies\n\nCalls arrive through the ZVR mesh; the service depends on **cbfiles**\n(presigned archive URLs) and talks outbound to BoldSign. BoldSign\ncredentials live in this service only — callers never see them.\n\n## Operational endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Liveness check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Warm cold-start resources (archive parse) |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"E-Signature Service"},{"name":"cbetl","description":"\n## Contact Data Processing ETL Service\n\nCB ETL provides address normalization and parsing services for Campaign Brain.\n\n### Features\n\n- **Address Normalization**: Standardize addresses with state abbreviations, street types, and ZIP formatting\n- **Address Parsing**: Parse raw address strings into structured components using libpostal\n- **Batch Processing**: Process multiple addresses in a single request\n- **Deduplication Hashes**: Generate unique hashes for address-based deduplication\n\n### External Dependencies\n\n- **libpostal**: International address parsing library (optional, with regex fallback)\n","x-displayName":"Contact Data ETL"},{"name":"cbfiles","description":"\n# CBFiles API\n\nFile management service wrapping MinIO object storage. Provides a simple REST API\nfor managing buckets and files with API key authentication.\n\n---\n\n## Quick Start\n\n### 1. Get an API Key\n\nBootstrap an admin user (first-time setup):\n```bash\npython -m cbfiles.cli bootstrap --name \"Admin\" --email \"admin@example.com\"\n```\n\n### 2. Authenticate Requests\n\nInclude your API key in every request using either header:\n```\nX-API-Key: cbfiles_xxxxx...\n```\nor\n```\nAuthorization: Bearer cbfiles_xxxxx...\n```\n\n### 3. Create a Bucket\n\n```bash\ncurl -X POST https://files.campaignbrain.dev/api/v1/buckets \\\n  -H \"X-API-Key: YOUR_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"my-bucket\", \"access\": \"private\"}'\n```\n\n### 4. Upload a File\n\n```bash\ncurl -X POST https://files.campaignbrain.dev/api/v1/buckets/my-bucket/files/path/to/file.pdf \\\n  -H \"X-API-Key: YOUR_KEY\" \\\n  -F \"file=@local-file.pdf\"\n```\n\n---\n\n## Hosts\n\n| Service | URL | Purpose |\n|---------|-----|---------|\n| **API** | `https://files.campaignbrain.dev` | Authenticated file operations |\n| **CDN** | `https://cdn.campaignbrain.dev` | Public file access (no auth) |\n\n---\n\n## CDN Hosting\n\nHost static assets (CSS, JS, fonts, images) on `cdn.campaignbrain.dev`:\n\n**1. Upload to the `cdn` bucket:**\n```bash\ncurl -X POST https://files.campaignbrain.dev/api/v1/buckets/cdn/files/css/app.v2.css \\\n  -H \"X-API-Key: YOUR_KEY\" \\\n  -F \"file=@app.css\"\n```\n\n**2. Reference on your site:**\n```html\n<link rel=\"stylesheet\" href=\"https://cdn.campaignbrain.dev/css/app.v2.css\">\n<script src=\"https://cdn.campaignbrain.dev/js/htmx.min.js\"></script>\n```\n\nClean URLs for `css/`, `js/`, and `fonts/` prefixes. Other paths use\n`cdn.campaignbrain.dev/api/v1/public/cdn/{path}`.\n\nResponses include `Cache-Control: public, immutable` (1 year). Use versioned\nfilenames for cache busting.\n\n---\n\n## Authentication\n\nAll endpoints except `/health`, `/`, and `/api/v1/public/*` require an API key.\n\n**Supported Headers:**\n- `X-API-Key: cbfiles_xxxxx...`\n- `Authorization: Bearer cbfiles_xxxxx...`\n\n**Key Format:** `cbfiles_{43-character-random-token}`\n\n---\n\n## Access Control\n\n| Bucket Access | Owner | Other Users | Anonymous |\n|---------------|-------|-------------|-----------|\n| `private` | Read/Write | ❌ No access | ❌ No access |\n| `public_read` | Read/Write | Read only | Read only (via CDN) |\n\n---\n\n## Error Responses\n\nAll errors return JSON with this structure:\n```json\n{\n  \"detail\": \"Human-readable error message\"\n}\n```\n\n| Status | Meaning |\n|--------|---------|\n| 400 | Invalid request (bad UUID, missing field) |\n| 401 | Missing or invalid API key |\n| 403 | Valid key but no permission for this resource |\n| 404 | Bucket or file not found |\n| 409 | Conflict (duplicate bucket name, etc.) |\n| 500 | Server error (MinIO unavailable, etc.) |\n","x-displayName":"File Storage & CDN"},{"name":"cbforge","description":"## Overview\n\ncbforge turns a **composition** of fleet services into a service of its own. Every\nregistered workload (`ServiceSpec`) is exposed as a set of entrypoints; calling one\nruns its graph against the fleet with the caller's identity, and the graph's\npersistent KV is the service's memory.\n\n## Quick start\n\n```bash\ncbcli cbforge services list                       # every composed service + manifest\ncbcli cbforge services run --params '{\"service\": \"<name>\", \"entrypoint\": \"<ep>\", ...}'\n```\n\n`GET /api/v1/services` lists manifests; `POST /api/v1/services/{service}/{entrypoint}`\nruns a graph with the body as inputs; the `GET` form takes inputs as query params.\n\n## Identity\n\nPer request, never shared: the rubric-resolved caller (`X-User-Email`, forwarded by\nthe mesh) becomes the run's actor, so every outbound call a graph makes is attributed\nto the human who clicked. Runtimes are cached per (service, actor, tenant).\n\n## Authentication\n\nOn-host only today (`127.0.0.1:32560`, no public vhost). Permissions:\n`cbforge.services.run` for POST, `cbforge.services.read` for GET.\n","x-displayName":"Forge API"},{"name":"cbfront","description":"\n# CBFRONT - Front App Email Integration\n\n**Campaign Brain service for shared-inbox email access via the\n[Front](https://front.com) API (`api2.frontapp.com`).**\n\nCBFront is a thin, typed, multi-account proxy in front of Front. Every endpoint\nmaps to a Front operation, normalizes the response into a stable CB model\n(stripping Front's `_results`/`_pagination` envelopes and decoding HTML message\nbodies to plain text), and enforces Campaign Brain access control on top.\n\n---\n\n## Overview\n\n| Area | What it does |\n|------|--------------|\n| **Inboxes** | List and inspect Front inboxes |\n| **Conversations** | Search, read, update, assign, comment on, and tag email threads |\n| **Messages** | Read messages, send new emails (via a channel), reply to threads |\n| **Drafts** | Create conversation/channel drafts and delete them |\n| **Contacts** | Search and view contact details |\n| **Tags** | List tags; apply/remove them on conversations |\n| **Canned Responses** | List and retrieve reusable message templates |\n| **Teammates** | List teammates and assign conversations |\n| **Analytics** | Create and poll asynchronous analytics exports |\n\n---\n\n## Multi-account model\n\nCBFront serves several Front workspaces from one process. Every resource path is\nscoped under an account slug:\n\n```\n/accounts/{account_id}/conversations\n/accounts/{account_id}/inboxes/{inbox_id}\n```\n\nAccounts (and their Front API tokens) are declared in `accounts.yaml` and loaded\ninto a per-account client registry at startup. `GET /accounts` lists the\naccounts the caller may reach. Operators can hot-reload the registry after a\ntoken rotation via `POST /admin/reload-accounts` (gated by `cbfront.admin`) —\nno restart required.\n\n---\n\n## Authentication & authorization\n\n- All endpoints require a Campaign Brain credential (cookie or `X-API-Key`),\n  verified through the shared sysforge auth layer.\n- Per-account access is gated by the `cbfront.{account}.access` permission.\n- Optional per-inbox enforcement (`cbfront.{account}.{inbox_id}`) can be turned\n  on; when off, inbox access is audit-logged but never denied (observation\n  mode).\n\n---\n\n## Response shape\n\nList endpoints return `{\"results\": [...], \"pagination\": {\"next\": \"<token>\"}}`.\nPass the opaque `next` token back as `page_token` to fetch the following page;\n`null` means the last page. Mutations that Front answers with `204 No Content`\nare surfaced as `204` here too.\n","x-displayName":"Front Email Proxy"},{"name":"cbgeo","description":"\nGeocoding service for Campaign Brain using Nominatim with OpenStreetMap data.\n\n## Endpoints\n\n- **Search**: Convert addresses to coordinates (geocoding)\n- **Reverse**: Convert coordinates to addresses (reverse geocoding)\n- **Lookup**: Get details for specific OSM objects\n- **Status**: Check API health\n\n## Data Coverage\n\nThis instance contains OpenStreetMap data for the **entire United States** (all 50 states + DC + Puerto Rico).\n\n## Attribution\n\nData © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright\n    ","x-displayName":"Geocoding Service"},{"name":"cbgreenroom","description":"\n# CBGREENROOM\n\nCB Greenroom — pre-flight staging service\n\n## Quick Start\n\n```bash\ncurl https://greenroom.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Greenroom"},{"name":"cbgroundgame","description":"\n# CBGROUNDGAME — app-composition orchestrator\n\nGroundGame packages a whole app — data backbone, virtual services, render theme,\ncbcast distribution, UI, and the cbcron schedule — as **one StitchStack manifest**,\nthen reconciles the live fleet to it as a single lifecycle group.\n\nThe TS DSL compiles a `.stack.ts` → a resolved manifest (the debrittling gate);\nthis service observes live fleet state and plans the create/update/noop/conflict\nops to converge it. cbstitch is the execution engine; GroundGame composes + deploys.\n\n## The reconcile model\n\nA *stack* is a directory of deployed artifacts; its `stack.json` is the\n**compiled manifest** (the TS DSL has already substituted every `{{ vars.* }}`).\nThe orchestrator never reasons over a raw template — it loads the resolved\nmanifest and flattens it into the objects it manages across the fleet layers:\n\n- **data** — cbintel/cbmodels catalogs (`cbintel.catalog`)\n- **services** — cbstitch virtual-service homes (`vs.home`)\n- **distribution** — cbcast channels + devices (`cbcast.channel`, `cbcast.device`)\n- **ui** — the Loom app spec (`loom.app`)\n- **schedule** — cbcron jobs (`cbcron.job`)\n\n`diff` plans the create/update/noop/conflict ops to converge live → manifest and\nmutates nothing. `apply` executes that plan under two gates: ownership\n**conflicts** (a competing driver on a channel a VS should own) require\n`?confirm=true`, and the **no-display-risk** gate refuses to dark a channel a\ndevice is actively streaming unless `?force=true`. The group-lifecycle verbs\n(`up`/`down`/`disable`/`destroy`) then operate the whole stack as one unit.\n\n## Endpoints\n\n| Method · Path | Description |\n|---------------|-------------|\n| `GET /stacks` | List deployed stacks |\n| `GET /stacks/{name}` | A stack's resolved manifest summary |\n| `GET /stacks/{name}/diff` | Plan the stack vs live fleet (read-only) |\n| `POST /stacks/{name}/apply` | Reconcile the stack to the manifest (mutates) |\n| `GET /stacks/{name}/status` | Observed health per layer vs the manifest |\n| `POST /stacks/{name}/up` | Start (or enable) every service |\n| `POST /stacks/{name}/down` | Stop every service (ephemeral) |\n| `POST /stacks/{name}/disable` | Durably disable every service |\n| `POST /stacks/{name}/destroy` | Tear the stack down (requires `?confirm=true`) |\n| `/health` `/status` `/docs` | Standard sysforge surface |\n\n## Read vs. write surface\n\n`GET /stacks*`, `/diff`, and `/status` are read-only and safe to call freely.\nThe mutating verbs (`apply`/`up`/`down`/`disable`/`destroy`) change live fleet\nstate; `apply` and `destroy` additionally require explicit confirmation flags.\nMCP write-through is held off until the apply path is hardened\n(`enable_mcp_writes=false`).\n","x-displayName":"GroundGame"},{"name":"cbharvest","description":"\n# CBHARVEST\n\nState-package harvest engine — one schema for every election source, one\nartifact over the mesh.\n\nThe one-line reason it exists, from the Florida retro: *every source was wired\nlast minute because there was no standard place to wire it in advance.*\n\n## Quick start\n\n```bash\ncurl https://harvest.campaignbrain.dev/health\ncurl https://harvest.campaignbrain.dev/api/v1/packages\ncurl https://harvest.campaignbrain.dev/api/v1/packages/fl-2026-primary/preflight\n```\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/packages` | Every StatePackage served |\n| `/api/v1/packages/{slug}` | One package, every source declared |\n| `/api/v1/packages/{slug}/preflight` | Probe every source; per-source verdicts |\n| `/health`, `/health/detailed` | Health |\n| `/status`, `/prime` | Service status, OpenAPI resolution |\n","x-displayName":"cbharvest"},{"name":"cbhive","description":"\n# CBHIVE\n\n**Hive** — the autonomous-agent **control plane** for the Campaign Brain fleet: a\nMAPE-K supervisor that *organizes before it acts*. It turns raw fleet noise into a\nranked, deduped, operator-rulable horizon, distills inbound work-requests into\ngated deliverables, and walks declarative LoomDSL flows to resolution — pausing at\nan operator gate before anything that touches production.\n\n## The MAPE-K loop\n\n| Layer | What it does | Surface |\n|-------|--------------|---------|\n| **Monitor** (intake) | Fan out across intake adapters (cbcron failures, cboverseer incidents, Stash doc-drops) → normalized `Signal`s | `GET /signals` |\n| **Analyze** (horizon) | Collapse signals by fingerprint, rank, split \"needs attention\" vs \"known noise\" | `GET /digest`, `GET /flows/outcomes` |\n| **Knowledge** (precedent) | Persisted operator *rulings* on fingerprints; honored by every later digest | `GET/POST /rulings` |\n| **Plan** (triage → roadmap) | An agent reads a doc-pile → structured fork-tagged work-items → gated roadmap | `/mailbox/{id}/triage`, `GET /roadmap` |\n| **Execute** (LoomDSL) | Walk a declarative flow to resolution, suspending at gates + agent steps | `/flows*`, `/work-items/*`, `/broker/*` |\n\n## Design posture — read-only by default\n\ncbhive **earns trust by organizing before it acts.** The credential broker, the\nLoomDSL flow runner, and the work-item fork all default to **plan mode**: they\nrecord full intent + audit trail but mint nothing and touch no production. Live\narming (`CBHIVE_BROKER_LIVE=1`, `dry_run=false`, an operator `/approve`) is always\na deliberate, gated step. The operator is the umpire — no prod-affecting action\nruns without a passed gate above it.\n\n## Quick Start\n\n```bash\ncurl https://hive.campaignbrain.dev/health          # liveness\ncurl https://hive.campaignbrain.dev/api/v1/digest    # the one-screen horizon\n```\n\n## Authentication\n\nBehind the fleet nginx auth gate (cbauth). Service-to-service intake calls\n(cbcron / cboverseer / cbfiles) ride the ZVR mesh `ServiceClient`.\n\n## Monitoring endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Liveness check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Warm + resolve the OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Hive — Agent Control Plane"},{"name":"cbinbox","description":"\n# CBINBOX\n\nPersistent inbox + read-state + web-push fan-out for the **cbmesh** messaging\ntransport (see cbmesh#29 / #30). cbinbox is the durable data layer behind the\ncbloom *Messages* flash-app: the gateway delivers ephemeral DMs over the mesh\nWebSocket, and cbinbox keeps the history so threads survive reconnects, page\nreloads, and offline periods.\n\n## How it works\n\n```\nDM sender ─▶ cbmesh gateway ─▶ topic:internal.dm.archive ─▶ cbinbox subscriber\n                                                                   │ persist\n                                                                   ▼\n                                                            SQLite (messages,\n                                                            conversations,\n                                                            push_subscriptions)\n                                                                   │\ncbloom Messages app ◀── REST (GET /messages/*, /push/*) ◀──────────┘\n```\n\nA long-lived **mesh subscriber** (service-account credentials) listens on\n`topic:internal.dm.archive` and writes one row per delivered envelope. The\nREST endpoints below are read-mostly and run behind the `cb-wildcard` nginx\nauth gate — the caller's identity arrives as the cbauth-injected `X-User-Id`\nheader, which cbinbox trusts (same pattern as every other cb* service).\n\nWhen a DM lands while the recipient's tab is closed, cbinbox also encrypts and\nPOSTs a **Web Push** notification (VAPID) to each of the recipient's registered\nbrowser subscriptions, so the inbox-bell badge stays live across devices.\n\n## Endpoint map\n\n| Path | Method | Purpose |\n|------|--------|---------|\n| `/messages/recent` | GET | List the caller's DM peers, newest-thread first (conversation list) |\n| `/messages/dm/{other_user_id}` | GET | Fetch the DM thread between caller and another user |\n| `/messages/dm/{other_user_id}/read` | POST | Mark that thread read (clears its unread count) |\n| `/messages/unread_total` | GET | Total unread DMs for the caller (inbox-bell badge) |\n| `/messages/topic/{topic}` | GET | Recent events on a topic (permission-gated) |\n| `/push/vapid_public` | GET | VAPID public key the browser needs to subscribe (public) |\n| `/push/subscribe` | POST | Register a browser web-push subscription for the caller |\n| `/push/subscribe` | DELETE | Remove a browser web-push subscription for the caller |\n| `/health`, `/status`, `/prime` | GET | Liveness / status / cold-start warm (sysforge built-ins) |\n| `/docs` | GET | Swagger UI |\n\n## Authentication\n\nAll `/messages/*` and `/push/subscribe` endpoints require the caller to be\nauthenticated through the nginx `auth_request` gate, which injects\n`X-User-Id` (and `X-User-Permissions` / `X-User-Kind` for topic reads).\n`/push/vapid_public` is intentionally public — the VAPID public key is\nnon-secret by design.\n\n## Topic authorization\n\n`/messages/topic/{topic}` mirrors the cbmesh *subscribe* permission model: the\ntopic's leading segment selects a namespace (`tenant`, `user`, `system`,\n`service`, `internal`) and the caller must hold\n`cbmesh.subscribe.<namespace>`. The `internal` namespace is service-account\nonly — humans cannot read the DM-archive feed through this endpoint even with\nthe permission (defense in depth).\n","x-displayName":"Inbox + Web-Push Fan-Out"},{"name":"cbindex","description":"\n# CBIndex - Vectorless RAG Document Indexing\n\nCBIndex is a FastAPI wrapper around [PageIndex](https://github.com/vectify-ai/PageIndex),\na vectorless RAG system that transforms documents into hierarchical tree structures\noptimized for LLM-based reasoning retrieval.\n\n## Key Features\n\n- **No Vector Database Required**: Uses hierarchical tree structures\n- **Semantic Organization**: TOC-like structures for LLM reasoning\n- **Multi-Format Support**: PDF, Markdown, images (OCR), plain text\n- **Workspace Management**: GUID-based workspaces with deduplication\n- **YouTube Integration**: Extract and index transcripts from YouTube videos\n\n## Authentication\n\nAll endpoints except `/health` require an API key via the `X-API-Key` header:\n\n```bash\ncurl -X POST \"https://api.example.com/index/pdf\" \\\n  -H \"X-API-Key: your-api-key\" \\\n  -F \"file=@document.pdf\"\n```\n\n## Response Interpretation for LLMs\n\nThe `structure` field contains a hierarchical tree where each node has:\n- `title`: Section name\n- `node_id`: Unique identifier (e.g., \"0001\")\n- `start_index` / `end_index`: Page or line numbers\n- `summary`: LLM-generated summary of the section\n- `nodes`: Child sections (recursive)\n\nUse this structure for semantic navigation and targeted content retrieval.\n","x-displayName":"Document Index"},{"name":"cbinfra","description":"Infrastructure inventory, GitHub issues, project bootstrap, config management, and AI orchestration for CB projects.","x-displayName":"Infrastructure Manager"},{"name":"cbingest","description":"\n# CBIngest — Document Processing & Data Extraction\n\nCentralized document processing pipeline for Campaign Brain. Send any document\n(PDF, image, Office, text) and get back structured data — without reimplementing\nOCR, text extraction, or AI parsing.\n\n## Quick Start\n\n```bash\n# Health check\ncurl https://ingest.campaignbrain.dev/health\n\n# Extract text from a PDF\ncurl -X POST https://ingest.campaignbrain.dev/api/v1/extract \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"content_base64\": \"<base64>\", \"content_type\": \"application/pdf\", \"filename\": \"doc.pdf\"}'\n\n# Extract structured data with a schema\ncurl -X POST https://ingest.campaignbrain.dev/api/v1/extract \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"content_base64\": \"<base64>\", \"content_type\": \"application/pdf\", \"filename\": \"invoice.pdf\",\n       \"extraction_schema\": {\"vendor\": {\"type\": \"string\"}, \"amount\": {\"type\": \"number\"}}}'\n```\n\n## How It Works\n\n1. **OCR** — auto-routes by file type (PDF → Mistral, image → Pixtral, Office → Unstructured, text → direct)\n2. **Schema extraction** — if `extraction_schema` provided, AI extracts structured JSON\n3. **Entity resolution** — if `entity_keys` provided, normalizes fuzzy names to canonical values\n\n## Providers\n\n| Provider | File Types | Backed By |\n|----------|-----------|-----------|\n| cbai-ocr | PDF, image | Mistral OCR / Pixtral via cbai |\n| cbai-unstructured | Office (DOCX, XLSX, PPTX) | Unstructured via cbai |\n| direct | Text (TXT, CSV, HTML, MD) | Base64 decode |\n","x-displayName":"Document Ingestion Pipeline"},{"name":"cbintel","description":"\n# cbintel Intelligence API\n\n**Async job processing for web intelligence gathering, content retrieval, and data extraction.**\n\n---\n\n## Quick Start\n\n```bash\n# Submit a crawl job\ncurl -X POST https://intel.campaignbrain.dev/api/v1/jobs/crawl \\\n  -H \"X-API-Key: $CBINTEL_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"query\": \"AI regulation California 2024\", \"max_depth\": 2}'\n\n# Response: {\"job_id\": \"crawl_abc123\", \"status\": \"queued\"}\n\n# Poll for completion\ncurl https://intel.campaignbrain.dev/api/v1/jobs/crawl_abc123 \\\n  -H \"X-API-Key: $CBINTEL_API_KEY\"\n\n# Response: {\"status\": \"completed\", \"result\": {\"result_url\": \"https://files.campaignbrain.dev/...\"}}\n```\n\n---\n\n## Authentication\n\nAll endpoints require an API key via the `X-API-Key` header:\n\n```bash\ncurl -H \"X-API-Key: your-api-key\" https://intel.campaignbrain.dev/api/v1/jobs/\n```\n\nAPI keys are scoped per-tenant. Contact engineering@campaignbrain.dev for access.\n\n---\n\n## Overview\n\nThe **cbintel Intelligence API** provides async job processing for web intelligence\ngathering, content retrieval, and data extraction. Submit jobs, poll for status,\nand retrieve results through a unified interface.\n\n## Key Capabilities\n\n### 🕷️ Web Crawling (`/api/v1/jobs/crawl`)\nAI-powered iterative web discovery:\n- Start from search queries or seed URLs\n- AI evaluates page quality and relevance\n- Automatically discovers child pages up to configured depth\n- Synthesizes findings into structured knowledge\n\n### 🏛️ Historical Archives (`/api/v1/jobs/lazarus`)\nRetrieve content from Internet Archive and Common Crawl:\n- URL discovery via CDX API and gau\n- Point-in-time snapshots of web pages\n- Track content changes over time\n- Recover deleted or changed content\n\n### 🔢 Vector Embeddings (`/api/v1/jobs/vectl`)\nSemantic search and similarity:\n- Generate embeddings via Ollama (nomic-embed-text)\n- Store vectors in high-performance C++ backend\n- Search by semantic similarity\n- Index documents for retrieval\n\n### 📸 Screenshots (`/api/v1/jobs/screenshots`)\nFull browser rendering via Playwright:\n- Capture full-page screenshots\n- Generate PDFs from web pages\n- Extract DOM structure with element positions\n- Support for all major browsers (Chromium, Firefox, WebKit)\n\n### 🤖 Browser Automation (`/api/v1/jobs/browser`)\nAdvanced browser automation via ferret/SWRM:\n- Execute JSON AST scripts for complex workflows\n- Natural language script compilation\n- Template-based automation with parameter substitution\n- Proxy and VPN routing support\n\n### 🎬 Transcript Processing (`/api/v1/jobs/transcript`)\nYouTube video transcript analysis:\n- Resilient URL parsing (video IDs, YouTube URLs, shortened links, embedded videos)\n- Transcript extraction with timestamp preservation\n- AI-powered content classification (debate, lecture, tutorial, interview)\n- Structured summaries with speaker attribution, chapters, or steps\n\n### ⚡ Synchronous Fetch (`/api/v1/fetch`)\nSwiss-army knife URL retrieval:\n- Multiple engines: aiohttp, curl, lynx, guzl (Playwright)\n- Content extraction: HTML, Markdown, text, links\n- Binary capture: screenshots, PDFs\n- Flexible delivery: inline JSON or file uploads\n\n**Reading the result.** `engine=\"auto\"` (the default) climbs\n`aiohttp → curl → guzl → lynx`, retrying each rung on the fast,\nnondeterministic failures that a single attempt cannot tell apart from a\nblock. Judge the outcome from these fields, never from `len(html)`:\n\n| field | meaning |\n|-------|---------|\n| `success` | Real content came back. False for a challenge page too. |\n| `status_code` | What the server actually sent — **present on failure**. |\n| `error` | **Never null when `success` is false.** |\n| `blocked` | The body is a bot-wall/challenge, not the page. |\n| `failure_origin` | `remote` = the host; `local` = cbintel's own plumbing. |\n| `attempts[]` | Every engine tried, in order, with its own verdict. |\n| `egress_used` | `direct` / `vpn` / `proxy`. |\n\nA `local` failure_origin means the URL is **not** known to be unreachable —\nan engine of ours broke. Do not grade a source on one.\n\n**`wait_until` matters more than `timeout`.** It defaults to\n`domcontentloaded`. The old default, `networkidle`, never arrives on a page\ncarrying analytics or a chat widget, and a longer `timeout` cannot help\nbecause the condition is never satisfied — it fails closed on exactly the\nsites that are hardest to reach. Pass `wait_until=\"networkidle\"` only for a\npage that genuinely needs it.\n\nUnknown request fields are **rejected with a 422 naming them** — e.g.\n`timeout_ms` is not a field (use `timeout`, in seconds), and neither are\n`method` / `data` / `form`. A knob that looks accepted and does nothing is the\nsame class of defect as a silent failure.\n\n## Geographic Routing (VPN)\n\nAll jobs support the `geo` parameter for geographic routing:\n\n```json\n{\"query\": \"...\", \"geo\": \"us:ca\"}\n```\n\n| Filter | Description |\n|--------|-------------|\n| `us` | United States (any state) |\n| `us:ca` | California specifically |\n| `us:ny` | New York |\n| `de` | Germany |\n| `gb` | United Kingdom |\n| `jp` | Japan |\n\nTraffic is routed through VPN exit nodes in the specified region via\nWireGuard tunnels managed by the [Network API](https://network.campaignbrain.dev/docs).\n\n## How to Use This API\n\n### For Claude/AI Integration\n\nThis API is designed to be called by AI assistants as a tool:\n\n1. **Submit a job** - POST to the appropriate endpoint with parameters\n2. **Poll for status** - GET `/api/v1/jobs/{job_id}` until complete\n3. **Retrieve results** - Results include URLs to uploaded files\n\n### Typical Workflow\n\n```\nUser: \"Research AI regulation trends in California\"\n\nAI: 1. POST /api/v1/jobs/crawl\n       {\"query\": \"AI regulation California 2024\", \"geo\": \"us:ca\", \"max_depth\": 2}\n    2. Poll GET /api/v1/jobs/{job_id} every 5 seconds\n    3. When status=\"completed\", retrieve result_url\n    4. Summarize findings for user\n```\n\n### Job Lifecycle\n\n```\nQUEUED → PROCESSING → COMPLETED\n                   ↘ FAILED\n        ↓\n     CANCELLED (if cancelled while queued)\n```\n\n## Result Storage\n\nCompleted jobs upload results to `files.campaignbrain.dev`:\n- JSON summaries with metadata\n- Raw content files (HTML, text, screenshots)\n- Results available for 7 days\n\n## Rate Limits\n\n- **Crawl jobs**: 10 concurrent, 100 max URLs per job\n- **Screenshot jobs**: 5 concurrent, 50 URLs per batch\n- **Fetch requests**: 100/minute\n- **All jobs**: 1000/hour per API key\n","x-displayName":"Intelligence API"},{"name":"cbissues","description":"\n# CBIssues - Issue Tracking Dashboard\n\n**Unified issue tracking and management for Campaign Brain**\n\n---\n\n## Overview\n\nCBIssues provides:\n\n- **Full CRUD**: Create, read, update, and delete GitHub Issues\n- **Repo Access Control**: Restricted to Nominate-AI/cb* and tinymachines/*\n- **GitHub Projects**: Add and remove issues from project boards\n- **Comment Management**: Add comments to issues\n\n---\n\n## Authentication\n\nAll endpoints require an API key via the `X-API-Key` header:\n\n```bash\ncurl -X GET https://issues.campaignbrain.dev/api/v1/repos \\\n  -H \"X-API-Key: your-api-key\"\n```\n\n---\n\n## Quick Start\n\n### List Allowed Repos\n\n```bash\ncurl -X GET https://issues.campaignbrain.dev/api/v1/repos \\\n  -H \"X-API-Key: $CBISSUES_API_KEY\"\n```\n\n### List Issues\n\n```bash\ncurl -X GET \"https://issues.campaignbrain.dev/api/v1/issues?repo=Nominate-AI/cbinfra&state=open\" \\\n  -H \"X-API-Key: $CBISSUES_API_KEY\"\n```\n\n### Create Issue\n\n```bash\ncurl -X POST https://issues.campaignbrain.dev/api/v1/issues \\\n  -H \"X-API-Key: $CBISSUES_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"repo\": \"Nominate-AI/cbissues\", \"title\": \"Test issue\"}'\n```\n\n---\n\n## Repository Access\n\nAccess is restricted to:\n- `Nominate-AI/cb*` - Campaign Brain repositories\n- `tinymachines/*` - TinyMachines repositories\n\nOther repositories will return 403 Forbidden.\n","x-displayName":"Issue Tracking"},{"name":"cbiterable","description":"\n# CBITERABLE\n\nMulti-tenant Iterable API wrapper with DuckDB sync.\n\n## Overview\n\nCBIterable provides a unified API for managing multiple Iterable projects.\nEach **tenant** maps to one Iterable API key and gets its own isolated DuckDB\ndata store. Background sync jobs pull data from Iterable into the local cache\nfor fast querying.\n\n## Quick Start\n\n```bash\n# Health check\ncurl https://iterable.campaignbrain.dev/health\n\n# Create a tenant\ncurl -X POST https://iterable.campaignbrain.dev/api/v1/tenants/ \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"name\": \"Production\", \"iterable_api_key\": \"YOUR_KEY\"}'\n\n# Start a sync\ncurl -X POST https://iterable.campaignbrain.dev/api/v1/tenants/{tenant_id}/sync/ \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"resource_types\": [\"lists\", \"campaigns\", \"users\"]}'\n\n# Query synced data\ncurl https://iterable.campaignbrain.dev/api/v1/tenants/{tenant_id}/lists/\n```\n\n## Authentication\n\nNo API-level authentication is required on the internal `/api/v1` surface\n(it sits behind the platform auth gate). Iterable API keys are configured\nper tenant, stored server-side (encrypted at rest), and masked in every\ntenant response — the plaintext is available only via the logged\n`POST /tenants/{tenant_id}/key/reveal` endpoint.\n\nThe external **vendor gateway** (`/gateway/api/…`) authenticates itself:\nconsumers present a CampaignBrain-issued token in the `Api-Key` header —\nthe same header Iterable uses, so a vendor only swaps the base URL. Real\nIterable keys are never exposed.\n\n## Rate Limits\n\nIterable API calls are rate-limited via a token-bucket algorithm\n(default: 10 req/s) with exponential backoff retry on transient errors.\n","x-displayName":"Iterable API Wrapper"},{"name":"cbkb","description":"\n# CBKB — Knowledge Base Orchestrator\n\nCBKB is the Campaign Brain **knowledge-base orchestrator**: a single,\ntenant-scoped retrieval API that composes a trustworthy answer from a layered\nstack of knowledge sources, then logs exactly how that answer was assembled so\nit can be traced back later.\n\n## The Layered Knowledge Model\n\nCBKB reasons over four conceptual layers, lowest (most authoritative) first:\n\n| Layer | What it holds | Stored / sourced from |\n|-------|---------------|-----------------------|\n| **Layer 2 — Records** | Structured, curated facts (the canonical \"what is true\") | `kb_records` table — see *KB Records* |\n| **Layer 3 — Rules** | Versioned brand / voice rules that *shape* how an answer is phrased | `kb_rules` table — see *KB Rules* |\n| **Layer 1 — Narrative** | Free-text / document context retrieved on demand | cbindex workspace search (non-fatal if unavailable) |\n| **Layer 4 — History** | Historical context merged at query time | cbscout (non-fatal if unavailable) |\n\nA single `POST /api/v1/kb/query` call retrieves across these layers, optionally\nruns a Layer-3 LLM **synthesizer** (via cbai) to rule-adapt the phrasing, and\nreturns a cited answer with a `retrieval_log_id` for full traceability.\n\n## Multi-Tenancy\n\nCBKB is **single-DB, multi-tenant**. There is one `data/cbkb.db` SQLite file;\ntenants are isolated by a `tenant_id` column, never by separate files. Every\nrequest carries a tenant identity derived from the caller's cbauth JWT — a\nbody-supplied `tenant_id` is always ignored and rebound to the JWT tenant.\n\n## Authentication\n\nAll `/api/v1/kb/*` endpoints require a valid cbauth-issued bearer token and a\nmatching permission scope:\n\n| Operation | Required permission |\n|-----------|---------------------|\n| Read records / rules / audit / retrieval logs | `cbkb.records.read` |\n| Write records | `cbkb.records.write` |\n| Write rules | `cbkb.rules.write` |\n| Compose a query | `cbkb.query.invoke` |\n\n## Quick Start\n\n```bash\ncurl https://kb.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n| `/api/v1/kb/records` | Layer-2 structured-fact CRUD |\n| `/api/v1/kb/rules` | Layer-3 versioned brand-rule CRUD |\n| `/api/v1/kb/query` | Composed retrieval over Layers 1-4 |\n| `/api/v1/kb/audit/{record_id}` | Audit trail for a record |\n| `/api/v1/kb/retrieval` | Retrieval-log trace-back |\n","x-displayName":"Knowledge Base Orchestrator"},{"name":"cbkrnl","description":"\n# CBKRNL\n\ncbkrnl — agent state kernel and graph lint\n\n## Quick Start\n\n```bash\ncurl https://krnl.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/lint` | Lint an agent-graph document (POST) |\n| `/api/v1/tool-schema` | The kernel exposed as a callable tool schema |\n| `/api/v1/configs` | Named kernel strategy configurations |\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"cbkrnl"},{"name":"cblinks","description":"\n# CBLinks API\n\nURL shortener service for creating and managing short links.\n\n---\n\n## Quick Start\n\n### 1. Get an API Key\n\nBootstrap an admin user (first-time setup):\n```bash\npython -m cblinks.cli bootstrap --name \"Admin\" --email \"admin@example.com\"\n```\n\n### 2. Authenticate Requests\n\nInclude your API key in every request using either header:\n```\nX-API-Key: cblinks_xxxxx...\n```\nor\n```\nAuthorization: Bearer cblinks_xxxxx...\n```\n\n### 3. Create a Short Link\n\n```bash\ncurl -X POST https://links.campaignbrain.dev/api/v1/links \\\n  -H \"X-API-Key: YOUR_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"target_url\": \"https://example.com/very/long/path\"}'\n```\n\n### 4. Use the Short Link\n\nVisit `https://links.campaignbrain.dev/{slug}` to be redirected.\n\n---\n\n## Authentication\n\nAll endpoints except `/health`, `/`, and `/{slug}` (redirect) require an API key.\n\n**Supported Headers:**\n- `X-API-Key: cblinks_xxxxx...`\n- `Authorization: Bearer cblinks_xxxxx...`\n\n**Key Format:** `cblinks_{43-character-random-token}`\n\n---\n\n## Error Responses\n\nAll errors return JSON with this structure:\n```json\n{\n  \"detail\": \"Human-readable error message\"\n}\n```\n\n| Status | Meaning |\n|--------|---------|\n| 400 | Invalid request (bad slug format, missing field) |\n| 401 | Missing or invalid API key |\n| 403 | Valid key but not the link owner |\n| 404 | Link not found |\n| 409 | Conflict (slug already exists) |\n| 410 | Link expired or disabled |\n| 500 | Server error |\n","x-displayName":"URL Shortener"},{"name":"cblist","description":"\n# CBLIST\n\nList service — durable lists/segments/audiences: ingest, normalize, dedup, geocode, score, tag, export (on cbdatabase)\n\n## Quick Start\n\n```bash\ncurl https://list.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"List API"},{"name":"cblogs","description":"\n# CB Logs — Log Query & Management Service\n\nRead-only API for querying and searching logs across DC-1 (monroe).\n\n## Log Groups\n\n| Group | Sources |\n|-------|---------|\n| `system` | journald priorities 0-6, syslog, kern.log |\n| `services` | All CB service journals |\n| `web` | nginx access + error logs |\n| `database` | PostgreSQL logs |\n| `dns` | BIND query logs |\n| `security` | auth.log, ufw.log, fail2ban.log |\n| `network` | VPN, network, TOR journals |\n| `cron` | CRON, crontab, anacron journals |\n","x-displayName":"Log Query & Analysis"},{"name":"cbloom","description":"\n# CBLOOM\n\nLoom - Campaign Brain workspace builder for composable data dashboards\n\n## Quick Start\n\n```bash\ncurl https://bloom.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Loom Workspace Builder"},{"name":"cbmanim","description":"\n# CBMANIM\n\nManim render service: scene/spec to mp4/gif via cbfiles, with cbai prompt-to-scene\n\n## Quick Start\n\n```bash\ncurl https://manim.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Manim API"},{"name":"cbmaps","description":"\nAPI for generating beautiful minimalist map posters for any city worldwide.\n\n## Render Modes\n\n| Mode | Description |\n|------|-------------|\n| **Vector** | High-quality vector rendering via OpenStreetMap + osmnx |\n| **Raster** | Tile-based rendering with CartoDB styles via cbtiles |\n| **District** | Congressional district maps with boundary overlays |\n\n## Features\n\n- **Theme Selection** - 17 built-in themes from noir to ocean, pastel to blueprint\n- **Customizable Distance** - Control map coverage radius (4km to 50km)\n- **High Quality Output** - 300 DPI PNG posters (3600x4800 pixels, 12x16 inches)\n- **Elegant Labels** - Auto-generated city name, country, and coordinate overlays\n- **Web UI** - Interactive poster builder at `/ui`\n\n## Generation Time\n\nPoster generation takes 10-60 seconds depending on:\n- Distance (larger = slower)\n- City complexity (more roads = slower)\n- Render mode (raster is fastest, vector most detailed)\n\n## Data Sources\n\n- **Vector mode**: OpenStreetMap data via osmnx + geopy geocoding\n- **Raster mode**: CartoDB tile server via cbtiles\n- **District mode**: Boundary geometry from cbdistricts API\n- **Street imagery**: Mapillary Graph API + KartaView, JIT-cached on disk\n\n## Example\n\n```json\nPOST /api/v1/posters/generate\n{\n  \"city\": \"Paris\",\n  \"country\": \"France\",\n  \"theme\": \"noir\",\n  \"distance\": 10000,\n  \"render_mode\": \"vector\"\n}\n```\n","x-displayName":"City Map Posters"},{"name":"cbmarketing","description":"\n# CBMARKETING\n\nCentralized brand, color & design-decision service for the Campaign Brain fleet.\n\nColor math (WCAG contrast, harmonies, tonal ramps), persisted per-tenant brand\nkits with design-token export, and mood-driven palette recommendations.\n\n## Quick Start\n\n```bash\ncurl https://marketing.campaignbrain.dev/health\ncurl \"https://marketing.campaignbrain.dev/api/v1/color/ramp?base=%233b82f6\"\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/color/*` | Convert, contrast, palette, ramp, accessible |\n| `/api/v1/brands/*` | Brand-kit CRUD + design-token export |\n| `/api/v1/decide/*` | Mood-driven palette recommendations |\n| `/health` | Health check |\n| `/status` | Service status, features, and routes |\n| `/docs` | Swagger UI |\n","x-displayName":"Marketing API"},{"name":"cbmobile","description":"\n## Overview\n\ncbmobile is the backend for the Campaign Brain mobile PWA, served at\n`https://mobile.campaignbrain.dev/api/`. It exposes the read APIs the\nfield-canvassing app uses (contact lists, individual contacts, offline-sync\ncheckpoints) plus a remote-debug WebSocket for capturing client-side logs from\ndevices in the field.\n\n## Status — evolving backend\n\nThis service is an early, evolving backend. The `/contacts` and `/sync` routes\ncurrently return **hardcoded in-memory stub data**; the real implementation\nwill query the walkbook DuckDB store. Treat the contact/sync response shapes as\nthe contract, but not the data, until that integration lands.\n\n## Remote debugging\n\n`/api/ws/debug` is a WebSocket that mobile clients connect to in order to stream\nstructured logs (`log` / `network` / `error` / `heartbeat` frames) to the\nserver, where each session is written to a per-day JSONL file under\n`./logs/remote/`. `/api/debug/sessions` lists the currently-connected sessions\nand their log paths.\n","x-displayName":"Mobile Canvassing"},{"name":"cbmodels","description":"\n## Overview\n\nThis API provides **voter segment analysis** capabilities for AI-powered data chat applications.\nGiven a list of State Voter IDs (SVIDs), it analyzes the segment's demographic and behavioral\ncharacteristics compared to the full voter baseline.\n\n## Key Capabilities\n\n### 🎯 Segment Analysis\nCompare any voter segment against the baseline population to identify:\n- **Demographic deviations** - Age, gender, party affiliation differences\n- **Geographic patterns** - County/state concentration\n- **Behavioral signals** - Email engagement, donation history (when available)\n\n### 📊 Baseline Statistics\nAccess pre-computed statistics for the entire voter database including:\n- Column distributions and summary statistics\n- Correlations between voter attributes\n- Outlier detection results\n- Pattern recognition (missing data, class imbalance)\n\n## How to Use This API\n\n### For Claude/AI Integration\nThis API is designed to be called by Claude or other AI assistants as a tool.\nThe AI should:\n\n1. **Collect SVIDs** from user queries (e.g., \"analyze voters in district 5\")\n2. **Call `/segment/analyze`** with the SVID list\n3. **Interpret results** and explain significant deviations to users\n\n### Typical Workflow\n\n```\nUser: \"How do Trump donors in FL differ from the general population?\"\n\nAI: 1. Query donor database for Trump donors in FL → get SVIDs\n    2. POST /api/v1/segment/analyze with SVIDs\n    3. Interpret response: \"This segment is 15% more likely to be male,\n       concentrated in Miami-Dade county, and 3x more likely to have\n       clicked on email campaigns...\"\n```\n\n## Data Sources\n\n- **i360 Voter File**: 81K persons with demographics, contact info, custom fields\n- **Behavioral Data**: Email clicks, donations, campaign engagement (41M+ records)\n- **Contact Mapping**: Phone/email linkage (182M records)\n\n## Rate Limits\n\n- Segment analysis: Up to 100,000 SVIDs per request\n- Recommended batch size: 1,000-10,000 SVIDs for optimal performance\n","x-displayName":"Voter Analysis"},{"name":"cbnetwork","description":"\n# CBNetwork\n\nREST API for infrastructure health, device inventory, and fleet topology.\n\nServes pre-collected inventory snapshots (from `cbn inventory`) as fast,\nread-only JSON endpoints. No live SSH — all data comes from on-disk snapshots.\n\n---\n\n## Endpoints\n\n- **Health** — `/health`, `/api/v1/fleet/status`\n- **Inventory** — `/api/v1/inventory`, `/api/v1/inventory/{ip}`\n- **Topology** — `/api/v1/topology`\n- **Printers** — `/api/v1/printers`, `/api/v1/printers/{ip}`, `POST /api/v1/printers/scan`\n","x-displayName":"Network Fleet Manager"},{"name":"cbnominate","description":"\n# CBNOMINATE\n\nThe consumer surface over **the most comprehensive and current dataset of public\nelections in the US** — the full administrative map of American public elections:\nwho administers them, what offices exist at every layer, where results are\npublished, and how the layers nest. Kept current, with receipts.\n\n## What this API serves\n\nThe **state spine**: one skeleton per state, served typed rather than as the\ndisplay strings the static site renders. Every number arrives as\n`{value, approximate, minimum, raw}`, and `value: null` means *unknown* — never\nzero. An unresearched tier and a researched-and-empty tier must never read the\nsame way.\n\nPhase A is in progress: **19 of 50 states exist**. Unbuilt states are absent\nrather than present-and-zero, so no consumer can mistake one for the other.\n\n## Quick Start\n\n```bash\ncurl https://nominate.campaignbrain.dev/api/v1/states\ncurl https://nominate.campaignbrain.dev/api/v1/states/FL\ncurl https://nominate.campaignbrain.dev/api/v1/vocabulary\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/states` | Coverage table over every built state |\n| `/api/v1/states/{state}` | One state's full typed skeleton |\n| `/api/v1/states/{state}/ladder` | Seat ladder + expected-offices roll-up |\n| `/api/v1/states/{state}/deltas` | Structural exceptions (crawler-breakers) |\n| `/api/v1/states/{state}/sources` | The receipts |\n| `/api/v1/states/{state}/counties` | County rows (a sample unless `complete`) |\n| `/api/v1/calendar` | Dated election events across built states |\n| `/api/v1/vocabulary` | Closed vocabularies + the count contract |\n| `/health`, `/health/detailed` | Health check |\n| `/status`, `/prime` | Service status, spec resolution |\n| `/docs`, `/redoc` | Swagger UI, ReDoc |\n\n`{state}` accepts a postal code (`FL`) or a kit slug (`florida`).\n","x-displayName":"cbnominate"},{"name":"cbnotify","description":"\n# CBNOTIFY\n\nFleet-wide **notification spine**. Producers (`cbtapwatch`, `cboverseer`\nalerts, deploy hooks, …) `POST /api/v1/notify` a notification — a\n`body_markdown` payload plus optional labeled links and a priority — and\ncbnotify persists it. A cbcron-driven dispatch tick (`POST /notify/dispatch`)\nthen delivers it — as an SMS page via `cbsms` for anything at or above the\npaging floor, and as email via `cbemail` when the email leg is armed.\n\nThe producer's identity is captured from the upstream `X-User-Email` header\n(set by `sysforge.core.client.caller_identity`), never trusted from the body,\nand writes are gated on the `cbnotify.notify.write` permission.\n\n> **Note on paths.** The notification routes are mounted at the service root —\n> `/notify`, **not** `/api/v1/notify`. Only `/api/v1/hello` carries that prefix.\n\n## Accepted is not delivered\n\nThe two are different facts, and for months this service reported only the\nfirst: a flat `201` on every publish while both delivery channels were shut.\nNothing said so, and a 102-failure outage went unnoticed for two days as a\nresult — cbcron's alerting had worked correctly and fired on time.\n\nSo `POST /notify` now answers the real question:\n\n- **201** — a channel will carry this row.\n- **202** — stored and queryable, but nothing will send it. `delivery.outlook`\n  says whether that is worth waiting out (`queued`) or needs a configuration\n  change (`no_channel_armed`, `no_channel_for_priority`,\n  `page_destination_missing`).\n\n`GET /notify/delivery` reports the same truth for the service as a whole —\nper-channel arming, the backlog, the last real hand-off, the last failure — and\nis mirrored as the `delivery` component on `/health/detailed`.\n\n## Quick Start\n\n```bash\n# can this service actually deliver anything right now?\ncurl https://notify.campaignbrain.dev/notify/delivery\n\n# publish (identity + permission headers set by the mesh caller upstream)\ncurl -X POST https://notify.campaignbrain.dev/notify \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"channel\":\"alerts\",\"title\":\"Heads up\",\"body_markdown\":\"**Something** happened\"}'\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `POST /notify` | Publish a notification; 201 if it will deliver, 202 if not |\n| `GET /notify` | List rows with their real delivery outcome |\n| `GET /notify/delivery` | Whether delivery actually works right now |\n| `POST /notify/dispatch` | Deliver pending rows via cbsms/cbemail (cbcron tick) |\n| `GET /notify/pending` | Count undelivered notifications |\n| `DELETE /notify/{id}` | Remove a row (housekeeping) |\n| `GET /api/v1/hello` | Trivial smoke greeting |\n| `/health` | Health check |\n| `/health/detailed` | Detailed health, including the `delivery` component |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Fleet Notification Spine"},{"name":"cbonboard","description":"\n# CBONBOARD — QR-Driven Progressive Trust Flow\n\nCampaign Brain's onboarding service. A single rotating QR code at a live demo\ngates anyone in the room into an identity funnel; each interaction graduates the\nuser up a **trust ladder** (scanned → provisional → verified → assigned) while\nthe big screen watches the room light up in real time.\n\n## Guiding Principles\n\n- **The QR authenticates \"a person at this demo right now\"**, not a specific\n  identity. It is a gate into the funnel, not an identity claim.\n- **Many users redeem the same QR** within a time-bounded window. Rotation\n  defeats photo-replay; a per-batch redemption cap defeats catastrophic leaks.\n- **Progressive trust.** Every scan / signup / verify graduates the user to a\n  higher tier — scopes expand and identity stabilizes.\n- **Nothing is \"real\" until verified.** A user becomes recoverable only after\n  confirming a phone number; before that they are ephemeral.\n- **Text-driven, not email-driven.** SMS is the verification channel — everyone\n  checks texts at a live demo; nobody checks email.\n\n## The Flow\n\n1. **Redeem** — the signup PWA exchanges the QR's `batch_token` for a short-lived\n   Tier-0 `session_token` (`POST /onboard/redeem`).\n2. **Signup** — name + phone create a provisional (Tier-1) cbauth user and fire\n   an SMS code (`POST /onboard/signup`).\n3. **Verify** — the SMS code elevates the user to Tier-2 and expands scopes\n   (`POST /onboard/verify`); `POST /onboard/resend` re-issues a code.\n4. **Watch** — the kiosk renders the live QR (`GET /qr/live/{id}`) and streams\n   roster transitions over a WebSocket (`/ws/demo/{id}`).\n5. **Manage** — admins create demo sessions and audit redemptions under `/admin`.\n\n## Tokens & Tiers\n\n| Tier | Name | Granted by |\n|------|------|------------|\n| 0 | `scanned` | redeeming a QR `batch_token` → `session_token` |\n| 1 | `provisional` | signup → disabled cbauth user + SMS code |\n| 2 | `verified` | correct SMS code → elevated scopes + confirmed phone |\n| 3 | `assigned` | (reserved) downstream role assignment |\n\n## Quick Start\n\n```bash\ncurl https://onboard.campaignbrain.dev/health\n```\n\n## Operational Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Warm cold-start resources |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Onboarding (QR Trust Flow)"},{"name":"cboverseer","description":"\n## Overview\n\nCB Overseer is the central system manager for Campaign Brain infrastructure.\nIt coordinates monitoring agents, collects telemetry, manages incidents, and\nenables self-healing through AI-powered diagnosis and learned remediation.\n\n## Architecture\n\nOverseer operates as a distributed agent-based monitoring platform:\n\n1. **Agents**: Lightweight workers that collect metrics from various systems\n2. **Telemetry Collector**: Receives and aggregates metrics via WebSocket\n3. **Memory Interface**: Stores incidents in cbscout for learning\n4. **Intelligence**: Uses cbai for diagnosis and remediation decisions\n5. **Alerting**: Sends notifications via cbsms with conversational follow-up\n\n## Core Philosophy\n\n**Bank issues and resolutions in long-term memory so agents can fix problems\nautonomously.** The system learns from every incident: what went wrong, how it\nwas fixed, and what patterns preceded the failure.\n\n## Key Capabilities\n\n### Telemetry Collection\nReal-time metrics from all CB services including:\n- System resources (memory, disk, CPU, load)\n- Service health (systemd units, health endpoints)\n- Database status (PostgreSQL, DuckDB, SQLite)\n- Network status (TOR, VPN, SWRM peers)\n\n### Incident Management\nAutomatic detection, diagnosis, and optional remediation of issues:\n- Threshold-based alerting with severity levels\n- AI-powered root cause analysis via cbai\n- Historical pattern matching via cbscout memory\n- Automated fixes for known issues (with confidence scoring)\n\n### Self-Healing\nApproved automatic actions with learned confidence:\n- Service restarts for OOM/hung processes\n- Cache/temp file cleanup for disk pressure\n- Log rotation for storage issues\n- NGINX reload after config validation\n\n## How to Use This API\n\n### Check System Status\n```\nGET /api/v1/telemetry/recent\n```\nReturns recent metrics from all agents.\n\n### View Active Incidents\n```\nGET /api/v1/incidents?status=open\n```\nLists current unresolved incidents.\n\n### Dispatch Monitoring Agent\n```\nPOST /api/v1/agents/dispatch\n{\n  \"agent_type\": \"system\",\n  \"target\": \"localhost\"\n}\n```\n\n### Resolve Incident\n```\nPOST /api/v1/incidents/{id}/resolve\n{\n  \"resolution\": \"Restarted service\",\n  \"auto_fixed\": false\n}\n```\n","x-displayName":"System Monitor"},{"name":"cbpayments","description":"\n# CB Payments — Donation Processing API\n\n**Payment processing and campaign management** for political campaigns, PACs, and nonprofits.\nMigrated from the legacy Transaxt ASP.NET platform.\n\n---\n\n## Overview\n\n- **Campaign-driven donations**: Each donation targets a campaign under an account (tenant)\n- **Fee calculation**: Platform fee (CEILING) + consultant commission (ROUND) deducted per donation\n- **EAV customization**: 30+ campaign attributes control form appearance, labels, and compliance text\n- **Privacy-aware feeds**: Public donation feeds with configurable name/amount visibility\n- **Authorize.Net gateway**: Real-time credit card processing with sub-merchant support\n\n---\n\n## Quick Start\n\n```bash\n# Get campaign info for a donation form\ncurl https://payments.campaignbrain.dev/api/1/campaigninfo/MYCAMP01\n\n# List active campaigns for an account\ncurl https://payments.campaignbrain.dev/api/1/campaignlist/ACCTCODE\n\n# Get account totals (requires API key)\ncurl \"https://payments.campaignbrain.dev/api/1/accounttotals/ACCTCODE?apikey=YOUR_KEY\"\n```\n\n---\n\n## Authentication\n\n| Method | Used By | How |\n|--------|---------|-----|\n| **None** | Campaign info, feed, images | Public endpoints |\n| **API Key** | Reporting, donation submission | `?apikey=` query parameter |\n| **JWT Bearer** | Admin sync, user info | `Authorization: Bearer <token>` header |\n","x-displayName":"Donation Processing"},{"name":"cbpersonnel","description":"\n# CBPERSONNEL\n\nPayroll tracking and **per-entity cost allocation** for 190 Personnel LLC, the\nemployer of record for 12 stateside operating entities.\n\nThe allocation rollup is the product: what each entity actually cost in payroll\nthis period, which is otherwise done by hand or not at all.\n\n## Scope boundary\n\n**Paychex Flex is the system of record.** No taxes are computed here and\nnothing is filed. `net_pre_tax` is not take-home pay: there is no withholding,\nFICA or employer-side tax in it, and every label for it reads \"Net (pre-tax)\".\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/entities` | Allocation entities |\n| `/api/v1/employees` | Roster (read-mostly; Paychex owns it) |\n| `/api/v1/periods` | Semi-monthly pay periods |\n| `/api/v1/periods/{key}/lines` | The payroll grid |\n| `/api/v1/periods/{key}/allocation` | Per-entity cost rollup |\n| `/api/v1/periods/{key}/approve` | Freeze the period's lines |\n| `/health` | Health check |\n","x-displayName":"cbpersonnel"},{"name":"cbprint","description":"\n# CBPRINT\n\nOffice print spooler face — live status, supplies, queues, and config over\nCUPS + IPP on monroe.\n\nCUPS is the source of truth for **queues and jobs**; each printer's own IPP\nendpoint is the source of truth for **state and supplies** — including the\nreal make-and-model (driverless CUPS queues all claim to be\n\"Printer - IPP Everywhere\"; the device tells the truth) and per-cartridge\npart numbers, so \"toner low\" comes with the thing to order.\n\n## Consumers\n\n| surface | what it uses |\n|---------|--------------|\n| Loom `/app/printers` (admin) | everything, via the cbloom relay |\n| `printers.campaignbrain.dev` (office LAN, planned — cbinfra#82) | read endpoints only |\n| cboverseer (planned) | `/api/v1/health` roll-up |\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/printers` | All queues — state, supplies, real models |\n| `/api/v1/printers/{queue}` | One queue, full detail |\n| `PATCH /api/v1/printers/{queue}` | Config writes (name, location, shared, defaults) |\n| `/api/v1/jobs` | Job list, active + completed |\n| `/api/v1/health` | Fleet roll-up: any printer needing hands |\n| `/api/v1/onboarding` | Per-platform connection payloads |\n| `/health` | Service health check |\n| `/docs` | Swagger UI |\n\nWrites require `cbprint.config.write` / `cbprint.jobs.write`. CUPS admin\nauthority is the service's own lpadmin group membership — no CUPS\ncredentials exist in any client.\n","x-displayName":"cbprint"},{"name":"cbpublish","description":"\n# CBPUBLISH\n\n## Overview\n\nArtifact publishing service — ingest structured & semistructured artifacts,\nnormalize them to a canonical intermediate representation, and render clean,\ntenant-aware work products through Jinja2 templates.\n\nThe pipeline is **ingest → normalize → template → publish**:\n\n1. **Ingest** an artifact (`json`, `csv`, `markdown`, `html`, `text`) — inline\n   content, tenant-scoped.\n2. **Normalize** it to a uniform IR (`title`, `text`, `data`, `sections`).\n3. **Template** — tenant-authored (or built-in shared) Jinja2 templates run in\n   a sandbox against the normalized artifacts.\n4. **Publish** — the rendered output is persisted as a work product\n   (`markdown` / `html` / `text`) and served back.\n\nHeavy *unstructured* extraction (PDF/OCR/office docs) is out of scope by design\n— run those through **cbunstruct / cbintel** first, then hand cbpublish the\nstructured or markdown result.\n\n## Quick Start\n\n```bash\ncurl https://publish.campaignbrain.dev/health\ncurl https://publish.campaignbrain.dev/api/v1/templates\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/artifacts/*` | Ingest + inspect + normalize artifacts |\n| `/api/v1/templates/*` | Work-product template CRUD (tenant-aware) |\n| `/api/v1/publish` | Render artifacts through a template |\n| `/api/v1/documents` | Assemble a full publication (cover/TOC/index/citations) → PDF/HTML |\n| `/api/v1/workproducts/*` | Retrieve + soft-delete rendered work products |\n| `/health` | Health check |\n| `/status` | Service status, features, and routes |\n| `/docs` | Swagger UI |\n","x-displayName":"Publish API"},{"name":"cbpulse","description":"\n# VIP API — Virgin Islands Pulse\n\n**Central nervous system for hospitality, media, and tourism businesses across the US Virgin Islands.**\n\n## Overview\n\nVIP aggregates data from disconnected external systems into a single source of truth:\n\n- **Financial Intelligence** — Real-time revenue, labor cost, COGS, and health scores across the portfolio\n- **Alert Engine** — Proactive alerts with lifecycle management (NEW → ACKNOWLEDGED → ASSIGNED → RESOLVED)\n- **Operations** — Purchase orders, invoices, and maintenance tickets with approval workflows\n- **Integration Hub** — Syncs with Clover (POS), Cloudbeds (PMS), WebRezPro (PMS), email invoices\n- **AI Summaries** — Daily executive briefings generated from portfolio-wide data\n- **Audit Trail** — Every action logged with user, timestamp, and context\n\n## Quick Start\n\n```bash\n# 1. Authenticate\nTOKEN=$(curl -s -X POST https://pulse.campaignbrain.dev/api/auth/token \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"you@example.com\", \"password\": \"secret\"}' | jq -r '.access_token')\n\n# 2. List businesses you can access\ncurl -H \"Authorization: Bearer $TOKEN\" https://pulse.campaignbrain.dev/api/businesses\n\n# 3. Check active alerts\ncurl -H \"Authorization: Bearer $TOKEN\" https://pulse.campaignbrain.dev/api/alerts?status=new\n\n# 4. Get portfolio financials\ncurl -H \"Authorization: Bearer $TOKEN\" https://pulse.campaignbrain.dev/api/financials/portfolio\n```\n\n## Portfolio\n\n| Property | Island | Type | PMS |\n|----------|--------|------|-----|\n| **The Fred** | St. Croix | Hotel / Restaurant / Bar / Spa | WebRezPro |\n| **The Saint** | St. John | Hotel | Cloudbeds |\n| **Lovango** | St. John | Rum Bar | — |\n\n## Authentication\n\nAll endpoints except `/health` and `POST /api/auth/token` require a JWT Bearer token.\nPass it via the `Authorization: Bearer <token>` header.\nTokens expire; use `POST /api/auth/refresh` to rotate without re-authenticating.\n\n## Row-Level Security (RLS)\n\nUsers only see data for businesses they are assigned to. Admins and sysops see all businesses.\nEvery business-scoped endpoint enforces RLS automatically.\n","x-displayName":"Hospitality BI (VIP)"},{"name":"cbquickbooks","description":"\n# CBQUICKBOOKS\n\nQuickBooks Online integration for the Campaign Brain fleet — a thin,\nmulti-tenant **service-to-service I/O handler** sitting in front of Intuit's\nQuickBooks Online API. Other CB services (notably **cbmodels** for cash-position\nfabric datasets) call cbquickbooks instead of talking to Intuit directly, so\nOAuth tokens, refresh rotation, and per-account access control all live here in\none place.\n\n## How it works\n\n- **One account = one `slug`.** Every connected QuickBooks company is registered\n  in `accounts.yaml` under a short slug (e.g. `greenroom`). All passthrough\n  routes are namespaced under `/api/v1/{slug}/...`.\n- **OAuth is fully managed.** Tokens are seeded once (via\n  `scripts/seed_token.py`) and persisted per-slug under the token directory.\n  cbquickbooks auto-refreshes the access token before each call and rotates the\n  refresh token; `/refresh` forces an immediate rotation.\n- **Three permission layers** gate every passthrough call: the slug must exist,\n  the caller must hold the `cbquickbooks.{slug}.access` permission (when\n  enforcement is on), and the caller's principal email must be mapped in that\n  account's `users` block.\n- **Entity passthrough is allowlisted.** Only canonical QuickBooks entity names\n  (Account, Invoice, Bill, Customer, Vendor, …) are accepted; unknown names are\n  rejected with 400 before any upstream call.\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/{slug}/status` | Connection + token expiry report |\n| `/api/v1/{slug}/query` | Passthrough QB SQL-like query |\n| `/api/v1/{slug}/entity/{Entity}[/{id}]` | Passthrough entity CRUD |\n| `/api/v1/{slug}/refresh` | Force a token rotation |\n| `/api/v1/datasets` · `/api/v1/datasets/{id}/rows` | Fabric dataset catalog + row stream (cbmodels integration) |\n| `/health` | Health check |\n| `/health/detailed` | Detailed health — includes per-slug token status |\n| `/status`, `/prime`, `/docs`, `/redoc` | CBApp standard endpoints |\n\n## Error semantics\n\nUpstream and configuration problems map to stable status codes: `404` unknown\nslug, `403` permission denied / principal not mapped, `409` account not\nconnected or refresh token expired (with a re-seed remedy), `400` disallowed\nentity, and `502` when Intuit's OAuth or QuickBooks API itself rejects a call.\n","x-displayName":"QuickBooks Integration"},{"name":"cbradio","description":"\n## Overview\n\nThe Rural AM/FM Rate Ingestion API provides programmatic access to radio station political advertising rates\nfor AI-powered media buying applications. It enables campaign managers and media buyers to query rates,\nbuild proposals, and manage radio advertising buys across 2,000+ stations nationwide.\n\n### Data Sources\n\n| Source | Records | Description |\n|--------|---------|-------------|\n| Stations | 2,023 | AM/FM radio stations (1,602 in-network) |\n| Rates | 30,039 | Political advertising rates extracted via AI |\n| Documents | 5,085 | Rate cards, agreements, contracts |\n| States | 47 | Coverage across continental US |\n\n### Key Features\n\n- **Station Management**: Query 2,000+ radio stations with metadata (callsign, format, market, contacts)\n- **Rate Queries**: Search and filter political advertising rates by station, state, ad type, duration\n- **Proposal Builder**: Auto-generate media buy proposals from campaign parameters\n- **Document Tracking**: Monitor rate card document processing status and extraction results\n- **Geographic Data**: FCC coverage contours and spatial queries\n\n### Rate Data Model\n\nRates are classified by:\n- **Ad Type**: `candidate` (subject to LUR), `issue` (PAC ads), or `all`\n- **Preemption**: `non_preemptible` (guaranteed), `preemptible` (can be bumped)\n- **Daypart**: `AMD` (morning), `MID` (midday), `PMD` (afternoon), `EVE` (evening), `OVN` (overnight), `ROS` (run of station)\n- **Time Slot**: Day pattern (`m-f`, `sat-sun`, etc.) and time range\n- **Duration**: 15, 30, 60, 90, or 120 seconds\n- **Pricing**: Gross rate (before commission) and net rate (after 15% commission)\n\n### Political Advertising Context\n\n- **LUR (Lowest Unit Rate)**: Candidates get lowest rate charged to any advertiser for same class/time\n- **LUR Window**: 45 days before a primary or primary runoff, 60 days before a general or special election (47 U.S.C. 315(b)). Derived from the election date — see `rate_context` on the proposal endpoints, and cbradio#376\n- **Candidate vs Issue**: Candidate ads have LUR protection; issue/PAC ads do not\n- **In-Network**: Stations with signed Rural AM/FM agreements get priority and negotiated rates\n\n### Integration Notes\n\n- All endpoints return JSON\n- Dates use ISO 8601 format (YYYY-MM-DD)\n- Times use 24-hour format (HH:MM)\n- Currency values are decimal (e.g., 25.00)\n- Use `station` parameter for exact callsign match, `callsign` for partial match\n- Use `campaign_type` as alias for `ad_type` parameter\n\n### For AI Systems\n\nThis API is designed for integration with AI agents. Key endpoints:\n- `/api/v1/rates/summary` - Quick overview of rate statistics\n- `/api/v1/rates?station=KAAA-AM&campaign_type=candidate` - Query rates for specific station\n- `/api/v1/stations?state=TX&is_in_network=true` - Filter in-network stations by state\n- `/api/v1/proposals/generate` - Auto-generate proposal from campaign parameters\n\n### Changelog\n\n**v1.37.0 (2026-02-10)**\n- **District Voters Endpoint**: `GET /districts/{geoid}/voters` — reverse lookup: given a county FIPS, returns voter data + all covering stations\n- **Generate Default Placements**: `POST /radio-buys/{id}/placements/generate-defaults` — auto-creates placements from proposal line items or radio_buy_station fallback\n- **Guards**: 409 Conflict if placements already exist; audit-logged action\n\n**v1.36.0 (2026-02-10)**\n- **Station Voter Data (CPE)**: Pre-aggregated voter universe data per station via coverage-weighted i360 county data\n- **New Endpoint**: `GET /stations/{callsign}/voters` - Voter counts, estimated listeners, and per-county breakdown\n- **Coverage Cache**: 62,863 station-to-county coverage rows from districts.campaignbrain.dev (8,714 stations × 3,224 counties)\n- **County Voter Table**: `county_voter_data` for i360 voter counts per county\n- **Station format_source**: Now included in station detail and list responses\n- **Scripts**: `backfill_county_coverage.py` (coverage cache), `backfill_county_voters.py` (voter data), `migrate_county_voters.py` (schema)\n\n**v1.35.0 (2026-02-10)**\n- **Nielsen Derived Fields**: Station scoring with market_station_rank, market_percentile, is_top_quartile, ratings_score (0-4)\n- **New Endpoint**: `GET /nielsen/by-geography` - Bulk station ratings by state/market with filters (format, min_share, top_quartile_only)\n- **Proposal station_pool**: `station_pool` field on proposals ('all' or 'network_only') — persists through sessions and carries to radio buy on conversion\n- **Migration Scripts**: `migrate_nielsen_derived.py` (backfill derived fields), `migrate_station_pool.py` (add station_pool column)\n- **39 New Tests**: Nielsen derived fields, by-geography endpoint, station_pool CRUD\n\n**v1.34.0 (2026-02-10)**\n- **i360 Voter Data**: County-level voter propensity reports and search counts via cbworkflow → i360 Public API\n- **i360 Endpoints** (Auth Required):\n  - `POST /i360/county-report` - County-level voter/HH counts by propensity criteria\n  - `POST /i360/search-counts` - Aggregate voter universe counts (individuals, residences, mobiles, etc.)\n- **Nielsen Ratings API**: Read-only access to parsed Nielsen/Radio Online survey data (5,151 ratings across 239 markets)\n- **Nielsen Endpoints** (Auth Required):\n  - `GET /nielsen/stats` - Aggregate data counts\n  - `GET /nielsen/markets` - List markets with filters (state, rank, population)\n  - `GET /nielsen/markets/{rank}` - Market detail with station ratings\n  - `GET /nielsen/stations/{callsign}` - Station ratings history across markets\n  - `GET /nielsen/formats` - Format distribution summary\n- **New Files**: `src/api/services/workflow_client.py` (cbworkflow HTTP client), `src/api/routes/i360.py`, `src/api/routes/nielsen.py`\n- **45 New Tests**: 24 i360 + 21 Nielsen\n\n**v1.30.0 (2026-01-31)**\n- **Shareable Proposal Links**: Generate secure, shareable links for proposals via CBLinks service\n- **Protected Endpoints** (Auth Required):\n  - `POST /proposals/{id}/share-link` - Create share link via CBLinks\n  - `DELETE /proposals/{id}/share-link` - Revoke active share link\n  - `GET /proposals/{id}/share-link/status` - Get link status + analytics\n- **Public Endpoints** (No Auth):\n  - `GET /public/proposals/{slug}` - View proposal (filtered fields)\n  - `POST /public/proposals/{slug}/approve` - Client approves proposal\n- **Features**:\n  - Integration with CBLinks (links.campaignbrain.dev) for short URLs\n  - View tracking with IP-based deduplication (10-minute window)\n  - Client approval workflow with audit trail\n  - Filtered public view excludes sensitive data (commission, internal notes)\n- **Database Changes**: New tables `proposal_share_link`, `proposal_share_view`, `proposal_client_approval`; New columns on `proposal` table (`client_approved_at`, `client_approval_status`)\n\n**v1.21.0 (2026-01-25)**\n- **Traffic Assets Management**: Audio spots and NAB forms for order packets\n- **Traffic Asset Endpoints** (P1):\n  - `GET /radio-buys/{id}/traffic-assets` - List audio spots and NAB form\n  - `POST /radio-buys/{id}/traffic-assets` - Create asset metadata\n  - `POST /radio-buys/{id}/traffic-assets/upload-url` - Get presigned S3 URL\n  - `PATCH /traffic-assets/{id}` - Update label/rotation\n  - `DELETE /traffic-assets/{id}` - Delete asset\n  - `PUT /radio-buys/{id}/traffic-assets/reorder` - Reorder audio spots\n- **Features**:\n  - Multiple audio spots with rotation order/weight\n  - Single NAB form per radio buy\n  - MIME type and file size validation\n  - Presigned URL generation for direct S3 upload\n- **Database Changes**: New table `traffic_asset`\n\n**v1.20.0 (2026-01-25)**\n- **Order Packet System**: Complete station order generation, confirmation, and make-good workflow\n- **Order Packet Management** (P1):\n  - `POST /radio-buys/{id}/order-packets/generate` - Generate order packets grouped by payee\n  - `GET /radio-buys/{id}/order-packets` - List order packets for a radio buy\n  - `GET /order-packets/{id}` - Get single order packet with line items\n  - `PATCH /order-packets/{id}` - Update order packet details\n  - `POST /order-packets/{id}/generate-pdf` - Generate order sheet PDF\n  - `POST /order-packets/{id}/send` - Send order email to station\n  - `POST /order-packets/bulk-send` - Bulk send multiple orders\n  - `POST /order-packets/{id}/confirm` - Manual order confirmation\n  - `POST /order-packets/{id}/cancel` - Cancel order packet\n  - `GET /order-packets/{id}/audit-log` - Confirmation audit trail\n  - `GET /order-packets/dashboard` - Status summary and alerts\n- **Public Confirmation Endpoints** (No Auth):\n  - `GET /public/orders/{token}` - View order by confirmation token\n  - `POST /public/orders/{token}/confirm` - Confirm order via web link\n  - `POST /public/orders/{token}/make-good` - Submit make-good request\n- **Make-Good Workflow**:\n  - `GET /make-good-requests` - List make-good requests\n  - `GET /make-good-requests/{id}` - Get make-good request detail\n  - `PATCH /make-good-requests/{id}` - Resolve make-good request\n- **Database Changes**: New tables `order_packet`, `order_packet_line_item`, `estimate_number_sequence`, `order_confirmation`, `make_good_request`\n\n**v1.19.0 (2026-01-25)**\n- **SRM Agreements & Governance**: Complete agreement lifecycle and network exit workflow\n- **Agreements System** (P0):\n  - `GET/POST /agreements` - List and create agreements\n  - `GET/PATCH /agreements/{id}` - Get and update agreement\n  - `POST /agreements/{id}/transition` - State machine transitions (draft→pending→signed→expired/terminated)\n  - `POST /agreements/{id}/documents` - Upload agreement documents\n  - `DELETE /agreements/{id}/documents/{doc_id}` - Delete documents (soft delete)\n  - `GET /agreements/expiring` - Renewal alerts\n- **Network Exit Workflow** (P0):\n  - `GET/POST /network-exits` - List and create exit requests\n  - `GET /network-exits/{id}` - Get exit request detail\n  - `POST /network-exits/{id}/review` - Admin approve/reject\n  - `DELETE /network-exits/{id}` - Cancel pending request\n- **Audit Logging**: Immutable audit trail for all governance actions\n- **Database Changes**: New tables `agreement`, `agreement_document`, `network_exit_request`, `audit_log`\n\n**v1.18.0 (2026-01-25)**\n- **Ownership Groups Extension**: Extends payment_recipients with ownership group features\n- **New Fields**:\n  - `legal_name` - Full legal entity name for payment documents\n  - `memo_template` - Default memo line for checks\n  - `mailing_address` - Now supports structured JSON format (street, street2, city, state, zip)\n- **New Route**: `/api/v1/ownership-groups` (canonical), `/api/v1/payment-recipients` (legacy alias)\n- **Search Enhancement**: Search now matches both `name` and `legal_name` fields\n- **Database Changes**: Added `legal_name`, `memo_template` columns to `payment_recipient` table\n\n**v1.17.0 (2026-01-21)**\n- **Rate Availability & QA Signaling**: Resolves dual-source conflict for rate availability\n- **Phase 1 - Rate Summary on Stations** (P0):\n  - `GET /stations` now includes `rate_summary` object with rate card QA status\n  - Single source of truth: `has_rates`, `rate_card_review_status`, `rate_source`, `last_verified_at`\n- **Phase 2 - Rate Provenance** (P1):\n  - `GET /rates` now includes provenance fields: `rate_source`, `rate_card_review_status`, `rate_last_verified_at`\n  - `GET /stations/{callsign}/rates` includes same provenance fields\n- **Phase 3 - Rate Corrections System** (P2):\n  - `POST /rate-corrections` - Create rate correction (any authenticated user)\n  - `GET /rate-corrections` - List corrections with filters\n  - `GET /rate-corrections/{id}` - Get correction details\n  - `POST /rate-corrections/{id}/verify` - Verify correction (admin only)\n  - `POST /rate-corrections/{id}/reject` - Reject correction (admin only)\n  - `GET /stations/{callsign}/rate-history` - Rate change audit trail\n- **Database Changes**: New `rate_correction` table for rate corrections overlay\n\n**v1.15.0 (2026-01-20)**\n- **Bug Fixes**:\n  - `GET /rates?station=X` - Fixed case-sensitivity issue (BUG-001): station filter now works regardless of callsign case in database\n  - `GET /fcc/stations` - Increased pagination limit from 1000 to 5000 (BUG-002): supports states with 1000+ stations (e.g., Texas)\n- **Radio Buy Placements** (P1):\n  - `GET /radio-buys/{id}/placements` - List station placements for a radio buy\n  - `POST /radio-buys/{id}/placements` - Add a station placement to a buy (calculates totals automatically)\n  - `DELETE /radio-buys/{id}/placements/{pid}` - Remove a placement\n- **Schema Changes**:\n  - Added `status` column to radio_buy (draft/pending/approved/placed/completed/cancelled)\n  - Added `proposal_id` column to radio_buy (link to source proposal)\n  - New table `radio_buy_placement` for station-level line items\n- **Note**: Radio Buys CRUD endpoints already existed (GET/POST/PATCH/DELETE) - only placements were added\n\n**v1.14.0 (2026-01-19)**\n- **Rate Card QA Workflow**: 8 new endpoints for QA review workflow per frontend integration spec\n- **Claim/Unclaim Workflow** (P0):\n  - `POST /rate-cards/{id}/claim` - Claim rate card for review (prevents duplicate work)\n  - `POST /rate-cards/{id}/unclaim` - Release rate card for reassignment\n  - `GET /rate-cards/my-claims` - Get user's currently claimed rate cards\n- **QA Detail View** (P0):\n  - `GET /rate-cards/{id}/qa-detail` - Full rate card data with extracted rates, warnings, corrections\n- **Corrections Export** (P1):\n  - `GET /rate-cards/corrections/export` - Export corrections for OCR training (JSON/CSV)\n- **Warnings Management** (P1):\n  - `GET /rate-cards/{id}/warnings` - Get all warnings for a rate card with severity summary\n  - `POST /rate-cards/{id}/warnings/{wid}/acknowledge` - Acknowledge warning with audit trail\n- **QA Statistics** (P2):\n  - `GET /rate-cards/qa-stats` - Queue health, reviewer activity, correction trends\n- **Database Changes**: 2 new columns on rate_card (claimed_by, claimed_at), 2 new tables (rate_card_claims, rate_card_warnings)\n\n**v1.13.0 (2026-01-19)**\n- **Google Drive Data Migration**: New tables and endpoints for importing data from Google Drive Buy Tracker\n- **Simulcast Groups** (6 endpoints):\n  - `GET/POST /simulcast-groups` - List and create simulcast groups\n  - `GET/PUT/DELETE /simulcast-groups/{id}` - CRUD operations\n  - `GET /simulcast-groups/by-callsign/{callsign}` - Find group by station callsign\n- **Payment Recipients** (7 endpoints):\n  - `GET/POST /payment-recipients` - List and create payment recipients\n  - `GET/PUT /payment-recipients/{id}` - Get and update recipients\n  - `POST/DELETE /payment-recipients/{id}/stations/{callsign}` - Add/remove stations\n  - `GET /payment-recipients/by-station/{callsign}` - Find recipient by station\n- **Import Staging** (8 endpoints):\n  - `POST /import/batches` - Create import batch with records\n  - `GET /import/batches/{id}` - Get batch status\n  - `GET /import/batches` - List batches\n  - `GET /import/staged` - List staged records\n  - `GET /import/staged/{id}` - Get staged record\n  - `POST /import/staged/{id}/corrections` - Apply corrections\n  - `POST /import/staged/{id}/approve` - Approve record\n  - `POST /import/staged/bulk-approve` - Bulk approve\n  - `POST /import/staged/{id}/reject` - Reject record\n- **Radio Buy Commission Splits**:\n  - `PUT /radio-buys/{id}/commission-splits` - Update commission splits with split tracking\n- **Database Changes**: 11 new columns on radio_buy, 4 new tables (simulcast_group, payment_recipient, import_staging, import_batch)\n\n**v1.12.0 (2026-01-17)**\n- **API Spec Final**: 9 new endpoints for aggregation, approval workflow, and review workflow\n- **Section 1 - Aggregation Endpoints**:\n  - `GET /stations/coverage-summary` - Station coverage statistics by state, DMA, format, lifecycle\n  - `GET /rate-cards/summary` - Rate card statistics by year, review status, type\n  - `GET /proposals/metrics` - Proposal conversion rates, top clients, recent activity\n- **Section 2 - Proposal Approval Workflow**:\n  - `GET /proposals/approval-queue` - List proposals pending approval\n  - `POST /proposals/{id}/submit-for-approval` - Submit proposal for manager approval\n  - `POST /proposals/{id}/review-decision` - Approve, reject, or request revision\n- **Section 3 - Rate Card Review Workflow**:\n  - `GET /rate-cards/review-queue` - List rate cards pending review\n  - `POST /rate-cards/{id}/review` - Submit review decision with optional corrections\n  - `POST /rate-cards/bulk-review` - Bulk approve/reject multiple rate cards\n- **Database Changes**: New tables for approval/review history and corrections tracking\n- **17 New Tests**: Comprehensive test coverage for all 9 endpoints\n\n**v1.11.0 (2026-01-16)**\n- **SRM Phase 4**: Integration with existing station endpoints\n- **Station List Enhancement**: Added `lifecycle_state` and `last_contacted_at` fields to station list responses\n- **Station Detail Enhancement**: Embedded SRM data object with full lifecycle, contact, and agreement information\n- **New Filter**: Added `lifecycle_state` filter parameter to `/stations` and `/stations/count` endpoints\n- **11 Integration Tests**: Comprehensive tests verifying SRM data flows through station APIs\n\n**v1.10.0 (2026-01-16)**\n- **SRM Phase 3**: Dashboard analytics, alerts, and computed signals\n- **6 New Endpoints**: Dashboard metrics, state/DMA coverage, alerts list/dismiss, station signals\n- **Dashboard Metrics**: Station counts by lifecycle, task summaries, contact activity\n- **Coverage Analytics**: Network penetration by state and DMA with opportunity scoring\n- **Alert System**: Auto-generated alerts for agreement expiration, no contact, ownership changes\n- **Station Signals**: Computed priority scores, relationship health, outreach urgency, recommendations\n- **Alert Generation Script**: Nightly cron job for alert creation (scripts/generate_alerts.py)\n- **19 New Tests**: Comprehensive coverage for dashboard and analytics\n\n**v1.9.0 (2026-01-16)**\n- **SRM Phase 2**: Task management system for sales rep outreach workflow\n- **7 New Endpoints**: GET /tasks, GET /tasks/{id}, POST /tasks (create), POST /tasks/{id}/claim, POST /tasks/{id}/unclaim, POST /tasks/{id}/complete, POST /tasks/{id}/cancel\n- **Task Types**: initial_outreach, follow_up, relationship_maintenance, agreement_renewal, win_back, ownership_change\n- **Task Workflow**: Claim/unclaim/complete with automatic contact logging and lifecycle state updates\n- **Task Generation Script**: Automated task creation based on station SRM state (scripts/generate_tasks.py)\n- **18 New Tests**: Comprehensive test coverage for task management\n\n**v1.8.0 (2026-01-16)**\n- **SRM Phase 1**: Station Relationship Management with lifecycle tracking, contact logging, and state machine transitions\n- **4 New Endpoints**: GET/PATCH `/stations/{callsign}/srm`, GET/POST `/stations/{callsign}/contacts`\n- **7 Lifecycle States**: prospect, contacted, negotiating, active, at_risk, churned, inactive\n- **12 Contact Outcomes**: Positive (interested, meeting_scheduled, etc.), Neutral (callback_requested), Negative (not_interested)\n- **Auto-Advance Rules**: Contact outcomes automatically advance lifecycle state based on configured rules\n- **25 New Tests**: Comprehensive test coverage for SRM functionality\n\n**v1.7.0 (2026-01-15)**\n- **FCC LMS Integration**: Complete FCC station registry with 15,359 licensed AM/FM stations\n- **6 New FCC Endpoints**: `/fcc/stations`, `/fcc/stations/{facility_id}`, `/fcc/lookup/{callsign}`, `/fcc/partners`, `/fcc/stats`, `/fcc/states`\n- **Partner Discovery**: Query unmatched FCC stations for outreach to potential new partners\n- **Station Matching**: 90.5% of our stations now linked to FCC facility IDs (was 19.6%)\n- **Daily Sync**: Automated cron job pulls fresh FCC data at 4 AM\n\n**v1.6.2 (2026-01-15)**\n- **Station Classification Fields**: Added `is_salem` boolean and `classification` field to all station responses\n- **Classification Values**: `salem`, `in_network`, `out_of_network`, `unknown` - single source of truth\n- **Commission Rate Support**: Classifications align with commission calculations (Salem 30%, In-Network 50%, Out-of-Network 15%)\n\n**v1.6.1 (2026-01-14)**\n- **FCC Facility ID in Stations API**: Added `fcc_facility_id` field to station list and detail responses\n- **Geo Facility Lookup**: New `/geo/facilities/{facility_id}` endpoint for lookup by FCC facility ID\n- **Bulk Geo Lookup**: New `/geo/facilities/bulk` POST endpoint for batch facility ID lookups (up to 100)\n- **Coverage Map Support**: Enables coverage map generation with canonical FCC identifiers\n\n**v1.6.0 (2026-01-14)**\n- **Buy Revisions & Payments Tracking**: New versioning system for radio buys with immutable snapshots\n- **8 New Endpoints**: Create/list versions, update payment status, compute deltas, export spreadsheets\n- **Station Classification**: Automatic classification (in_network 50%, salem 30%, out_of_network 15%)\n- **Payee Groups**: Stations grouped by pay_to::payment_method for payment tracking\n- **Proposal Conversion**: Now creates radio_buy_station records and auto-creates v1\n- **Airtable Cron Sync**: Daily sync at 3:00 AM via pyairtable API\n\n**v1.5.0 (2026-01-14)**\n- **GitHub Issues API**: New `/api/v1/issues` endpoints for code agent interaction with GitHub Issues\n- **19 CB* Projects**: Full registry of Campaign Brain projects with descriptions\n- **Issue Operations**: List, search, create, update, close, reopen, delete, comment, pin, lock\n- **Cross-Project Search**: Search issues across multiple projects\n- **Bulk Operations**: Add/remove labels and close multiple issues at once\n\n**v1.4.0 (2026-01-13)**\n- **Station validation flags**: Added `has_rates`, `rate_count`, `min_gross_rate`, `max_gross_rate`, `available_durations`, `available_ad_types` to station responses (eliminates N+1 query problem)\n- **Rate diagnostics**: Added `?include_diagnostics=true` parameter to explain why no rates matched\n- **Expanded daypart normalization**: Improved from 33% to 89%+ accuracy for slot name → daypart mapping\n\n**v1.3.0 (2026-01-12)**\n- Added `station` parameter for exact callsign match on rates endpoint\n- Added `campaign_type` as alias for `ad_type` parameter\n- Added computed `daypart_id` field to all rate responses\n- Fixed proposal export date parsing\n- Enhanced API documentation\n\n**v1.2.0 (2026-01-08)**\n- Added Airtable sync scripts and document triage\n- Added proposal CSV/Excel export\n- Completed rate deduplication (removed 13,399 duplicates)\n- Backfilled station_state on 94% of rates\n","x-displayName":"Rural AM/FM Rate Ingestion"},{"name":"cbradio-qa","description":"\n## Overview\n\nThe Rural AM/FM Rate Ingestion API provides programmatic access to radio station political advertising rates\nfor AI-powered media buying applications. It enables campaign managers and media buyers to query rates,\nbuild proposals, and manage radio advertising buys across 2,000+ stations nationwide.\n\n### Data Sources\n\n| Source | Records | Description |\n|--------|---------|-------------|\n| Stations | 2,023 | AM/FM radio stations (1,602 in-network) |\n| Rates | 30,039 | Political advertising rates extracted via AI |\n| Documents | 5,085 | Rate cards, agreements, contracts |\n| States | 47 | Coverage across continental US |\n\n### Key Features\n\n- **Station Management**: Query 2,000+ radio stations with metadata (callsign, format, market, contacts)\n- **Rate Queries**: Search and filter political advertising rates by station, state, ad type, duration\n- **Proposal Builder**: Auto-generate media buy proposals from campaign parameters\n- **Document Tracking**: Monitor rate card document processing status and extraction results\n- **Geographic Data**: FCC coverage contours and spatial queries\n\n### Rate Data Model\n\nRates are classified by:\n- **Ad Type**: `candidate` (subject to LUR), `issue` (PAC ads), or `all`\n- **Preemption**: `non_preemptible` (guaranteed), `preemptible` (can be bumped)\n- **Daypart**: `AMD` (morning), `MID` (midday), `PMD` (afternoon), `EVE` (evening), `OVN` (overnight), `ROS` (run of station)\n- **Time Slot**: Day pattern (`m-f`, `sat-sun`, etc.) and time range\n- **Duration**: 15, 30, 60, 90, or 120 seconds\n- **Pricing**: Gross rate (before commission) and net rate (after 15% commission)\n\n### Political Advertising Context\n\n- **LUR (Lowest Unit Rate)**: Candidates get lowest rate charged to any advertiser for same class/time\n- **LUR Window**: 45 days before a primary or primary runoff, 60 days before a general or special election (47 U.S.C. 315(b)). Derived from the election date — see `rate_context` on the proposal endpoints, and cbradio#376\n- **Candidate vs Issue**: Candidate ads have LUR protection; issue/PAC ads do not\n- **In-Network**: Stations with signed Rural AM/FM agreements get priority and negotiated rates\n\n### Integration Notes\n\n- All endpoints return JSON\n- Dates use ISO 8601 format (YYYY-MM-DD)\n- Times use 24-hour format (HH:MM)\n- Currency values are decimal (e.g., 25.00)\n- Use `station` parameter for exact callsign match, `callsign` for partial match\n- Use `campaign_type` as alias for `ad_type` parameter\n\n### For AI Systems\n\nThis API is designed for integration with AI agents. Key endpoints:\n- `/api/v1/rates/summary` - Quick overview of rate statistics\n- `/api/v1/rates?station=KAAA-AM&campaign_type=candidate` - Query rates for specific station\n- `/api/v1/stations?state=TX&is_in_network=true` - Filter in-network stations by state\n- `/api/v1/proposals/generate` - Auto-generate proposal from campaign parameters\n\n### Changelog\n\n**v1.37.0 (2026-02-10)**\n- **District Voters Endpoint**: `GET /districts/{geoid}/voters` — reverse lookup: given a county FIPS, returns voter data + all covering stations\n- **Generate Default Placements**: `POST /radio-buys/{id}/placements/generate-defaults` — auto-creates placements from proposal line items or radio_buy_station fallback\n- **Guards**: 409 Conflict if placements already exist; audit-logged action\n\n**v1.36.0 (2026-02-10)**\n- **Station Voter Data (CPE)**: Pre-aggregated voter universe data per station via coverage-weighted i360 county data\n- **New Endpoint**: `GET /stations/{callsign}/voters` - Voter counts, estimated listeners, and per-county breakdown\n- **Coverage Cache**: 62,863 station-to-county coverage rows from districts.campaignbrain.dev (8,714 stations × 3,224 counties)\n- **County Voter Table**: `county_voter_data` for i360 voter counts per county\n- **Station format_source**: Now included in station detail and list responses\n- **Scripts**: `backfill_county_coverage.py` (coverage cache), `backfill_county_voters.py` (voter data), `migrate_county_voters.py` (schema)\n\n**v1.35.0 (2026-02-10)**\n- **Nielsen Derived Fields**: Station scoring with market_station_rank, market_percentile, is_top_quartile, ratings_score (0-4)\n- **New Endpoint**: `GET /nielsen/by-geography` - Bulk station ratings by state/market with filters (format, min_share, top_quartile_only)\n- **Proposal station_pool**: `station_pool` field on proposals ('all' or 'network_only') — persists through sessions and carries to radio buy on conversion\n- **Migration Scripts**: `migrate_nielsen_derived.py` (backfill derived fields), `migrate_station_pool.py` (add station_pool column)\n- **39 New Tests**: Nielsen derived fields, by-geography endpoint, station_pool CRUD\n\n**v1.34.0 (2026-02-10)**\n- **i360 Voter Data**: County-level voter propensity reports and search counts via cbworkflow → i360 Public API\n- **i360 Endpoints** (Auth Required):\n  - `POST /i360/county-report` - County-level voter/HH counts by propensity criteria\n  - `POST /i360/search-counts` - Aggregate voter universe counts (individuals, residences, mobiles, etc.)\n- **Nielsen Ratings API**: Read-only access to parsed Nielsen/Radio Online survey data (5,151 ratings across 239 markets)\n- **Nielsen Endpoints** (Auth Required):\n  - `GET /nielsen/stats` - Aggregate data counts\n  - `GET /nielsen/markets` - List markets with filters (state, rank, population)\n  - `GET /nielsen/markets/{rank}` - Market detail with station ratings\n  - `GET /nielsen/stations/{callsign}` - Station ratings history across markets\n  - `GET /nielsen/formats` - Format distribution summary\n- **New Files**: `src/api/services/workflow_client.py` (cbworkflow HTTP client), `src/api/routes/i360.py`, `src/api/routes/nielsen.py`\n- **45 New Tests**: 24 i360 + 21 Nielsen\n\n**v1.30.0 (2026-01-31)**\n- **Shareable Proposal Links**: Generate secure, shareable links for proposals via CBLinks service\n- **Protected Endpoints** (Auth Required):\n  - `POST /proposals/{id}/share-link` - Create share link via CBLinks\n  - `DELETE /proposals/{id}/share-link` - Revoke active share link\n  - `GET /proposals/{id}/share-link/status` - Get link status + analytics\n- **Public Endpoints** (No Auth):\n  - `GET /public/proposals/{slug}` - View proposal (filtered fields)\n  - `POST /public/proposals/{slug}/approve` - Client approves proposal\n- **Features**:\n  - Integration with CBLinks (links.campaignbrain.dev) for short URLs\n  - View tracking with IP-based deduplication (10-minute window)\n  - Client approval workflow with audit trail\n  - Filtered public view excludes sensitive data (commission, internal notes)\n- **Database Changes**: New tables `proposal_share_link`, `proposal_share_view`, `proposal_client_approval`; New columns on `proposal` table (`client_approved_at`, `client_approval_status`)\n\n**v1.21.0 (2026-01-25)**\n- **Traffic Assets Management**: Audio spots and NAB forms for order packets\n- **Traffic Asset Endpoints** (P1):\n  - `GET /radio-buys/{id}/traffic-assets` - List audio spots and NAB form\n  - `POST /radio-buys/{id}/traffic-assets` - Create asset metadata\n  - `POST /radio-buys/{id}/traffic-assets/upload-url` - Get presigned S3 URL\n  - `PATCH /traffic-assets/{id}` - Update label/rotation\n  - `DELETE /traffic-assets/{id}` - Delete asset\n  - `PUT /radio-buys/{id}/traffic-assets/reorder` - Reorder audio spots\n- **Features**:\n  - Multiple audio spots with rotation order/weight\n  - Single NAB form per radio buy\n  - MIME type and file size validation\n  - Presigned URL generation for direct S3 upload\n- **Database Changes**: New table `traffic_asset`\n\n**v1.20.0 (2026-01-25)**\n- **Order Packet System**: Complete station order generation, confirmation, and make-good workflow\n- **Order Packet Management** (P1):\n  - `POST /radio-buys/{id}/order-packets/generate` - Generate order packets grouped by payee\n  - `GET /radio-buys/{id}/order-packets` - List order packets for a radio buy\n  - `GET /order-packets/{id}` - Get single order packet with line items\n  - `PATCH /order-packets/{id}` - Update order packet details\n  - `POST /order-packets/{id}/generate-pdf` - Generate order sheet PDF\n  - `POST /order-packets/{id}/send` - Send order email to station\n  - `POST /order-packets/bulk-send` - Bulk send multiple orders\n  - `POST /order-packets/{id}/confirm` - Manual order confirmation\n  - `POST /order-packets/{id}/cancel` - Cancel order packet\n  - `GET /order-packets/{id}/audit-log` - Confirmation audit trail\n  - `GET /order-packets/dashboard` - Status summary and alerts\n- **Public Confirmation Endpoints** (No Auth):\n  - `GET /public/orders/{token}` - View order by confirmation token\n  - `POST /public/orders/{token}/confirm` - Confirm order via web link\n  - `POST /public/orders/{token}/make-good` - Submit make-good request\n- **Make-Good Workflow**:\n  - `GET /make-good-requests` - List make-good requests\n  - `GET /make-good-requests/{id}` - Get make-good request detail\n  - `PATCH /make-good-requests/{id}` - Resolve make-good request\n- **Database Changes**: New tables `order_packet`, `order_packet_line_item`, `estimate_number_sequence`, `order_confirmation`, `make_good_request`\n\n**v1.19.0 (2026-01-25)**\n- **SRM Agreements & Governance**: Complete agreement lifecycle and network exit workflow\n- **Agreements System** (P0):\n  - `GET/POST /agreements` - List and create agreements\n  - `GET/PATCH /agreements/{id}` - Get and update agreement\n  - `POST /agreements/{id}/transition` - State machine transitions (draft→pending→signed→expired/terminated)\n  - `POST /agreements/{id}/documents` - Upload agreement documents\n  - `DELETE /agreements/{id}/documents/{doc_id}` - Delete documents (soft delete)\n  - `GET /agreements/expiring` - Renewal alerts\n- **Network Exit Workflow** (P0):\n  - `GET/POST /network-exits` - List and create exit requests\n  - `GET /network-exits/{id}` - Get exit request detail\n  - `POST /network-exits/{id}/review` - Admin approve/reject\n  - `DELETE /network-exits/{id}` - Cancel pending request\n- **Audit Logging**: Immutable audit trail for all governance actions\n- **Database Changes**: New tables `agreement`, `agreement_document`, `network_exit_request`, `audit_log`\n\n**v1.18.0 (2026-01-25)**\n- **Ownership Groups Extension**: Extends payment_recipients with ownership group features\n- **New Fields**:\n  - `legal_name` - Full legal entity name for payment documents\n  - `memo_template` - Default memo line for checks\n  - `mailing_address` - Now supports structured JSON format (street, street2, city, state, zip)\n- **New Route**: `/api/v1/ownership-groups` (canonical), `/api/v1/payment-recipients` (legacy alias)\n- **Search Enhancement**: Search now matches both `name` and `legal_name` fields\n- **Database Changes**: Added `legal_name`, `memo_template` columns to `payment_recipient` table\n\n**v1.17.0 (2026-01-21)**\n- **Rate Availability & QA Signaling**: Resolves dual-source conflict for rate availability\n- **Phase 1 - Rate Summary on Stations** (P0):\n  - `GET /stations` now includes `rate_summary` object with rate card QA status\n  - Single source of truth: `has_rates`, `rate_card_review_status`, `rate_source`, `last_verified_at`\n- **Phase 2 - Rate Provenance** (P1):\n  - `GET /rates` now includes provenance fields: `rate_source`, `rate_card_review_status`, `rate_last_verified_at`\n  - `GET /stations/{callsign}/rates` includes same provenance fields\n- **Phase 3 - Rate Corrections System** (P2):\n  - `POST /rate-corrections` - Create rate correction (any authenticated user)\n  - `GET /rate-corrections` - List corrections with filters\n  - `GET /rate-corrections/{id}` - Get correction details\n  - `POST /rate-corrections/{id}/verify` - Verify correction (admin only)\n  - `POST /rate-corrections/{id}/reject` - Reject correction (admin only)\n  - `GET /stations/{callsign}/rate-history` - Rate change audit trail\n- **Database Changes**: New `rate_correction` table for rate corrections overlay\n\n**v1.15.0 (2026-01-20)**\n- **Bug Fixes**:\n  - `GET /rates?station=X` - Fixed case-sensitivity issue (BUG-001): station filter now works regardless of callsign case in database\n  - `GET /fcc/stations` - Increased pagination limit from 1000 to 5000 (BUG-002): supports states with 1000+ stations (e.g., Texas)\n- **Radio Buy Placements** (P1):\n  - `GET /radio-buys/{id}/placements` - List station placements for a radio buy\n  - `POST /radio-buys/{id}/placements` - Add a station placement to a buy (calculates totals automatically)\n  - `DELETE /radio-buys/{id}/placements/{pid}` - Remove a placement\n- **Schema Changes**:\n  - Added `status` column to radio_buy (draft/pending/approved/placed/completed/cancelled)\n  - Added `proposal_id` column to radio_buy (link to source proposal)\n  - New table `radio_buy_placement` for station-level line items\n- **Note**: Radio Buys CRUD endpoints already existed (GET/POST/PATCH/DELETE) - only placements were added\n\n**v1.14.0 (2026-01-19)**\n- **Rate Card QA Workflow**: 8 new endpoints for QA review workflow per frontend integration spec\n- **Claim/Unclaim Workflow** (P0):\n  - `POST /rate-cards/{id}/claim` - Claim rate card for review (prevents duplicate work)\n  - `POST /rate-cards/{id}/unclaim` - Release rate card for reassignment\n  - `GET /rate-cards/my-claims` - Get user's currently claimed rate cards\n- **QA Detail View** (P0):\n  - `GET /rate-cards/{id}/qa-detail` - Full rate card data with extracted rates, warnings, corrections\n- **Corrections Export** (P1):\n  - `GET /rate-cards/corrections/export` - Export corrections for OCR training (JSON/CSV)\n- **Warnings Management** (P1):\n  - `GET /rate-cards/{id}/warnings` - Get all warnings for a rate card with severity summary\n  - `POST /rate-cards/{id}/warnings/{wid}/acknowledge` - Acknowledge warning with audit trail\n- **QA Statistics** (P2):\n  - `GET /rate-cards/qa-stats` - Queue health, reviewer activity, correction trends\n- **Database Changes**: 2 new columns on rate_card (claimed_by, claimed_at), 2 new tables (rate_card_claims, rate_card_warnings)\n\n**v1.13.0 (2026-01-19)**\n- **Google Drive Data Migration**: New tables and endpoints for importing data from Google Drive Buy Tracker\n- **Simulcast Groups** (6 endpoints):\n  - `GET/POST /simulcast-groups` - List and create simulcast groups\n  - `GET/PUT/DELETE /simulcast-groups/{id}` - CRUD operations\n  - `GET /simulcast-groups/by-callsign/{callsign}` - Find group by station callsign\n- **Payment Recipients** (7 endpoints):\n  - `GET/POST /payment-recipients` - List and create payment recipients\n  - `GET/PUT /payment-recipients/{id}` - Get and update recipients\n  - `POST/DELETE /payment-recipients/{id}/stations/{callsign}` - Add/remove stations\n  - `GET /payment-recipients/by-station/{callsign}` - Find recipient by station\n- **Import Staging** (8 endpoints):\n  - `POST /import/batches` - Create import batch with records\n  - `GET /import/batches/{id}` - Get batch status\n  - `GET /import/batches` - List batches\n  - `GET /import/staged` - List staged records\n  - `GET /import/staged/{id}` - Get staged record\n  - `POST /import/staged/{id}/corrections` - Apply corrections\n  - `POST /import/staged/{id}/approve` - Approve record\n  - `POST /import/staged/bulk-approve` - Bulk approve\n  - `POST /import/staged/{id}/reject` - Reject record\n- **Radio Buy Commission Splits**:\n  - `PUT /radio-buys/{id}/commission-splits` - Update commission splits with split tracking\n- **Database Changes**: 11 new columns on radio_buy, 4 new tables (simulcast_group, payment_recipient, import_staging, import_batch)\n\n**v1.12.0 (2026-01-17)**\n- **API Spec Final**: 9 new endpoints for aggregation, approval workflow, and review workflow\n- **Section 1 - Aggregation Endpoints**:\n  - `GET /stations/coverage-summary` - Station coverage statistics by state, DMA, format, lifecycle\n  - `GET /rate-cards/summary` - Rate card statistics by year, review status, type\n  - `GET /proposals/metrics` - Proposal conversion rates, top clients, recent activity\n- **Section 2 - Proposal Approval Workflow**:\n  - `GET /proposals/approval-queue` - List proposals pending approval\n  - `POST /proposals/{id}/submit-for-approval` - Submit proposal for manager approval\n  - `POST /proposals/{id}/review-decision` - Approve, reject, or request revision\n- **Section 3 - Rate Card Review Workflow**:\n  - `GET /rate-cards/review-queue` - List rate cards pending review\n  - `POST /rate-cards/{id}/review` - Submit review decision with optional corrections\n  - `POST /rate-cards/bulk-review` - Bulk approve/reject multiple rate cards\n- **Database Changes**: New tables for approval/review history and corrections tracking\n- **17 New Tests**: Comprehensive test coverage for all 9 endpoints\n\n**v1.11.0 (2026-01-16)**\n- **SRM Phase 4**: Integration with existing station endpoints\n- **Station List Enhancement**: Added `lifecycle_state` and `last_contacted_at` fields to station list responses\n- **Station Detail Enhancement**: Embedded SRM data object with full lifecycle, contact, and agreement information\n- **New Filter**: Added `lifecycle_state` filter parameter to `/stations` and `/stations/count` endpoints\n- **11 Integration Tests**: Comprehensive tests verifying SRM data flows through station APIs\n\n**v1.10.0 (2026-01-16)**\n- **SRM Phase 3**: Dashboard analytics, alerts, and computed signals\n- **6 New Endpoints**: Dashboard metrics, state/DMA coverage, alerts list/dismiss, station signals\n- **Dashboard Metrics**: Station counts by lifecycle, task summaries, contact activity\n- **Coverage Analytics**: Network penetration by state and DMA with opportunity scoring\n- **Alert System**: Auto-generated alerts for agreement expiration, no contact, ownership changes\n- **Station Signals**: Computed priority scores, relationship health, outreach urgency, recommendations\n- **Alert Generation Script**: Nightly cron job for alert creation (scripts/generate_alerts.py)\n- **19 New Tests**: Comprehensive coverage for dashboard and analytics\n\n**v1.9.0 (2026-01-16)**\n- **SRM Phase 2**: Task management system for sales rep outreach workflow\n- **7 New Endpoints**: GET /tasks, GET /tasks/{id}, POST /tasks (create), POST /tasks/{id}/claim, POST /tasks/{id}/unclaim, POST /tasks/{id}/complete, POST /tasks/{id}/cancel\n- **Task Types**: initial_outreach, follow_up, relationship_maintenance, agreement_renewal, win_back, ownership_change\n- **Task Workflow**: Claim/unclaim/complete with automatic contact logging and lifecycle state updates\n- **Task Generation Script**: Automated task creation based on station SRM state (scripts/generate_tasks.py)\n- **18 New Tests**: Comprehensive test coverage for task management\n\n**v1.8.0 (2026-01-16)**\n- **SRM Phase 1**: Station Relationship Management with lifecycle tracking, contact logging, and state machine transitions\n- **4 New Endpoints**: GET/PATCH `/stations/{callsign}/srm`, GET/POST `/stations/{callsign}/contacts`\n- **7 Lifecycle States**: prospect, contacted, negotiating, active, at_risk, churned, inactive\n- **12 Contact Outcomes**: Positive (interested, meeting_scheduled, etc.), Neutral (callback_requested), Negative (not_interested)\n- **Auto-Advance Rules**: Contact outcomes automatically advance lifecycle state based on configured rules\n- **25 New Tests**: Comprehensive test coverage for SRM functionality\n\n**v1.7.0 (2026-01-15)**\n- **FCC LMS Integration**: Complete FCC station registry with 15,359 licensed AM/FM stations\n- **6 New FCC Endpoints**: `/fcc/stations`, `/fcc/stations/{facility_id}`, `/fcc/lookup/{callsign}`, `/fcc/partners`, `/fcc/stats`, `/fcc/states`\n- **Partner Discovery**: Query unmatched FCC stations for outreach to potential new partners\n- **Station Matching**: 90.5% of our stations now linked to FCC facility IDs (was 19.6%)\n- **Daily Sync**: Automated cron job pulls fresh FCC data at 4 AM\n\n**v1.6.2 (2026-01-15)**\n- **Station Classification Fields**: Added `is_salem` boolean and `classification` field to all station responses\n- **Classification Values**: `salem`, `in_network`, `out_of_network`, `unknown` - single source of truth\n- **Commission Rate Support**: Classifications align with commission calculations (Salem 30%, In-Network 50%, Out-of-Network 15%)\n\n**v1.6.1 (2026-01-14)**\n- **FCC Facility ID in Stations API**: Added `fcc_facility_id` field to station list and detail responses\n- **Geo Facility Lookup**: New `/geo/facilities/{facility_id}` endpoint for lookup by FCC facility ID\n- **Bulk Geo Lookup**: New `/geo/facilities/bulk` POST endpoint for batch facility ID lookups (up to 100)\n- **Coverage Map Support**: Enables coverage map generation with canonical FCC identifiers\n\n**v1.6.0 (2026-01-14)**\n- **Buy Revisions & Payments Tracking**: New versioning system for radio buys with immutable snapshots\n- **8 New Endpoints**: Create/list versions, update payment status, compute deltas, export spreadsheets\n- **Station Classification**: Automatic classification (in_network 50%, salem 30%, out_of_network 15%)\n- **Payee Groups**: Stations grouped by pay_to::payment_method for payment tracking\n- **Proposal Conversion**: Now creates radio_buy_station records and auto-creates v1\n- **Airtable Cron Sync**: Daily sync at 3:00 AM via pyairtable API\n\n**v1.5.0 (2026-01-14)**\n- **GitHub Issues API**: New `/api/v1/issues` endpoints for code agent interaction with GitHub Issues\n- **19 CB* Projects**: Full registry of Campaign Brain projects with descriptions\n- **Issue Operations**: List, search, create, update, close, reopen, delete, comment, pin, lock\n- **Cross-Project Search**: Search issues across multiple projects\n- **Bulk Operations**: Add/remove labels and close multiple issues at once\n\n**v1.4.0 (2026-01-13)**\n- **Station validation flags**: Added `has_rates`, `rate_count`, `min_gross_rate`, `max_gross_rate`, `available_durations`, `available_ad_types` to station responses (eliminates N+1 query problem)\n- **Rate diagnostics**: Added `?include_diagnostics=true` parameter to explain why no rates matched\n- **Expanded daypart normalization**: Improved from 33% to 89%+ accuracy for slot name → daypart mapping\n\n**v1.3.0 (2026-01-12)**\n- Added `station` parameter for exact callsign match on rates endpoint\n- Added `campaign_type` as alias for `ad_type` parameter\n- Added computed `daypart_id` field to all rate responses\n- Fixed proposal export date parsing\n- Enhanced API documentation\n\n**v1.2.0 (2026-01-08)**\n- Added Airtable sync scripts and document triage\n- Added proposal CSV/Excel export\n- Completed rate deduplication (removed 13,399 duplicates)\n- Backfilled station_state on 94% of rates\n","x-displayName":"Rural AM/FM Rate Ingestion (QA)"},{"name":"cbradio2","description":"\n# CBRADIO2\n\nRural AM/FM rate + media-buying backend (v2)\n\n## Quick Start\n\n```bash\ncurl https://radio2.campaignbrain.dev/health\n```\n\n## The shape\n\nResources are DECLARED, not hand-written, so the verbs come out identical every\ntime: `GET /api/v1/<resource>` (filtered, paginated, unpaged total in\n`X-Total-Count`), `GET /api/v1/<resource>/{id}`, `PATCH` where the resource\ndeclares writable columns, and `POST` only where a create can actually succeed.\n\n**There is no DELETE anywhere.** The fleet rule is soft delete, and for these\ntables \"gone\" is a status a person set, not a row that stopped existing.\n\n**No silent truncation.** Asking for more than the page maximum is a 422, and an\nunknown query parameter is a 422 naming what is accepted — a filter that was\nquietly ignored is a promise about the answer that was not kept.\n\n**Money is a string with two decimals**, parsed as a decimal and never a float.\n\n## Where to start\n\n| Path | What it answers |\n|------|-----------------|\n| `POST /api/v1/quote` | What do we quote for this station? (refuses rather than guesses) |\n| `POST /api/v1/plan` | Price a whole footprint, dropping nobody |\n| `GET /api/v1/buys/summary?cycle=` | The cycle's buys, commission and financials |\n| `GET /api/v1/stations` | Find a station, its contacts and its tier |\n| `/health`, `/status`, `/prime` | Service health, features and upstream readiness |\n| `/docs`, `/redoc` | Swagger UI / ReDoc |\n","x-displayName":"cbradio2"},{"name":"cbrouter","description":"\n# CBRouter\n\nMCP/API frontend for OpenWRT router management.\n\nManage OpenWRT routers through a clean REST API. Supports UCI config CRUD with\nrollback, network/firewall/wireless monitoring, package management, VPN control,\nand full system backup.\n\nAll endpoints accept `?router=<ip>` to target any router in the network\n(defaults to the main gateway at 192.168.1.1).\n\n---\n\n## Transports\n\n- **ubus HTTP**: Structured JSON access to UCI, system, and network data\n- **SSH**: Shell commands for nftables, opkg, file ops, and logs\n","x-displayName":"OpenWRT Router Manager"},{"name":"cbscout","description":"\n## Overview\n\nCB Scout provides agent memory and knowledge management capabilities for\nCampaign Brain AI agents. It implements a hierarchical memory system\nwith automatic categorization, temporal awareness, and decay.\n\n## Architecture\n\nScout uses a three-layer memory hierarchy:\n\n1. **Resources** (Layer 1): Immutable raw data (canvass logs, transcripts)\n2. **Items** (Layer 2): Atomic facts with temporal validity\n3. **Categories** (Layer 3): Evolving markdown summaries\n\n## Key Capabilities\n\n### Memory Storage\nStore and retrieve agent memories with automatic extraction and categorization.\nContent is processed into atomic facts that can be queried independently.\n\n### Temporal Awareness\nFacts have validity periods (valid_from/valid_until). When new information\nconflicts with existing facts, the old facts are archived and remain\navailable for historical queries.\n\n### Multi-Tenancy\nAll data is isolated by campaign_id. Every request must include the\nX-Campaign-ID header for tenant isolation.\n\n## How to Use This API\n\n### Write Path (Memorize)\n```\nPOST /api/v1/memories\nX-Campaign-ID: your-campaign-id\nContent-Type: application/json\n\n{\n  \"entity_id\": \"voter_123\",\n  \"entity_type\": \"voter\",\n  \"content\": \"Voter supports education funding.\",\n  \"resource_type\": \"canvass_log\"\n}\n```\n\n### Read Path (Retrieve)\n```\nGET /api/v1/memories/voter/voter_123\nX-Campaign-ID: your-campaign-id\n```\n\n## Integration\n\nScout integrates with other CB services:\n- **cbworkflow**: Workflow context and history\n- **cbintel**: Intelligence gathering results\n- **cbai**: LLM interaction memories\n","x-displayName":"Agent Memory"},{"name":"cbsearch","description":"\n# CBSEARCH\n\nMonroe Search — answer-engine control plane: catalog-gated agent orchestration over cbai/cbmodels federation\n\n## Quick Start\n\n```bash\ncurl https://search.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/catalogs` | Catalogs the caller's grants unlock |\n| `/api/v1/probes` | Probe suggestions for the idle screen |\n| `/api/v1/conversations` | Conversation ledger (CRUD, soft-delete) |\n| `/api/v1/workspaces` | Monroe Workspaces — shared tree, CRUD + `/tree` |\n| `/api/v1/projects` | Projects — the apex pointer container (PROJECTS.md) |\n| `/api/v1/catalog/tree` | Published catalog snapshot (cbcron-refreshed) |\n| `/api/v1/ask` | Run one ask turn (SSE stream via cbai agent loop) |\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Monroe Search API"},{"name":"cbship","description":"\n# CBSHIP\n\nCluster manager — day-0 machine enrollment. Accepts attested probe reports from\nmachines that are **not yet fleet members** and records them for placement.\n\n## The boundary\n\ncbship owns a machine until it is a fleet member, then gets out of the way.\ncbinfra/cbdc own everything after the handoff. This service does not provision,\ndeploy, or configure anything.\n\n## What a verified enrollment means\n\nAuthenticity, integrity, origin-continuity and freshness of the *report* — **not**\nthat the hardware is as described. The probe reads `/proc` and `/sys`, and root on\nthat box can make them say anything.\n\n> A machine reporting its own capabilities is a claim, not a fact.\n\nVerified nodes land as `pending`; an operator promotes them. Treat an enrollment\nas authenticated input to a placement decision, never as authorization.\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `POST /api/v1/enroll` | submit an attested artifact (**unauthenticated by design**) |\n| `POST /api/v1/tokens` | mint an enrollment token (secret returned **once**) |\n| `GET /api/v1/tokens` | list token metadata (never secrets) |\n| `POST /api/v1/tokens/{token_id}/revoke` | revoke + destroy key material |\n| `GET /api/v1/nodes` | list registered nodes |\n| `GET /api/v1/nodes/{fingerprint}` | one node |\n| `POST /api/v1/nodes/{fingerprint}/promote` | operator accept/reject (fires hand-off) |\n| `GET /api/v1/nodes/{fingerprint}/handoff` | the hand-off packet |\n| `POST /api/v1/nodes/{fingerprint}/handoff` | re-drive one hand-off |\n| `POST /api/v1/handoff/sweep` | re-drive undelivered hand-offs (cbcron) |\n| `GET /api/v1/capacity` | what the fleet has to offer, and why nodes are excluded |\n| `POST /api/v1/leases` | allocate capacity (409 + shortfall if nothing fits) |\n| `POST /api/v1/leases/{id}/renew` · `/release` | extend or return a lease |\n| `POST /api/v1/leases/reap` | mark expired leases released (cbcron) |\n| `POST /api/v1/nodes/refresh-sweep` | surface nodes with stale capacity data (cbcron) |\n| `/health` | health check |\n| `/docs` | Swagger UI |\n","x-displayName":"cbship"},{"name":"cbsme","description":"\n# CBSME\n\nSME Interview orchestration — legacy-system rule discovery via chattable\ninterviews.\n\nAn **engagement** pairs one legacy system with one subject-matter expert and\none tenant knowledge base. The interview loop is three operations the chat\nside (cbsearch/cbai) drives:\n\n| Tool | What it does |\n|------|--------------|\n| `discovery_agenda` | Where the interview stands — open questions by stage, next question to work. The KB is the state; correct across days and sessions. |\n| `discovery_kb_lookup` | Answer from the discovered rules, with KB citations. |\n| `discovery_resolve` | Record a consensus rule after the SME agrees to the exact wording. Attributed, no review gate. |\n| `data_catalog` / `data_query` | Raw-data evidence, forwarded to the cbmodels federation with the caller's identity. |\n\nThe knowledge base itself lives in **cbkb** (tenant-scoped); evidence data\nstays in the **cbmodels** federation. cbsme owns only the interview.\n\n## Quick Start\n\n```bash\ncurl https://sme.campaignbrain.dev/health\ncurl https://sme.campaignbrain.dev/api/v1/engagements\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/engagements` | Registered engagements |\n| `/api/v1/discovery/agenda` | Interview agenda from live KB state |\n| `/api/v1/discovery/lookup` | Cited answers from the engagement KB |\n| `/api/v1/discovery/resolve` | Record a consensus rule |\n| `/api/v1/data/catalogs` | Federation catalogs (forwarded to cbmodels) |\n| `/api/v1/data/query` | Cross-catalog SQL evidence (forwarded to cbmodels) |\n| `/health` | Health check |\n| `/docs` | Swagger UI |\n","x-displayName":"cbsme"},{"name":"cbsms","description":"\n## Overview\n\n**CBSMS** is a unified API for Ejoin SMS/MMS gateway devices. It provides queue-based\nmessage delivery with full tracking, device management, and webhook support.\n\n## Features\n\n- **SMS Sending** - Single and bulk SMS with delivery tracking\n- **MMS Support** - Send multimedia messages with attachments\n- **Task Management** - Queue-based sending with pause/resume/delete\n- **Device Control** - SIM status, signal monitoring, device commands\n- **Webhooks** - Receive incoming SMS and delivery reports\n\n## Quick Start\n\n### Send an SMS\n\n```bash\ncurl -X POST https://sms.campaignbrain.dev/api/sms/send \\\\\n  -H \"Content-Type: application/json\" \\\\\n  -d '{\n    \"line\": 1,\n    \"to\": \"+15551234567\",\n    \"message\": \"Hello from CBSMS!\"\n  }'\n```\n\n### Check Task Status\n\n```bash\ncurl https://sms.campaignbrain.dev/api/tasks/{task_id}\n```\n\n## Authentication\n\nWebhook endpoints require a `webhook-secret` header matching the configured secret.\n\n## Webhook Configuration\n\nConfigure your Ejoin device to send webhooks to:\n- **Incoming SMS**: `POST /api/webhooks/incoming`\n- **Delivery Reports**: `POST /api/webhooks/delivery`\n","x-displayName":"SMS/MMS Gateway"},{"name":"cbsnailmail","description":"\n# CBSNAILMAIL\n\nPhysical mail dispatch gateway — the offline peer to `cbsms` / `cbemail`. Push a\nrecipient + a rendered document, and a print-and-postage vendor prints it, mails\nit, and reports delivery back. cbsnailmail owns the platform layer (tenancy,\nledger, tracking, templating-via-cbpublish); the vendor is a **swappable dumb\nprimitive** behind a uniform provider interface (Lob today).\n\nThe flow is **render → dispatch → track**:\n\n1. **Render** — content comes from a cbpublish work product (`artifact_ref`,\n   resolved over the mesh), a direct `content_url`, or inline `content_html`.\n   cbsnailmail does not re-render; cbpublish owns print-quality document output.\n2. **Dispatch** — the active provider submits the piece for print + mailing.\n3. **Track** — provider webhooks advance the piece through the mail stream to\n   `delivered` / `returned`.\n\n## Stub-by-default (opt-in gate)\n\nPhysical mail is the one comms channel that can't be self-hosted — every vendor\nneeds an API key and charges real postage. So cbsnailmail ships **stubbed**:\nwith no key configured it *simulates* the lifecycle (`status=simulated`) — no\nexternal call, no cost. Set `CBSNAILMAIL_PROVIDER=lob` **and** supply a key to\ngo live. See `GET /api/v1/providers` for the current state.\n\n## Quick Start\n\n```bash\ncurl https://snailmail.campaignbrain.dev/health\ncurl https://snailmail.campaignbrain.dev/api/v1/providers\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/mailpieces` | Create + dispatch, list, get, soft-delete mailpieces |\n| `/api/v1/addresses/verify` | Address deliverability check + normalization |\n| `/api/v1/providers` | Which provider is live + opt-in gate state |\n| `/api/v1/webhooks/lob` | Inbound Lob delivery-status callbacks |\n| `/health` | Health check |\n| `/status` | Service status, features, and routes |\n| `/docs` | Swagger UI |\n","x-displayName":"Snailmail API"},{"name":"cbsocial","description":"\n# CBSocial — Social Messaging Hub\n\nCBSocial is Campaign Brain's bridge to consumer messaging platforms. It\ningests **WhatsApp** conversations through a local Baileys relay, exposes them\nas a queryable message/chat archive, consolidates them into AI-written\nMarkdown digests, and provides **send-only Slack** alerting — all behind one\nmesh-registered FastAPI service.\n\n## What it does\n\n- **WhatsApp control plane** — pair accounts (QR), send messages, and manage\n  per-account incoming-message webhooks. Each account is owned by a cbauth\n  user and scoped to a tenant. The relay it proxies is a non-`cb*` Node\n  process on localhost.\n- **Message archive** — every captured WhatsApp message is parsed from\n  `messages_root/<account>/<jid>/messages.jsonl` into an in-memory index and\n  served as paginated, filterable lists of messages and chats, with optional\n  inline base64 media.\n- **Consolidation pipeline** — a query (time window, sender, JID, type) selects\n  a slice of messages and asks cbai to fold them into a single Markdown digest,\n  persisted as a \"processed run\" with a manifest and a shareable HTML page.\n- **Gists** — a background loop periodically asks cbai for a short per-chat\n  summary (\"gist\") over a rolling window, retrievable as JSON or HTML.\n- **Slack send-only slots** — register a bot identity (token kept in SOPS,\n  referenced by name, never stored) and post outbound alerts to a channel.\n\n## Authorization model\n\nIdentity is resolved from headers the mesh/cbauth set upstream\n(`X-User-Id`, `X-User-Email`, `X-Tenant-Slug`, `X-User-Permissions`). Reads are\ntenant- and owner-scoped; mutating actions require ownership. Holders of\n`cbsocial.admin.*` (or a broader wildcard) see and act on everything.\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` · `/health/detailed` · `/status` | Liveness, component health, service status |\n| `/api/accounts…` | WhatsApp account control plane + per-account reads |\n| `/api/messages` · `/api/chats` | Cross-account message + chat archive |\n| `/api/media/{jid}/{file}` | Serve a WhatsApp media file |\n| `/api/process` · `/api/processed…` | Run + retrieve consolidation runs (JSON) |\n| `/processed…` · `/gists…` | Shareable HTML renders of runs + gists |\n| `/api/gists…` | Per-chat AI summary gists (JSON) |\n| `/api/slack/accounts…` · `/api/slack/send` | Slack slot registry + outbound alerts |\n| `/api/reload` | Re-scan the message archive from disk |\n| `/docs` | Swagger UI |\n","x-displayName":"WhatsApp Ingestion"},{"name":"cbsquare","description":"\n# CBSQUARE\n\nSquare payments gateway: donations and checkout for campaign properties\n\n## Quick Start\n\n```bash\ncurl https://square.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Square API"},{"name":"cbstitch","description":"\n# CBSTITCH — Virtual Service Runtime\n\nOne real `cb-` service: a FastAPI host that dynamically loads **virtual\nservices** — long-lived, stateful, endpoint-exposing controllers driven by a\nreconcile loop. cbstitch *drives* the `cbintel → cbmodels → cbweave → cbcast`\nvertical; it replaces none of it.\n\nA virtual service is a controller: it declares a *desired* state, observes\n*actual* state, and the host loop drives actual → desired idempotently, woken by\nan interval **or** an event.\n\n## Quick Start\n\n```bash\ncurl https://stitch.campaignbrain.dev/health\ncurl https://stitch.campaignbrain.dev/v          # list virtual services\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/v` | List virtual services (lifecycle + loop health) |\n| `/v/{name}` | VS status + controller-state snapshot |\n| `/v/{name}/load` `/start` `/stop` `/reload` | Lifecycle control (ephemeral) |\n| `/v/{name}/disable` `/enable` | Durable on/off (survives restart; `stop` does not) |\n| `/v/{name}/tick` | Externally poke a reconcile (cbcron) |\n| `/v/{name}/*` | A VS's own mounted routes + `openapi.json` |\n| `/health` `/status` `/prime` `/docs` | Standard sysforge surface |\n","x-displayName":"Virtual Service Runtime"},{"name":"cbstt","description":"\n# CBSTT — Speech-to-Text\n\nCBSTT is the Campaign Brain **speech-to-text** service (the sibling of cbtts,\nwhich does text-to-speech). It transcribes audio and video into text using a\nlocal [faster-whisper](https://github.com/SYSTRAN/faster-whisper) backend —\nno third-party API keys, no cloud STT. It is the transcription engine behind\ncbintel's radio/podcast ingest pipeline.\n\n## How it works\n\nTranscription is **asynchronous and job-based**. You submit audio, get back a\n`job_id` immediately (`202 Accepted`), then poll the job (or stream its events)\nuntil it reaches a terminal state and fetch the result in your preferred format.\n\n1. **Submit** — `POST /api/v1/transcriptions` (multipart upload) or\n   `POST /api/v1/transcriptions/from-url` (cbstt fetches a signed URL).\n2. **Track** — `GET /api/v1/jobs/{job_id}` for status/progress.\n3. **Retrieve** — `GET /api/v1/jobs/{job_id}/result?format=…` once the job\n   has `succeeded` (`json` · `verbose_json` · `text` · `srt` · `vtt`).\n4. **Cancel** (optional) — `POST /api/v1/jobs/{job_id}/cancel` (best-effort).\n\n## Model tiers\n\nMultiple whisper tiers can be loaded at once (`GET /api/v1/models`). Each\nsubmission may pin a `model=<tier>`; if omitted, the configured default tier is\nused. Unknown or unloaded tier names are rejected with `400`.\n\n## Conventions\n\n- **Idempotency** — send an `Idempotency-Key` header on submit; a repeat with\n  the same key returns the original job (`200`) instead of creating a new one.\n- **`metadata`** — an opaque ≤2 KB JSON object echoed back on every job view.\n- **Queue depth** — every response carries an `X-CBSTT-Queue-Depth` header with\n  the current count of `queued` jobs, so clients can autotune concurrency\n  without polling a separate endpoint.\n\n## Quick start\n\n```bash\n# health\ncurl https://stt.campaignbrain.dev/health\n\n# submit a file and capture the job id\ncurl -X POST https://stt.campaignbrain.dev/api/v1/transcriptions \\\n  -F \"file=@clip.mp3\"\n\n# fetch the result as SRT once it has succeeded\ncurl \"https://stt.campaignbrain.dev/api/v1/jobs/<job_id>/result?format=srt\"\n```\n\n## Platform endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Liveness check |\n| `/health/detailed` | Per-component health (STT engine, db) |\n| `/status` | Generic CBApp service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Speech-to-Text"},{"name":"cbsurveys","description":"\n# YASP - Yet Another Survey Platform\n\nA REST API for creating surveys, managing questions, and collecting responses.\nDesigned for programmatic integration with AI-directed platforms and automation systems.\n\n---\n\n## Quick Start\n\n### 1. Get an API Key\n\nThe first user and API key must be created via CLI:\n\n```bash\npython -m yasp.cli bootstrap --name \"Admin\" --email \"admin@example.com\"\n```\n\nThis outputs an API key like `yasp_xxxxx`. **Save it immediately** - it cannot be retrieved later.\n\n### 2. Authenticate Requests\n\nInclude the API key in all requests (except response submission):\n\n```bash\ncurl -H \"X-API-Key: yasp_xxxxx\" https://api.example.com/api/v1/users/me\n```\n\nOr use Bearer token format:\n\n```bash\ncurl -H \"Authorization: Bearer yasp_xxxxx\" https://api.example.com/api/v1/users/me\n```\n\n### 3. Create a Survey\n\n```bash\ncurl -X POST https://api.example.com/api/v1/surveys \\\n  -H \"X-API-Key: yasp_xxxxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"Customer Feedback\", \"description\": \"Q1 2025 survey\"}'\n```\n\n### 4. Add Questions\n\n```bash\ncurl -X POST https://api.example.com/api/v1/surveys/{survey_id}/questions \\\n  -H \"X-API-Key: yasp_xxxxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"type\": \"single_select\",\n    \"label\": \"satisfaction\",\n    \"text\": \"How satisfied are you?\",\n    \"required\": true,\n    \"options\": [\n      {\"value\": \"very_satisfied\", \"label\": \"Very Satisfied\"},\n      {\"value\": \"satisfied\", \"label\": \"Satisfied\"},\n      {\"value\": \"neutral\", \"label\": \"Neutral\"},\n      {\"value\": \"dissatisfied\", \"label\": \"Dissatisfied\"}\n    ]\n  }'\n```\n\n### 5. Publish the Survey\n\n```bash\ncurl -X POST https://api.example.com/api/v1/surveys/{survey_id}/publish \\\n  -H \"X-API-Key: yasp_xxxxx\"\n```\n\n### 6. Collect Responses (No Auth Required)\n\n```bash\ncurl -X POST https://api.example.com/api/v1/surveys/{survey_id}/responses \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"respondent_id\": \"user_123\",\n    \"answers\": [\n      {\"question_id\": \"...\", \"question_type\": \"single_select\", \"selected_value\": \"satisfied\"}\n    ]\n  }'\n```\n\n---\n\n## Core Concepts\n\n### Survey Lifecycle\n\nSurveys follow a state machine:\n\n```\nDRAFT ──► ACTIVE ──► INACTIVE ──► ARCHIVED\n  │         │           │            ▲\n  │         │           └────────────┤\n  │         └────────────────────────┤\n  └──────────────────────────────────┘\n```\n\n| State | Can Edit Questions | Accepts Responses | Description |\n|-------|-------------------|-------------------|-------------|\n| DRAFT | ✅ Yes | ❌ No | Initial state, configure questions |\n| ACTIVE | ❌ No | ✅ Yes | Published, collecting responses |\n| INACTIVE | ❌ No | ❌ No | Paused, can resume |\n| ARCHIVED | ❌ No | ❌ No | Terminal, read-only |\n\n### Question Types\n\nQuestions use a discriminated union pattern. The `type` field determines available properties:\n\n**checkbox** - Simple yes/no toggle\n```json\n{\n  \"type\": \"checkbox\",\n  \"label\": \"newsletter\",\n  \"text\": \"Subscribe to newsletter?\",\n  \"default_checked\": false\n}\n```\n\n**single_select** - Choose exactly one option (radio buttons)\n```json\n{\n  \"type\": \"single_select\",\n  \"label\": \"rating\",\n  \"text\": \"Rate your experience\",\n  \"options\": [\n    {\"value\": \"excellent\", \"label\": \"Excellent\"},\n    {\"value\": \"good\", \"label\": \"Good\"}\n  ],\n  \"allow_other\": false\n}\n```\n\n**multi_select** - Choose multiple options (checkboxes)\n```json\n{\n  \"type\": \"multi_select\",\n  \"label\": \"features\",\n  \"text\": \"Which features do you use?\",\n  \"options\": [...],\n  \"min_selections\": 1,\n  \"max_selections\": 3,\n  \"allow_other\": true\n}\n```\n\n**scale** - A numeric rating (1-5 stars, a 1-5 rating row, an NPS 0-10)\n\nUse this rather than a single_select whenever the answer is a rating: select\nanswers are strings and cannot be averaged or trended.\n\n```json\n{\n  \"type\": \"scale\",\n  \"label\": \"overall\",\n  \"text\": \"Overall, how was your stay?\",\n  \"scale_min\": 1,\n  \"scale_max\": 5,\n  \"point_labels\": [\"Poor\", \"Fair\", \"Good\", \"Very Good\", \"Exceptional\"],\n  \"presentation\": \"stars\",\n  \"allow_skip\": true,\n  \"skip_label\": \"Did Not Use\"\n}\n```\n\n- `scale_min` / `scale_max` — 0 and 10 for NPS, 1 and 5 for rating rows.\n- `min_label` / `max_label` — endpoint anchors (e.g. NPS \"Not at all likely\").\n- `point_labels` — one per point, in order; must have exactly\n  `scale_max - scale_min + 1` entries.\n- `presentation` — renderer hint (`stars`, `row`, `nps`); inferred from the\n  bounds when omitted.\n- `allow_skip` / `skip_label` — the escape option (\"Did Not Use\" / \"Not\n  Applicable\"). `skip_label` without `allow_skip` is rejected: the renderer\n  would show the option and the submit would then be refused.\n\n### Answer Format\n\nAnswers mirror question types with matching `question_type` discriminator:\n\n```json\n{\n  \"answers\": [\n    {\"question_id\": \"...\", \"question_type\": \"checkbox\", \"checked\": true},\n    {\"question_id\": \"...\", \"question_type\": \"single_select\", \"selected_value\": \"excellent\"},\n    {\"question_id\": \"...\", \"question_type\": \"multi_select\", \"selected_values\": [\"a\", \"b\"], \"other_text\": \"custom input\"},\n    {\"question_id\": \"...\", \"question_type\": \"scale\", \"value\": 4, \"skipped\": false},\n    {\"question_id\": \"...\", \"question_type\": \"scale\", \"value\": null, \"skipped\": true}\n  ]\n}\n```\n\n---\n\n## Authentication\n\nAll endpoints except response submission require API key authentication.\n\n**Supported Headers:**\n- `X-API-Key: yasp_xxxxx` (preferred)\n- `Authorization: Bearer yasp_xxxxx`\n\n**Error Responses:**\n- `401 Unauthorized` - Missing or invalid API key\n- `403 Forbidden` - Valid key but no access to resource\n\n---\n\n## Error Handling\n\nAll errors return JSON with a `detail` field:\n\n```json\n{\"detail\": \"Survey not found: 550e8400-e29b-41d4-a716-446655440000\"}\n```\n\n**Common Status Codes:**\n- `400` - Bad request (malformed JSON, etc.)\n- `401` - Authentication required\n- `403` - Not authorized for this resource\n- `404` - Resource not found\n- `409` - Conflict (invalid state transition, duplicate email)\n- `422` - Validation error (invalid data format)\n\n---\n\n## Data Isolation\n\nEach survey stores its data in a separate DuckDB database file for isolation:\n- `data/registry.duckdb` - Users, API keys, survey index\n- `data/surveys/{survey_id}.duckdb` - Survey definition and responses\n\n---\n\n## Rate Limits\n\nNo rate limits are currently enforced. Implement at the reverse proxy level if needed.\n\n---\n\n## Versioning\n\nThe API is versioned via URL prefix (`/api/v1/`). Breaking changes will use a new version.\nThe `survey_version` field in responses tracks schema changes within a survey.\n","x-displayName":"Survey Platform (YASP)"},{"name":"cbtags","description":"\n# CBTAGS\n\nTags-as-a-Service — polymorphic, tenant-partitioned tagging across the CB fleet\n\n## Quick Start\n\n```bash\ncurl https://tags.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"Tags-as-a-Service"},{"name":"cbtapwatch","description":"\n## Overview\n\ncbtapwatch subscribes to the cbloom-web mesh-tap relay, writes every call\nrecord to a rotating NDJSON log, and (in later milestones) produces rolling\nanomaly digests for cboverseer.\n\n## Data path\n\n```\ncbmesh gateway ──UDP:5679──> cbloom-web ──WS /ws/tap──> cbtapwatch\n                                                            │\n                                          rotating NDJSON <─┘\n                                          rolling counters (M2)\n                                          anomaly pass     (M2)\n```\n\n## Milestones\n\n- **M1:** durable log only.\n- **M2 (current):** rolling 1h ring-buffer, suppress-list, err-rate + silence\n  rules, periodic push to cboverseer. Baseline-latency rule deferred to M3.\n","x-displayName":"Mesh Tap Watcher"},{"name":"cbtenant","description":"\n## Overview\n\nAPI for managing multiple CampaignBrain tenant instances. Each tenant is a complete\nCampaignBrain deployment with its own domain, database, and configuration.\n\n## Authentication\n\nAll endpoints (except health checks) require JWT Bearer token authentication.\n\n```\nAuthorization: Bearer <access_token>\n```\n\nObtain a token via `POST /api/auth/login` with username/password.\n\n## Base URL\n\n- **Production:** See OpenAPI servers dropdown above\n- **Development:** See OpenAPI servers dropdown above\n\n## Rate Limits\n\nNo rate limits currently enforced. Be reasonable with polling intervals.\n\n## Error Responses\n\nAll errors return JSON with this structure:\n```json\n{\n    \"error\": \"Error type\",\n    \"detail\": \"Human-readable description\",\n    \"code\": \"MACHINE_READABLE_CODE\",\n    \"timestamp\": \"2025-01-03T12:00:00Z\"\n}\n```\n\n## Common Error Codes\n\n| HTTP | Code | Description |\n|------|------|-------------|\n| 401 | UNAUTHORIZED | Invalid or expired token |\n| 403 | FORBIDDEN | Insufficient permissions |\n| 404 | NOT_FOUND | Resource doesn't exist |\n| 409 | CONFLICT | Resource already exists (e.g., duplicate slug) |\n| 422 | VALIDATION_ERROR | Invalid request parameters |\n| 500 | INTERNAL_ERROR | Server error - check logs |\n","x-displayName":"Tenant Manager"},{"name":"cbtiles","description":"\n# CBTiles\n\n`tiles.campaignbrain.dev` hosts **two separate things**. Read this before\ncalling anything — the interesting one is not served by this API.\n\n---\n\n## 1. The live basemap — NOT this service\n\nThe current Campaign Brain basemap is a self-hosted **pmtiles vector archive**\nplus glyphs, sprites and MapLibre styles, served by **nginx byte ranges**\ndirectly off disk. No process sits in that request path, so nothing here\nproxies it, caches it, or can break it.\n\n| Piece | URL |\n|---|---|\n| Archive | `https://tiles.campaignbrain.dev/pmtiles/us-territories.pmtiles` |\n| Style (dark) | `https://tiles.campaignbrain.dev/pm-assets/styles/loom-dark.json` |\n| Style (light) | `https://tiles.campaignbrain.dev/pm-assets/styles/loom-light.json` |\n| Glyphs / sprites | `https://tiles.campaignbrain.dev/pm-assets/...` |\n\n```javascript\n// MapLibre — the current, supported way to draw a CB map\nnew maplibregl.Map({\n    container: 'map',\n    style: 'https://tiles.campaignbrain.dev/pm-assets/styles/loom-dark.json'\n});\n```\n\nThat archive is re-extracted monthly from the Protomaps daily build. **This\nAPI reports whether that is still happening** — `GET /status` → `basemap` —\nbecause the serving layer has no health surface of its own.\n\n---\n\n## 2. This API — a frozen raster archive\n\nA **read-only** archive of the CartoDB raster tiles cached before August 2026.\n\nCARTO began requiring an API key for basemap fetches in Aug 2026; keyless\nrequests return a PNG with \"API KEY REQUIRED\" burned into the image. Rather\nthan cache watermarked garbage, the fetch path was **deleted**\n(cbtiles#16 step 6, 2026-08-27). What survives is the clean pre-cutoff cache.\n\n- **Hits serve.** Cached tiles return immediately with a 1-hour browser TTL.\n- **Misses 404, permanently.** There is no upstream, no download, no\n  prefetch, and no cache warming. A tile absent today is absent forever.\n- **Coverage is whatever was browsed before the cutoff** — partial, and\n  concentrated in `dark_all`. Do not assume any given tile exists.\n\nExisting raster clients are supported here so they do not break. **New work\nshould use the pmtiles basemap above.**\n\n```bash\n# Only useful for tiles the archive already holds\ncurl https://tiles.campaignbrain.dev/dark_all/10/512/384.png -o tile.png\n```\n\n---\n\n## Tile URL Format\n\n```\nGET /{style}/{z}/{x}/{y}.png\n```\n\n| Parameter | Description | Range |\n|-----------|-------------|-------|\n| `style` | Archived basemap style | See Archived Styles |\n| `z` | Zoom level | 0-20 |\n| `x` | Tile X coordinate | 0 to 2^z - 1 |\n| `y` | Tile Y coordinate | 0 to 2^z - 1 |\n\n## Archived Styles\n\n`dark_all`, `dark_nolabels`, `dark_only_labels`, `light_all`,\n`light_nolabels`, `light_only_labels`, `voyager`, `voyager_nolabels`,\n`voyager_only_labels`.\n\nListing a style means the archive *may* hold tiles for it, not that it does.\n`GET /stats` reports the real per-style counts.\n\n---\n\n## Authentication\n\nNone. Tiles are public, GET-only, and carry no credentials.\n","x-displayName":"Map Tile Cache"},{"name":"cbtor","description":"\n## Overview\n\nTOR Management API provides anonymous web fetching through a pool of Tor-enabled\nOpenWRT workers. Each worker runs Tor connected via WireGuard tunnels, with automatic\nhealth monitoring, circuit rotation, and self-healing recovery.\n\n**Base URL:** `https://tor.campaignbrain.dev`\n\n## Key Capabilities\n\n### Anonymous Web Fetching\nFetch any URL (clearnet or .onion) through the Tor network with automatic\nworker selection and load balancing.\n\n### Worker Pool Management\nMonitor and control 4 Tor workers with health states, connection tracking,\nand manual drain/enable operations.\n\n### Self-Healing Architecture\nTwo-tier health system: host-side API monitors workers every 10 seconds,\nwhile each node runs its own minute-by-minute health checks with automatic\nTor restart and circuit rotation.\n\n## How to Use This API\n\n### Typical Workflow\n\n```\n1. Check pool health: GET /api/v1/health\n2. Fetch URL anonymously: POST /api/v1/fetch {\"url\": \"https://example.com\"}\n3. Response includes exit IP, timing, and worker used\n```\n\n### Routing Logic\n\n| URL Type | Routing Method | Port |\n|----------|----------------|------|\n| Clearnet (http/https) | TransPort via WireGuard | 9040 |\n| .onion | SOCKS5 proxy | 9050 |\n\n### Load Balancing Modes\n\n| Mode | Description | Use Case |\n|------|-------------|----------|\n| `round_robin` | Rotate through healthy workers | Default, even distribution |\n| `sticky` | Consistent worker per session key | Maintain same exit IP |\n| `random` | Random healthy worker | Simple distribution |\n| `least_connections` | Fewest active requests | High-throughput scenarios |\n\n## Worker Health States\n\n```\nHEALTHY → SUSPECT → DEAD → RECOVERY → HEALTHY\n    ↑                           │\n    └───────────────────────────┘\n```\n\n| State | Description | Receives Traffic |\n|-------|-------------|------------------|\n| `healthy` | All checks passing | Yes |\n| `suspect` | 3+ consecutive failures | Yes (degraded) |\n| `dead` | 6+ failures, entering recovery | No |\n| `recovery` | Active recovery in progress | No |\n\n## Timeout & Recovery Cycles\n\n### Host-Side Health Manager (API Server)\n\nThe API server runs continuous health monitoring:\n\n| Parameter | Value | Description |\n|-----------|-------|-------------|\n| `health_check_interval` | 10 sec | Check all workers |\n| `failure_threshold` | 3 | Failures before SUSPECT |\n| `success_threshold` | 2 | Successes to recover |\n| `recovery_backoff` | 1, 2, 4, 8, 16, 32 sec | Exponential retry delays |\n| `reboot_after_attempts` | 6 | Attempts before forcing reboot |\n\n**Recovery Sequence:**\n1. Check SSH reachable\n2. Check SOCKS port (9050) responding\n3. Restart Tor if needed\n4. Reconnect WireGuard tunnel\n5. Verify Tor circuit via check endpoints\n\n### Node-Side Self-Healing (Each Worker)\n\nEach Tor worker runs its own health check every minute via cron:\n\n| Parameter | Value | Description |\n|-----------|-------|-------------|\n| `TEST_TIMEOUT` | 30 sec | .onion page load timeout |\n| `NEWNYM_WAIT` | 5 sec | Wait after circuit rotation |\n| `RESTART_WAIT` | 30 sec | Wait after Tor restart |\n| `RECOVERY_WAIT` | 15 min | Offline before retry |\n\n**Node Recovery Sequence:**\n```\n1. Test random .onion site via SOCKS\n2. If fail → NEWNYM (new circuit), retry\n3. If still fail → Restart Tor, retry\n4. If still fail → Mark OFFLINE, stop Tor\n5. After 15 min → Attempt recovery\n```\n\n**Test Endpoints (.onion sites):**\n\n| Site | URL |\n|------|-----|\n| DuckDuckGo | `duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion` |\n| ProPublica | `p53lf57qovyuvwsc6xnrppyply3vtqm7l6pcobkmyqsiofyeznfu5uqd.onion` |\n| BBC News | `bbcnewsd73hkzno2ini43t4gblxvycyac5aw4gnv7t2rccijh7745uqd.onion` |\n| ProtonMail | `protonmailrmez3lotccipshtkleegetolb73fuirgj7r4o4vfu7ozyd.onion` |\n| Riseup | `vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion` |\n| Archive.org | `archiveiya74codqgiixo33q62qlrqtkgmcitqx5u2oeqnmn5bpcbiyd.onion` |\n\n## Rate Limits\n\n- No explicit rate limits on fetch requests\n- Workers handle ~10-50 concurrent connections each\n- .onion requests are slower (10-30 sec typical)\n- Clearnet via Tor: 1-5 sec typical\n\n## Error Handling\n\n| HTTP Code | Meaning | Action |\n|-----------|---------|--------|\n| 200 | Success | Response contains body |\n| 400 | Invalid request | Check parameters |\n| 404 | Worker not found | Check worker ID |\n| 422 | Validation error | Check request schema |\n| 500 | Worker/Tor error | Check worker health |\n| 503 | No healthy workers | Wait for recovery |\n\n## Data Sources\n\n- **4 Tor Workers**: OpenWRT routers running Tor + WireGuard\n- **TOR Check Endpoints**: torproject.org, ipinfo.io, icanhazip.com, ipify.org\n- **Circuit Rotation**: Automatic every ~10 min, on-demand via NEWNYM\n","x-displayName":"TOR Cluster"},{"name":"cbunifi","description":"\n# CBUNIFI\n\nUniFi network and Protect observability plus captive-portal control-plane for VI properties\n\n## Quick Start\n\n```bash\ncurl https://unifi.campaignbrain.dev/health\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/health` | Health check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n","x-displayName":"UniFi API"},{"name":"cbuserguide","description":"\n# cbuserguide - Automated User Guide Generator\n\n**Automated documentation generation for Campaign Brain applications** using\nPlaywright-based crawling, screenshot capture, and markdown generation.\n\n---\n\n## Overview\n\ncbuserguide automates the creation of user documentation by:\n\n- **Crawling web apps**: Uses Playwright to navigate and authenticate\n- **Capturing screenshots**: Full-page captures of each documented screen\n- **Tracking UI components**: Identifies buttons, forms, and interactive elements\n- **Managing versions**: Track documentation across app releases\n\n---\n\n## Quick Start\n\n### 1. Register an app\n\n```bash\ncurl -X POST https://userguide.campaignbrain.dev/api/apps \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"testsite\", \"base_url\": \"https://testsite.campaignbrain.dev\"}'\n```\n\n### 2. Create a version\n\n```bash\ncurl -X POST https://userguide.campaignbrain.dev/api/apps/1/versions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"version\": \"1.0.0\"}'\n```\n\n### 3. Create and generate a module\n\n```bash\n# Create module\ncurl -X POST https://userguide.campaignbrain.dev/api/versions/1/modules \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"admin-users\", \"paths\": [\"/users\", \"/users/new\"]}'\n\n# Start generation\ncurl -X POST https://userguide.campaignbrain.dev/api/modules/1/generate\n```\n\n---\n\n## Authentication\n\nThis API is intended for internal use and does not require authentication.\nApps being crawled use their own credentials stored in the app configuration.\n\n---\n\n## Workflow\n\n1. **Register App** → Configure target app with URL and credentials\n2. **Create Version** → Track documentation for a specific release\n3. **Create Module** → Define a section with paths to document\n4. **Generate** → Run Playwright crawler to capture screenshots\n5. **Validate** → Review captured artifacts\n6. **Publish** → Make documentation available to users\n","x-displayName":"User Guide Generator"},{"name":"cbvoice","description":"\n# CBVOICE\n\n**Campaign Brain Voice Transport** — the iOS Shortcuts + Siri voice layer for the\nCampaign Brain fleet. cbvoice is a thin, mesh-resident edge service that lets a\nphone (via a Siri Shortcut) speak to the platform and get a short, *spoken-ready*\nsentence back. It does no heavy computation of its own: each voice intent fans\nout over the ZVR mesh to the service that actually owns the data (cbmodels for\nvoter stats, cbauth for identity) and folds the result into a single `spoken`\nstring the Shortcut can read aloud.\n\n## How a voice request flows\n\n```\niPhone (Siri Shortcut)\n   │  HTTPS  (optional Bearer token)\n   ▼\nvoice.campaignbrain.dev  →  cbvoice\n   │  mesh ServiceClient\n   ├─► cbauth   /api/v1/users/me      (who is asking? — optional)\n   └─► cbmodels /api/v1/voters/stats  (the data being spoken)\n```\n\nThe response shape every voice endpoint returns is intentionally uniform:\n\n| Field    | Purpose                                                              |\n|----------|----------------------------------------------------------------------|\n| `spoken` | A ready-to-speak sentence the Shortcut reads aloud verbatim.         |\n| `raw`    | The structured upstream data behind the sentence (for debugging/UI). |\n\n## Authentication\n\nVoice endpoints are **auth-optional** and degrade gracefully. Pass the caller's\nbearer in the `Authorization` header to be greeted by name; omit it and you are\ngreeted as *\"stranger\"* — an invalid or missing token never fails the request.\n\n## Quick Start\n\n```bash\ncurl https://voice.campaignbrain.dev/health\ncurl https://voice.campaignbrain.dev/api/v1/voice/ping\n```\n\n## Endpoints\n\n| Path                 | Description                                      |\n|----------------------|--------------------------------------------------|\n| `/api/v1/hello`      | Liveness greeting — no upstream calls            |\n| `/api/v1/voice/ping` | Connectivity smoke test across the cbvoice→mesh hop |\n| `/health`            | Health check (provided by CBApp)                 |\n| `/health/detailed`   | Detailed health with component status            |\n| `/status`            | Service status, features, and routes             |\n| `/prime`             | Warm/resolve service + OpenAPI spec              |\n| `/docs`              | Swagger UI                                        |\n| `/redoc`             | ReDoc                                             |\n","x-displayName":"Voice Transport"},{"name":"cbvpn","description":"\n## Overview\n\nThe **cbintel Network API** manages a distributed VPN cluster for geographic routing.\nIt controls OpenWRT-based workers that provide geographically-targeted exit points\nfor web scraping and intelligence gathering operations.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        Network API                              │\n│                    (network.campaignbrain.dev)                        │\n└──────────────────────────┬──────────────────────────────────────┘\n                           │\n            ┌──────────────┼──────────────────┐\n            │              │                  │\n            ▼              ▼                  ▼\n       ┌─────────┐    ┌─────────┐       ┌─────────┐\n       │ Worker 1│    │ Worker 2│  ...  │Worker 15│\n       │   wg1   │    │   wg2   │       │  wg15   │\n       │  US:CA  │    │   DE    │       │   JP    │\n       └─────────┘    └─────────┘       └─────────┘\n```\n\n## Key Concepts\n\n### Banks\nIsolated pools of VPN workers grouped by geographic filter:\n- **Filter format**: `country[:state][:type]` (e.g., \"us\", \"us:ca\", \"us::p2p\")\n- **Type** is a Proton server feature (`p2p`, `tor`, `streaming`,\n  `secure_core`, `ipv6`), resolved against the harvested inventory — not a\n  substring of a profile filename. It is always the third field, so a\n  nationwide feature filter needs the empty state slot: `us::p2p`.\n- **Allocation**: Workers assigned to banks for exclusive use\n- **Routing**: Use a bank's `endpoint` — a per-bank HAProxy frontend that\n  balances across the bank's workers. It is null unless the port is verified\n  to be listening, because reserved is not served; `worker_endpoints` is the\n  per-worker fallback and the way to pin one exit.\n\n### Workers\nOpenWRT devices running WireGuard VPN tunnels:\n- **Devices**: wg1 through wg15 (WireGuard interfaces)\n- **Proxies**: a tinyproxy per worker on port 3129 — this is the path that\n  carries traffic today\n- **Exit IPs**: Geographic exit points via VPN providers\n\n### Cluster Operations\nManage the overall cluster state:\n- **Statistics**: Worker counts, bank allocation\n- **Deployment**: Push configurations to workers\n- **Health**: Monitor tunnel and proxy status\n\n## How to Use This API\n\n### For Intelligence API Integration\n\nThe [Intelligence API](https://intel.campaignbrain.dev/docs) uses this Network API\nautomatically when jobs include a `geo` parameter:\n\n```json\nPOST intel.campaignbrain.dev/api/v1/jobs/crawl\n{\"query\": \"...\", \"geo\": \"us:ca\"}\n```\n\nInternally, the Intelligence API:\n1. Calls `/api/v1/banks` to find or create a bank for \"us:ca\"\n2. Allocates a worker from the bank\n3. Routes job traffic through that worker's VPN tunnel\n4. Releases the worker when the job completes\n\n### Direct API Usage\n\nFor custom VPN management:\n\n```python\n# Create a bank for California traffic\nPOST /api/v1/banks\n{\"geo_filter\": \"us:ca\", \"bank_size\": 2}\n\n# List workers in the bank\nGET /api/v1/banks/{bank_id}/workers\n\n# Get bank statistics\nGET /api/v1/banks/{bank_id}/stats\n```\n\n## Geographic Filters\n\n| Filter | Description | Example Exit Locations |\n|--------|-------------|----------------------|\n| `us` | United States (any) | New York, California, Texas |\n| `us:ca` | California | Los Angeles, San Francisco |\n| `us:ny` | New York | New York City, Buffalo |\n| `de` | Germany | Frankfurt, Berlin |\n| `gb` | United Kingdom | London, Manchester |\n| `jp` | Japan | Tokyo, Osaka |\n\n## Worker Lifecycle\n\n```\nIDLE → ALLOCATED → ACTIVE → IDLE\n        ↓\n     FAILED (if tunnel/proxy fails)\n```\n\n## Device Communication\n\n- **LuCI RPC**: SSH-based command execution on OpenWRT\n- **HAProxy Stats**: Real-time proxy metrics via stats socket\n- **Endpoint probes**: bank ports are TCP-probed before being advertised, so a\n  reported endpoint is one that answered\n- **WireGuard**: Tunnel status via `wg show` command\n\n## Rate Limits\n\n- **Bank operations**: 10/minute (create, delete)\n- **Worker queries**: 100/minute\n- **Deployment**: 1/minute (affects all workers)\n","x-displayName":"Network Inventory"},{"name":"cbweave","description":"\n# CBWEAVE\n\nDeterministic **markdown → SVG** compiler. Authors write enhanced markdown\n(frontmatter + `::name{attrs}` directives + fenced ` ```data ` blocks); the\nengine compiles to a single `<svg>` blob. *Same input → same bytes.*\n\n## Authoring quick-reference\n\n```markdown\n---\ntitle: \"Q3 Pipeline\"\ncanvas: { width: 400, height: 280 }\n---\n\n:::grid{cols=2 gap=s3}\n::tile{label=\"THROUGHPUT\" value=\"847\" unit=\"/sec\" delta=\"+12% ▲\" delta-mode=positive}\n::tile{label=\"LATENCY · P95\" value=\"8.4\" unit=\"ms\" delta=\"-0.3 ▼\" delta-mode=positive}\n:::\n```\n\n- **Leaf directives**: `::name{attrs}` or `::name[label]{attrs}` (both `[label]` and `{attrs}` independently optional)\n- **Container directives**: `:::name{attrs}…:::`, nestable\n- **Layout primitives**: `:::vstack`, `:::hstack`, `:::grid`\n- **Component leaves**: `tile`, `kicker`, `value`, `delta`, `stubs`, `pill`, `live-indicator`, `meter`, `action-button`, `elbow`\n- **Data-consumer containers**: `:::sparkline{...}` / `:::map{...}` with a fenced ` ```data ` JSON block child\n- **Geographic borders**: `::map{geoid=\"0601\"}` or `::map{state=\"19\" tolerance=0.01}` renders district/state boundaries as SVG `<path>`s — geometry fetched from cbdistricts over the mesh (cbweave#13)\n- **Design tokens**: spacing (`s1`-`s8`), tones (`act`, `analyze`, `monitor`, `system`, `alert`), `bone` neutrals\n\nFull DSL in [`docs/lcars-svg/lcars-svg/DIRECTIVE-SYNTAX.md`](https://github.com/Nominate-AI/cbweave/blob/main/docs/lcars-svg/lcars-svg/DIRECTIVE-SYNTAX.md).\nComponent specs in [`COMPONENTS.md`](https://github.com/Nominate-AI/cbweave/blob/main/docs/lcars-svg/lcars-svg/COMPONENTS.md).\nDesign tokens in [`DESIGN-SYSTEM.md`](https://github.com/Nominate-AI/cbweave/blob/main/docs/lcars-svg/lcars-svg/DESIGN-SYSTEM.md).\n\n## Core endpoints\n\n| Method | Path | Purpose |\n|--------|------|---------|\n| `POST` | `/api/v1/render` | Compile arbitrary markdown to SVG (with `fonts={reference,embed,outline}`) |\n| `GET`  | `/api/v1/examples` | List bundled example slugs |\n| `GET`  | `/api/v1/examples/{slug}` | Render a bundled example (great for copy-paste starting points) |\n\n## Font portability modes (`fonts=` on `/render`)\n\n| Mode | `<text>` | `<path>` glyphs | Size | When to use |\n|------|----------|-----------------|------|-------------|\n| `reference` | yes | — | small | dev / hosts with the fonts installed |\n| `embed`     | yes | — | large (~1 MB) | chat embeds where the browser must self-host |\n| `outline`   | — | yes | medium | print, PNG conversion, mesh transport — most portable |\n\n## Determinism guarantees\n\n- No timestamps, random IDs, or non-deterministic ordering in the output.\n- Two `POST /render` calls with the same body return byte-identical SVG.\n- Cacheable by `sha256(markdown || fonts)`.\n\n## Authentication\n\nPublic traffic is gated by sysforge's rubric middleware. Authenticated\nclients receive `<text>` SVG responses directly; unauthenticated public\nrequests get `302 → /auth/gate/`.\n","x-displayName":"SVG Visualization Engine"},{"name":"cbwebhook","description":"\n# CBWebhook - Webhook Ingestion Service\n\n**Centralized webhook ingestion and routing for Campaign Brain**\n\n---\n\n## Overview\n\nCBWebhook provides:\n\n- **Webhook Reception**: Accept webhooks from external services (GitHub, Stripe, etc.)\n- **Payload Validation**: Verify signatures and validate payload formats\n- **Event Routing**: Route events to appropriate internal services\n\n---\n\n## Quick Start\n\n### Receive a Webhook\n\n```bash\ncurl -X POST https://webhook.campaignbrain.dev/api/v1/hooks/github \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-Hub-Signature-256: sha256=...\" \\\n  -d '{\"action\": \"opened\", \"issue\": {...}}'\n```\n\n---\n\n## Supported Integrations\n\n| Provider | Endpoint | Status |\n|----------|----------|--------|\n| Typeform | `/api/v1/hooks/typeform` | Active |\n| BoldSign | `/api/v1/hooks/boldsign` | Active |\n| GDrive (Pub/Sub) | `/api/v1/hooks/gdrive` | Active |\n| Iterable (clicks) | `/api/v1/hooks/iterable/{project}` | Active |\n| GitHub | `/api/v1/hooks/github` | Planned |\n| Stripe | `/api/v1/hooks/stripe` | Planned |\n| Twilio | `/api/v1/hooks/twilio` | Planned |\n","x-displayName":"Webhook Router"},{"name":"cbworkflow","description":"\n# CBWorkflow API\n\nLightweight JSON-backed workflow engine for CRM contact management.\nManages contacts, workflow templates, and workflow instances with full\nstate tracking and event timeline.\n\n---\n\n## Quick Start\n\n### 1. Create a contact\n```bash\ncurl -X POST https://workflow.campaignbrain.dev/api/contacts \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"Jane Smith\", \"email\": \"jane@example.com\", \"tags\": [\"volunteer\"]}'\n```\n\n### 2. Create a workflow instance\n```bash\ncurl -X POST https://workflow.campaignbrain.dev/api/instances \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"contact_id\": \"CONTACT_ID\", \"template_id\": \"TEMPLATE_ID\"}'\n```\n\n### 3. Start the workflow\n```bash\ncurl -X POST https://workflow.campaignbrain.dev/api/instances/INSTANCE_ID/start\n```\n\n---\n\n## Multi-Tenant Usage\n\nFor multi-tenant deployments, use tenant-scoped endpoints:\n- `/api/tenants/{tenant_id}/contacts`\n- `/api/tenants/{tenant_id}/workflows`\n- `/api/tenants/{tenant_id}/instances`\n\n---\n\n## Key Concepts\n\n- **Contact**: A person being managed through workflows\n- **WorkflowTemplate**: A reusable blueprint defining process steps\n- **WorkflowInstance**: A live execution of a template for a contact\n- **Node**: A step in a workflow (action, decision, wait, end)\n- **Event**: An audit trail entry in the instance timeline\n","x-displayName":"Workflow Engine"},{"name":"cbworkspaces","description":"\n# CBWORKSPACES\n\nWorkspaces — multi-tenant workspace management API for the Campaign Brain\nfleet.\n\nCBWORKSPACES is the home for **workspace** resources: the per-tenant\ncontainers that group related documents, datasets, and processing artifacts\nunder a single isolated namespace. Other fleet services (cbindex, cbmodels,\ncbloom) attach their outputs to a workspace so that everything produced for a\ncampaign or tenant lives behind one stable identifier.\n\n> **Status:** This service is in early scaffold. The surface below is the\n> framework-provided baseline (health, status, prime) plus a connectivity\n> probe. Workspace CRUD endpoints are forthcoming; the documentation contract\n> here is the standard every future route will follow.\n\n## Quick Start\n\n```bash\n# Liveness probe — no auth required\ncurl https://workspaces.campaignbrain.dev/health\n\n# Connectivity / greeting probe\ncurl https://workspaces.campaignbrain.dev/api/v1/hello\n```\n\n## Endpoints\n\n| Path | Description |\n|------|-------------|\n| `/api/v1/hello` | Connectivity / greeting probe |\n| `/health` | Liveness check |\n| `/health/detailed` | Detailed health with component status |\n| `/status` | Service status, features, and routes |\n| `/prime` | Warm cold-start resources and resolve OpenAPI spec |\n| `/docs` | Swagger UI |\n| `/redoc` | ReDoc |\n\n## Conventions\n\n- **Mesh access.** Reach this service via the ZVR mesh\n  (`sysforge.core.client.ServiceClient`) under the mesh name `cbworkspaces`,\n  not a public URL or direct port.\n- **Versioned routes.** Application routes live under the `/api/v1` prefix;\n  the unversioned `/health`, `/status`, and `/prime` are framework-managed\n  monitoring endpoints.\n","x-displayName":"Workspaces"}],"x-tagGroups":[{"name":"Uncategorized","tags":["cbcaptive","cbconvo","cbenrichment","cbharvest","cbkrnl","cbnominate","cbpersonnel","cbprint","cbradio2","cbship","cbsme"]},{"name":"ai","tags":["cbai","cbea","cbindex","cbintel","cbkb","cbmanim","cbscout","cbstt"]},{"name":"business","tags":["cbapp","cbbanking","cbbilling","cbcampaign","cbcast","cbcrm","cbesig","cbgroundgame","cbissues","cbloom","cbmarketing","cbmobile","cbonboard","cbpayments","cbpublish","cbpulse","cbquickbooks","cbradio","cbradio-qa","cbsquare","cbworkflow","cbworkspaces"]},{"name":"communication","tags":["cbcalendar","cbemail","cbfront","cbinbox","cblinks","cbnotify","cbsms","cbsnailmail","cbsocial","cbvoice","cbwebhook"]},{"name":"data","tags":["cbairtable","cbdatabase","cbdistricts","cbelections","cbetl","cbfiles","cbgeo","cbingest","cbiterable","cblist","cbmaps","cbmodels","cbsearch","cbsurveys","cbtags","cbtiles","cbuserguide"]},{"name":"infrastructure","tags":["cbauth","cbcron","cbforge","cbgreenroom","cbhive","cbinfra","cblogs","cbnetwork","cboverseer","cbrouter","cbstitch","cbtapwatch","cbtenant","cbtor","cbunifi","cbvpn","cbweave"]}]}