{"openapi":"3.1.0","info":{"title":"Scoutpost API","version":"2.1.0","description":"Scoutpost Edge Functions — the public HTTP surface used by the web UI, the `scout` CLI, the `scout-mcp` stdio bridge, and third-party agents. All endpoints live under `${SUPABASE_URL}/functions/v1/`. Authentication: `Authorization: Bearer <token>` where the token is either a Supabase user JWT (browser sessions) or a `cj_…` API key (agents, CI). When the host is a Supabase project, Kong additionally requires the `apikey: <anon_key>` header.","contact":{"name":"Scoutpost","url":"https://scoutpost.ai"}},"servers":[{"url":"https://scoutpost.ai/functions/v1","description":"Production"},{"url":"/functions/v1","description":"Relative (when proxied by the app)"}],"security":[{"bearerAuth":[],"anonKey":[]}],"tags":[{"name":"scouts","description":"Scout CRUD + lifecycle (list/create/update/run/pause/resume/delete)"},{"name":"units","description":"Information units — the editorial inbox + verification + semantic search"},{"name":"projects","description":"Investigation projects (a workspace for grouping scouts + units)"},{"name":"entities","description":"Canonical entity store + merge"},{"name":"reflections","description":"Durable editorial notes tied to units/scouts/entities"},{"name":"knowledge","description":"Ingest content into information units"},{"name":"user","description":"Authed user profile + preferences"},{"name":"agents","description":"`cj_…` API keys (create, list, revoke) + MCP server"},{"name":"meta","description":"Spec + health"}],"paths":{"/scouts":{"get":{"tags":["scouts"],"summary":"List scouts owned by the caller","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"type","in":"query","schema":{"$ref":"#/components/schemas/ScoutType"}}],"responses":{"200":{"description":"Paginated scouts","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Paginated"},{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Scout"}}}}]}}}}}},"post":{"tags":["scouts"],"summary":"Create a scout. Scheduled creation establishes the initial baseline; Run Now never bootstraps a missing baseline.","description":"Before insert the server re-runs step 1 of the probe: `type:web` scrapes `url` once (reach); `type:civic` validates that every `tracked_urls` entry exposes meetings (detect) unless a `preview_snapshot_token` from POST /civic/test is supplied. A failed gate answers 422 with the ProbeEnvelope and the scout is not created — run POST /scouts/test or POST /civic/discover + /civic/test first so the failure is seen before create. Other scout types are not gated.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScoutCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scout"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"402":{"$ref":"#/components/responses/OutOfCredits"},"422":{"$ref":"#/components/responses/ProbeGateFailure"}}}},"/transport-test":{"post":{"tags":["scouts"],"summary":"Test a Fleet Scout configuration against live data and prepare a silent baseline","description":"Non-credit Step 1 for Fleet creation. Hosted Scoutpost requires Pro/Team. Pass the returned baseline_ids as transport_baseline_ids when creating the scout; an explicit empty array is a valid tested quiet baseline.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["config"],"properties":{"config":{"$ref":"#/components/schemas/TransportConfig"}}}}}},"responses":{"200":{"description":"Live check completed","content":{"application/json":{"schema":{"type":"object","required":["valid","baseline_ids","preview"],"properties":{"valid":{"type":"boolean","const":true},"baseline_ids":{"type":"array","maxItems":500,"items":{"type":"string"}},"preview":{"type":"array","maxItems":20,"items":{"type":"object","required":["id","label"],"properties":{"id":{"type":"string"},"label":{"type":"string"}}}}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"403":{"description":"Fleet Scout is unavailable for the caller's hosted tier"},"503":{"description":"Required live transport data is unavailable or stale"}}}},"/scouts/from-template":{"post":{"tags":["scouts"],"summary":"Create a scout from a named template","description":"Creates an inactive scout. Read GET /scout-templates for each template's required fields; the merged defaults and supplied fields must satisfy ScoutCreate, including type-specific requirements. Supply profile_handle for the Instagram template, not a profile URL in url.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["template_slug","name"],"properties":{"template_slug":{"type":"string"},"name":{"type":"string","minLength":1,"pattern":"\\S","maxLength":200},"fields":{"type":"object","description":"Template inputs. Required keys are listed in the selected template's fields metadata; defaults supply its Project tags and schedule.","additionalProperties":true},"project_id":{"type":"string","format":"uuid","nullable":true}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scout"}}}}}}},"/scouts/test":{"post":{"tags":["scouts"],"summary":"Probe a Page (web) scout URL without creating it (establishes no baseline)","description":"Step 1 (reach) for Page scouts. One fresh scrape plus an optional criteria check. `scraper_status:false` maps to `unreachable`, `blocked`, `empty_content`, `outside_configured_page` or `page_too_long` — the same codes POST /scouts returns as 422 for the same URL. Pages above 150,000 characters of normalized extracted Markdown are rejected; choose a shorter or more specific page. `criteria_status:false` with criteria supplied maps to advisory `criteria_not_met` while `ok` stays true.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","maxLength":2000},"criteria":{"type":"string","maxLength":4000},"scraperName":{"type":"string","maxLength":200}}}}}},"responses":{"200":{"description":"Probe completed (inspect `ok`)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ProbeEnvelope"},{"type":"object","required":["summary","scraper_status","criteria_status"],"properties":{"summary":{"type":"string"},"scraper_status":{"type":"boolean"},"criteria_status":{"type":"boolean"}}}]}}}},"400":{"$ref":"#/components/responses/ValidationError"}}}},"/civic/discover":{"post":{"tags":["scouts"],"summary":"Step 1 (detect) for Civic scouts: discover meeting listings for a council domain, or validate chosen tracked URLs","description":"Discovery mode (`root_domain`): maps the site, fingerprints the committee system and returns only listing pages behind which at least one meeting document is visible, best first with `recommended:true`. When nothing qualifies the response is the `no_meetings_detected` outcome with empty `candidates` and up to five ranked `unverified` pages for manual inspection. Reach failures (`unreachable`, `blocked`, `empty_content`) are returned with HTTP 200 and `ok:false`. Validation mode (`tracked_urls`): applies the same check POST /scouts uses at create; `validated` lists URLs that expose meetings, `invalid` those that do not, and `candidates` lists replacements found nearby. Exactly one of the two fields is required.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"root_domain":{"type":"string","minLength":3},"tracked_urls":{"type":"array","minItems":1,"maxItems":2,"items":{"type":"string","format":"uri"}}}}}}},"responses":{"200":{"description":"Probe completed (inspect `ok` and `error_code`)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ProbeEnvelope"},{"type":"object","required":["system","candidates"],"properties":{"system":{"$ref":"#/components/schemas/CivicSystem"},"candidates":{"type":"array","maxItems":5,"items":{"$ref":"#/components/schemas/CivicListingCandidate"}},"unverified":{"type":"array","maxItems":5,"description":"Discovery mode only, on `no_meetings_detected`: ranked pages with no visible documents; never offered as tracked URLs.","items":{"type":"object","required":["url","description","confidence"],"properties":{"url":{"type":"string","format":"uri"},"description":{"type":"string"},"confidence":{"type":"number"}}}},"validated":{"type":"array","description":"Validation mode only.","items":{"type":"string","format":"uri"}},"invalid":{"type":"array","description":"Validation mode only.","items":{"type":"string","format":"uri"}}}}]}}}},"400":{"$ref":"#/components/responses/ValidationError"}}}},"/civic/test":{"post":{"tags":["scouts"],"summary":"Step 2 (sample) for Civic scouts: resolve a bounded document sample from chosen listings and preview extraction","description":"Read-only. Resolves meeting documents from each listing (multi-hop for fingerprinted systems), parses at most two, runs one extraction pass and returns `sample_items`. When at least one document was extracted the response includes an opaque `preview_snapshot_token`; pass it to POST /scouts to import the previewed items and skip the create gate. `ok:false` codes: `no_documents` (nothing resolved), `unreachable` (a linked meeting could not be fetched), `parse_failed`, `model_failed`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tracked_urls"],"properties":{"tracked_urls":{"type":"array","minItems":1,"maxItems":2,"items":{"type":"string","format":"uri"}},"criteria":{"type":"string","maxLength":4000}}}}}},"responses":{"200":{"description":"Sample completed (inspect `ok`)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ProbeEnvelope"},{"type":"object","required":["api_version","valid","documents_found","sample_items"],"properties":{"api_version":{"type":"string","const":"2"},"valid":{"type":"boolean","description":"True when documents_found > 0."},"documents_found":{"type":"integer"},"documents_resolved":{"type":"integer"},"documents_evaluated":{"type":"integer"},"source_results":{"type":"array","items":{"type":"object","additionalProperties":true}},"policy_version":{"type":"string"},"preview_snapshot_token":{"type":"string","nullable":true},"sample_items":{"type":"array","items":{"type":"object","additionalProperties":true}},"sample_promises":{"type":"array","description":"Legacy promise-only projection; new consumers use sample_items.","items":{"type":"object","additionalProperties":true}}}}]}}}},"400":{"$ref":"#/components/responses/ValidationError"}}}},"/scouts/{id}":{"parameters":[{"$ref":"#/components/parameters/IdPath"}],"get":{"tags":["scouts"],"summary":"Fetch a scout","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scout"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["scouts"],"summary":"Update a scout (any subset of name/criteria/schedule/is_active)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScoutUpdate"}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scout"}}}}}},"delete":{"tags":["scouts"],"summary":"Delete a scout and unschedule its cron job","responses":{"204":{"description":"deleted"}}}},"/scouts/{id}/run":{"parameters":[{"$ref":"#/components/parameters/IdPath"}],"post":{"tags":["scouts"],"summary":"Trigger an on-demand scout run (spends credits)","responses":{"202":{"description":"Accepted — run queued","content":{"application/json":{"schema":{"type":"object","properties":{"run_id":{"type":"string","format":"uuid"}}}}}},"402":{"$ref":"#/components/responses/OutOfCredits"}}}},"/scouts/{id}/pause":{"parameters":[{"$ref":"#/components/parameters/IdPath"}],"post":{"tags":["scouts"],"summary":"Set is_active=false and unschedule the cron job","responses":{"200":{"description":"paused"}}}},"/scouts/{id}/resume":{"parameters":[{"$ref":"#/components/parameters/IdPath"}],"post":{"tags":["scouts"],"summary":"Set is_active=true and re-schedule the cron job","responses":{"200":{"description":"resumed"}}}},"/units":{"get":{"tags":["units"],"summary":"List information units with scope and lifecycle-state filters.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"project_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"scout_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"verified","in":"query","schema":{"type":"boolean"},"description":"Filter by verified state. `false` = needs-review inbox."},{"name":"used_in_article","in":"query","schema":{"type":"boolean"},"description":"Filter by published state. `false` = not yet used."},{"name":"include_deleted","in":"query","schema":{"type":"boolean"},"description":"Include soft-deleted units. Deleted units are excluded by default."},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Lower bound for `last_seen_at` / surfaced `extracted_at`."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Upper bound for `last_seen_at` / surfaced `extracted_at`."}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Paginated"},{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Unit"}}}}]}}}}}}},"/units/locations":{"get":{"tags":["units"],"summary":"Legacy compatibility: list location keys represented in the caller's units.","deprecated":true,"description":"Compatibility route used by older feed/workspace clients. Prefer GET /units with location filters for new integrations.","responses":{"200":{"description":"location keys"}}}},"/units/topics":{"get":{"tags":["units"],"summary":"Legacy compatibility: list topics represented in the caller's units.","deprecated":true,"description":"Compatibility route used by older feed/workspace clients. Prefer GET /units with topic/scout/project filters for new integrations.","responses":{"200":{"description":"topics"}}}},"/units/all":{"get":{"tags":["units"],"summary":"Legacy compatibility: list unused units.","deprecated":true,"parameters":[{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"legacy units envelope"}}}},"/units/unused":{"get":{"tags":["units"],"summary":"Legacy compatibility: list unused units for a required country/location scope.","deprecated":true,"parameters":[{"$ref":"#/components/parameters/Limit"},{"name":"country","in":"query","required":true,"schema":{"type":"string"}},{"name":"state","in":"query","schema":{"type":"string"}},{"name":"city","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"legacy units envelope"}}}},"/units/by-topic":{"get":{"tags":["units"],"summary":"Legacy compatibility: list unused units by topic.","deprecated":true,"parameters":[{"$ref":"#/components/parameters/Limit"},{"name":"topic","in":"query","required":true,"schema":{"type":"string"}},{"name":"country","in":"query","schema":{"type":"string"}},{"name":"state","in":"query","schema":{"type":"string"}},{"name":"city","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"legacy units envelope"}}}},"/units/search":{"get":{"tags":["units"],"summary":"Legacy compatibility: search units with query-string parameters.","deprecated":true,"parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string","minLength":2}},{"$ref":"#/components/parameters/Limit"},{"name":"topic","in":"query","schema":{"type":"string"}},{"name":"country","in":"query","schema":{"type":"string"}},{"name":"state","in":"query","schema":{"type":"string"}},{"name":"city","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"legacy units envelope"}}},"post":{"tags":["units"],"summary":"Search over the caller's units using semantic, keyword, or hybrid mode.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query_text"],"properties":{"query_text":{"type":"string","minLength":1,"maxLength":4000},"mode":{"type":"string","enum":["semantic","keyword","hybrid"],"default":"hybrid"},"project_id":{"type":"string","format":"uuid"},"scout_id":{"type":"string","format":"uuid"},"verified":{"type":"boolean"},"used_in_article":{"type":"boolean"},"include_deleted":{"type":"boolean"},"limit":{"type":"integer","minimum":1,"maximum":100}}}}}},"responses":{"200":{"description":"ranked units","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Unit"}}}}}}}}}},"/units/mark-used":{"patch":{"tags":["units"],"summary":"Legacy compatibility: mark units used from Dynamo-style unit keys.","deprecated":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["unit_keys"],"properties":{"unit_keys":{"type":"array","minItems":1,"maxItems":200,"items":{"type":"object","required":["pk","sk"],"properties":{"pk":{"type":"string"},"sk":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"marked count"}}}},"/units/{id}":{"parameters":[{"$ref":"#/components/parameters/IdPath"}],"get":{"tags":["units"],"summary":"Fetch a unit","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Unit"}}}}}},"patch":{"tags":["units"],"summary":"Verify / reject / mark-used. All fields optional; only sent keys change.","description":"Conventions:\n- Verify: `{ verified: true, verified_by, verification_notes }`\n- Reject: `{ verified: false, verification_notes: \"<reason>\" }`\n- Mark used: `{ used_in_article: true, used_in_url, used_at }`","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnitUpdate"}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Unit"}}}}}},"delete":{"tags":["units"],"summary":"Soft-delete a unit","responses":{"204":{"description":"deleted"}}}},"/units/{id}/evidence":{"parameters":[{"$ref":"#/components/parameters/IdPath"}],"get":{"tags":["units"],"summary":"Fetch owner-only exact source evidence for a unit","responses":{"200":{"description":"evidence links and exact source passages"},"404":{"description":"unit not found or not owned by caller"}}}},"/units/{id}/evidence/{linkId}":{"parameters":[{"$ref":"#/components/parameters/IdPath"},{"name":"linkId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"patch":{"tags":["units"],"summary":"Accept or reject an evidence relation","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["review_status"],"properties":{"review_status":{"type":"string","enum":["accepted","rejected"]},"review_notes":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"review updated"}}}},"/projects":{"get":{"tags":["projects"],"summary":"List projects","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Paginated"},{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}}}]}}}}}},"post":{"tags":["projects"],"summary":"Create a project","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":2000},"visibility":{"type":"string","enum":["private","team"],"default":"private"},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":30}}}}}},"responses":{"201":{"description":"created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}}}}},"/projects/{id}":{"parameters":[{"$ref":"#/components/parameters/IdPath"}],"get":{"tags":["projects"],"summary":"Fetch a project","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}}}},"patch":{"tags":["projects"],"summary":"Update a project","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":2000,"nullable":true},"visibility":{"type":"string","enum":["private","team"]},"tags":{"type":"array","items":{"type":"string"},"maxItems":30}}}}}},"responses":{"200":{"description":"ok"}}},"delete":{"tags":["projects"],"summary":"Delete a project","responses":{"204":{"description":"deleted"}}}},"/entities":{"get":{"tags":["entities"],"summary":"List canonical entities","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"search","in":"query","schema":{"type":"string"}},{"name":"type","in":"query","schema":{"type":"string","enum":["person","org","place","policy"]}}],"responses":{"200":{"description":"ok"}}},"post":{"tags":["entities"],"summary":"Create an entity","responses":{"201":{"description":"created"}}}},"/entities/{id}":{"parameters":[{"$ref":"#/components/parameters/IdPath"}],"get":{"tags":["entities"],"summary":"Fetch an entity (with mentions[])","responses":{"200":{"description":"ok"}}}},"/entities/merge":{"post":{"tags":["entities"],"summary":"Collapse duplicate entities into a keeper","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["keeper_id","merge_ids"],"properties":{"keeper_id":{"type":"string","format":"uuid"},"merge_ids":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1}}}}}},"responses":{"200":{"description":"merged"}}}},"/reflections":{"get":{"tags":["reflections"],"summary":"List reflections owned by the caller","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"ok"}}},"post":{"tags":["reflections"],"summary":"Create a reflection (embeds content for semantic search)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scope_description","content"],"properties":{"scope_description":{"type":"string","minLength":1},"content":{"type":"string","minLength":1},"unit_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"entity_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"scout_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"201":{"description":"created"}}}},"/reflections/search":{"post":{"tags":["reflections"],"summary":"Semantic search over reflections","responses":{"200":{"description":"ok"}}}},"/reflections/{id}":{"parameters":[{"$ref":"#/components/parameters/IdPath"}],"get":{"tags":["reflections"],"summary":"Fetch a reflection","responses":{"200":{"description":"ok"}}},"delete":{"tags":["reflections"],"summary":"Delete a reflection","responses":{"204":{"description":"deleted"}}}},"/ingest":{"post":{"tags":["knowledge"],"summary":"Ingest a URL or raw text → extracts atomic information units","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["url","text"]},"url":{"type":"string","format":"uri"},"text":{"type":"string"},"title":{"type":"string"},"criteria":{"type":"string"},"notes":{"type":"string"},"project_id":{"type":"string","format":"uuid"}}}}}},"responses":{"201":{"description":"ingested","content":{"application/json":{"schema":{"type":"object","properties":{"ingest_id":{"type":"string","format":"uuid"},"raw_capture_id":{"type":"string","format":"uuid"},"units":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"statement":{"type":"string"}}}}}}}}}}}},"/user/me":{"get":{"tags":["user"],"summary":"Authed user profile (tier, credit balance, timezone)","responses":{"200":{"description":"ok"}}}},"/user/preferences":{"get":{"tags":["user"],"summary":"Get preferences","responses":{"200":{"description":"ok"}}},"patch":{"tags":["user"],"summary":"Update preferences","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"preferred_language":{"type":"string"},"timezone":{"type":"string"},"health_notifications_enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"ok"}}}},"/user/onboarding-complete":{"post":{"tags":["user"],"summary":"Mark onboarding complete","responses":{"200":{"description":"ok"}}}},"/api-keys":{"get":{"tags":["agents"],"summary":"List active cj_… API keys","responses":{"200":{"description":"ok"}}},"post":{"tags":["agents"],"summary":"Mint a new cj_… API key. The full key is returned ONCE.","responses":{"201":{"description":"created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string","description":"cj_… — store immediately, the server never returns it again"}}}}}}}}},"/api-keys/{id}":{"parameters":[{"$ref":"#/components/parameters/IdPath"}],"delete":{"tags":["agents"],"summary":"Revoke an API key","responses":{"204":{"description":"revoked"}}}},"/mcp-server":{"post":{"tags":["agents"],"summary":"MCP JSON-RPC 2.0 entry point (protocol 2024-11-05)","description":"Used by stdio bridges (scout-mcp) and remote MCP clients. Request/response bodies follow the MCP spec. Use the OAuth metadata endpoint under this path (`/.well-known/oauth-authorization-server`) for client discovery.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"oneOf":[{"type":"string"},{"type":"number"},{"type":"null"}]},"method":{"type":"string","example":"tools/list"},"params":{"type":"object"}}}}}},"responses":{"200":{"description":"JSON-RPC response"}}}},"/openapi-spec":{"get":{"tags":["meta"],"summary":"Fetch this spec","security":[],"responses":{"200":{"description":"ok"}}}},"/snapshots":{"get":{"tags":["snapshots"],"summary":"List Page Archive evidence snapshots (newest first).","description":"Returns the caller's page snapshots, optionally scoped to one scout. Each item lists the artifact kinds it holds (full: mhtml+screenshot+markdown; rendered_thirdparty: screenshot+rawhtml+markdown; markdown_only: markdown) plus RFC 3161 / Wayback trust statuses.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"scout_id","in":"query","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Paginated snapshot list","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Paginated"},{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Snapshot"}}}}]}}}}}}},"/snapshots/{id}/url":{"post":{"tags":["snapshots"],"summary":"Mint a short-lived (5 min) signed download URL for one snapshot artifact.","description":"The URL forces a download (content-disposition attachment) so archived HTML never renders on the storage origin. MHTML downloads as multipart/related for Chrome/Edge.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["artifact"],"properties":{"artifact":{"type":"string","enum":["mhtml","screenshot","rawhtml","markdown","manifest","tsr"]}}}}}},"responses":{"200":{"description":"Signed URL","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"artifact":{"type":"string"},"content_type":{"type":"string"},"expires_in":{"type":"integer"}}}}}},"400":{"description":"Unknown artifact kind"},"404":{"description":"Snapshot not found, not owned by caller, or artifact not present on this row"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Supabase user JWT OR `cj_…` API key. Send as `Authorization: Bearer <token>`."},"anonKey":{"type":"apiKey","in":"header","name":"apikey","description":"Supabase project anon/publishable key. Required at the Kong layer on Supabase hosts. The CLI + MCP bridge send this automatically when `supabase_anon_key` is configured."}},"parameters":{"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"Offset":{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},"IdPath":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}},"responses":{"ValidationError":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"OutOfCredits":{"description":"Not enough credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ProbeGateFailure":{"description":"Probe gate failed: the scout was not created. Body is the ProbeEnvelope; for `type:civic` it also carries `system`, `validated`, `invalid` and replacement `candidates`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ProbeEnvelope"},{"type":"object","properties":{"system":{"$ref":"#/components/schemas/CivicSystem"},"validated":{"type":"array","items":{"type":"string","format":"uri"}},"invalid":{"type":"array","items":{"type":"string","format":"uri"}},"candidates":{"type":"array","items":{"$ref":"#/components/schemas/CivicListingCandidate"}}}}]}}}}},"schemas":{"ScoutType":{"type":"string","enum":["web","beat","social","civic","transport"]},"Regularity":{"type":"string","enum":["daily","weekly","monthly"]},"Scout":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"$ref":"#/components/schemas/ScoutType"},"description":{"type":"string","nullable":true,"description":"Human-readable scout context shown in clients."},"criteria":{"type":"string","nullable":true},"topic":{"type":"string","nullable":true,"description":"1-3 short comma-separated tags for organization, not filtering criteria."},"url":{"type":"string","format":"uri","nullable":true},"location":{"type":"object","nullable":true,"additionalProperties":true},"platform":{"type":"string","enum":["instagram","x","facebook","tiktok","linkedin"],"nullable":true},"profile_handle":{"type":"string","nullable":true},"monitor_mode":{"type":"string","enum":["summarize","criteria"],"nullable":true,"description":"Social scout mode. New public clients use criteria by default; legacy rows may contain null and execute with summarize semantics."},"track_removals":{"type":"boolean","nullable":true},"regularity":{"$ref":"#/components/schemas/Regularity","nullable":true},"schedule_cron":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"consecutive_failures":{"type":"integer"},"last_run":{"type":"object","nullable":true,"properties":{"started_at":{"type":"string","format":"date-time","nullable":true},"status":{"type":"string","nullable":true},"articles_count":{"type":"integer","nullable":true},"merged_existing_count":{"type":"integer","nullable":true}}},"project_id":{"type":"string","format":"uuid","nullable":true},"created_at":{"type":"string","format":"date-time"},"archive_enabled":{"type":"boolean","description":"Evidence archiving for Page Scouts (Pro/Team on hosted SaaS; enabling on free tier returns 403 archive_forbidden)."},"wayback_enabled":{"type":"boolean","description":"Also submit captures to the Internet Archive Wayback Machine (public). Default true; per-scout opt-out for sensitive investigations."},"config":{"$ref":"#/components/schemas/TransportConfig","nullable":true}},"required":["id","name","type","is_active"]},"ScoutCreate":{"type":"object","required":["name","type"],"allOf":[{"if":{"properties":{"type":{"enum":["web","beat","social","civic"]}}},"then":{"anyOf":[{"required":["topic"],"properties":{"topic":{"pattern":"[^\\s,]"}}},{"required":["location"]}]}},{"if":{"properties":{"type":{"const":"web"}}},"then":{"required":["url"]}},{"if":{"properties":{"type":{"const":"social"}}},"then":{"required":["platform","profile_handle"],"if":{"required":["monitor_mode"],"properties":{"monitor_mode":{"const":"criteria"}}},"then":{"required":["criteria"],"properties":{"criteria":{"pattern":"\\S"}}}}},{"if":{"properties":{"type":{"const":"civic"}}},"then":{"required":["root_domain","tracked_urls"],"if":{"required":["import_current_items"],"properties":{"import_current_items":{"const":true}}},"then":{"required":["preview_snapshot_token"]}}},{"if":{"properties":{"type":{"const":"transport"}}},"then":{"required":["config"]}}],"properties":{"name":{"type":"string","minLength":1,"pattern":"\\S","maxLength":200},"type":{"$ref":"#/components/schemas/ScoutType"},"description":{"type":"string","maxLength":2000,"description":"Optional human-readable context. Use criteria for filtering and notification rules."},"criteria":{"type":"string","maxLength":4000},"topic":{"type":"string","maxLength":200,"description":"For web, beat, social and civic scouts, required when location is omitted. Use 1-3 short comma-separated tags, each at most 50 characters. Optional for transport scouts. This organizational Project label is distinct from project_id."},"url":{"type":"string","format":"uri","maxLength":2000},"location":{"type":"object","anyOf":[{"required":["displayName"],"properties":{"displayName":{"type":"string","pattern":"\\S"}}},{"required":["city"],"properties":{"city":{"type":"string","pattern":"\\S"}}},{"required":["state"],"properties":{"state":{"type":"string","pattern":"\\S"}}},{"required":["country"],"properties":{"country":{"type":"string","pattern":"\\S"}}}],"additionalProperties":true},"regularity":{"$ref":"#/components/schemas/Regularity"},"schedule_cron":{"type":"string","maxLength":200,"description":"When supplied, the server establishes the initial baseline before scheduling. This applies to web, beat, social, and civic scouts."},"day_number":{"type":"integer","minimum":0,"maximum":31},"time":{"type":"string","pattern":"^\\d{1,2}:\\d{2}$"},"project_id":{"type":"string","format":"uuid"},"priority_sources":{"type":"array","items":{"type":"string"}},"platform":{"type":"string","enum":["instagram","x","facebook","tiktok","linkedin"],"description":"Required for social scouts."},"profile_handle":{"type":"string","minLength":1,"pattern":"\\S","maxLength":200,"description":"Required for social scouts. For LinkedIn, provide a full personal-profile URL such as https://www.linkedin.com/in/example; other platforms accept the profile username."},"monitor_mode":{"type":"string","enum":["summarize","criteria"],"example":"criteria","x-client-default":"criteria","description":"New public clients should default social scouts to criteria and provide non-empty criteria text. Pass summarize explicitly to collect all substantive new posts. For backward compatibility, raw REST requests that omit both monitor_mode and criteria continue with summarize semantics."},"track_removals":{"type":"boolean","default":false,"description":"For social scouts, also report posts removed after the saved baseline."},"root_domain":{"type":"string","minLength":1,"maxLength":300,"pattern":"\\S","description":"Required for Civic scouts: the municipal website domain."},"tracked_urls":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"string","format":"uri","maxLength":2000},"description":"Required for Civic scouts: official meeting-document listing URLs verified by discovery."},"import_current_items":{"type":"boolean","default":false,"description":"Import the server-owned Civic preview; requires preview_snapshot_token when true."},"preview_snapshot_token":{"type":"string","format":"uuid","description":"Token returned by the Civic preview. Required only when import_current_items is true."},"archive_enabled":{"type":"boolean","description":"Evidence archiving for Page Scouts (Pro/Team on hosted SaaS; enabling on free tier returns 403 archive_forbidden)."},"wayback_enabled":{"type":"boolean","description":"Also submit captures to the Internet Archive Wayback Machine (public). Default true; per-scout opt-out for sensitive investigations."},"transport_baseline_ids":{"type":"array","maxItems":500,"items":{"type":"string"},"description":"Transport scouts only: baseline_ids returned by POST /transport-test. Pass an explicit empty array when Step 1 found no current matches; omission preserves legacy first-run baseline behavior."},"config":{"$ref":"#/components/schemas/TransportConfig","description":"Required for type transport. Fleet Scout alerts when a watched object enters this area; criteria is an optional post-entry filter."}}},"ScoutUpdate":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000,"nullable":true},"criteria":{"type":"string","nullable":true},"topic":{"type":"string","nullable":true,"description":"1-3 short comma-separated tags. Scout must retain topic or location."},"url":{"type":"string","format":"uri","nullable":true},"regularity":{"$ref":"#/components/schemas/Regularity"},"schedule_cron":{"type":"string"},"day_number":{"type":"integer","minimum":0,"maximum":31},"time":{"type":"string","pattern":"^\\d{1,2}:\\d{2}$"},"is_active":{"type":"boolean"},"project_id":{"type":"string","format":"uuid"},"platform":{"type":"string","enum":["instagram","x","facebook","tiktok","linkedin"]},"profile_handle":{"type":"string","maxLength":200},"monitor_mode":{"type":"string","enum":["summarize","criteria"],"description":"When changing a social scout to criteria mode, also provide non-empty criteria text."},"track_removals":{"type":"boolean"},"archive_enabled":{"type":"boolean","description":"Evidence archiving for Page Scouts (Pro/Team on hosted SaaS; enabling on free tier returns 403 archive_forbidden)."},"wayback_enabled":{"type":"boolean","description":"Also submit captures to the Internet Archive Wayback Machine (public). Default true; per-scout opt-out for sensitive investigations."},"config":{"$ref":"#/components/schemas/TransportConfig","description":"Full transport configuration replacement; use a circle area for all modes."}}},"TransportConfig":{"type":"object","required":["mode","watch_ids","geofence"],"properties":{"mode":{"type":"string","enum":["aircraft","vessel"]},"watch_ids":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"string"}},"geofence":{"$ref":"#/components/schemas/TransportArea"},"categories":{"type":"array","items":{"type":"string"}},"criteria":{"type":"string","description":"Optional filter evaluated only after a watched object enters the area."}}},"TransportArea":{"type":"object","required":["center","radius_km"],"properties":{"center":{"type":"object","required":["lat","lon"],"properties":{"lat":{"type":"number","minimum":-90,"maximum":90},"lon":{"type":"number","minimum":-180,"maximum":180}}},"radius_km":{"type":"number","exclusiveMinimum":0,"maximum":1500,"description":"Maximum 463 km for aircraft and 1,500 km for vessel; enforced according to mode."},"display_name":{"type":"string","maxLength":256},"maptiler_id":{"type":"string","maxLength":256}},"description":"Map-selected circular area. Fleet Scout alerts when a watched object enters it."},"Unit":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"statement":{"type":"string","nullable":true},"unit_type":{"type":"string","nullable":true},"context_excerpt":{"type":"string","nullable":true},"entities":{"type":"array","items":{"type":"object","properties":{"entity_id":{"type":"string","format":"uuid","nullable":true},"canonical_name":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"mention_text":{"type":"string"}},"required":["entity_id","canonical_name","type","mention_text"]}},"location":{"type":"object","nullable":true,"additionalProperties":true},"country":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"topic":{"type":"string","nullable":true},"scout_type":{"type":"string","nullable":true},"occurred_at":{"type":"string","format":"date","nullable":true},"extracted_at":{"type":"string","format":"date-time","nullable":true},"occurrence_count":{"type":"integer"},"source":{"type":"object","properties":{"url":{"type":"string","format":"uri","nullable":true},"title":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true}},"required":["url","title","domain"]},"sources":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","format":"uri","nullable":true},"title":{"type":"string","nullable":true},"domain":{"type":"string","nullable":true},"extracted_at":{"type":"string","format":"date-time","nullable":true}},"required":["url","title","domain","extracted_at"]}},"linked_scouts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","nullable":true},"name":{"type":"string","nullable":true},"type":{"type":"string","nullable":true}},"required":["id","name","type"]}},"scout_id":{"type":"string","format":"uuid","nullable":true},"scout_name":{"type":"string","nullable":true},"verification":{"type":"object","properties":{"verified":{"type":"boolean"},"verified_at":{"type":"string","format":"date-time","nullable":true},"verified_by":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true}},"required":["verified","verified_at","verified_by","notes"]},"usage":{"type":"object","properties":{"used_in_article":{"type":"boolean"},"used_at":{"type":"string","format":"date-time","nullable":true},"used_in_url":{"type":"string","format":"uri","nullable":true}},"required":["used_in_article","used_at","used_in_url"]},"deletion":{"type":"object","properties":{"deleted":{"type":"boolean"},"deleted_at":{"type":"string","format":"date-time","nullable":true},"deleted_by":{"type":"string","nullable":true},"reason":{"type":"string","nullable":true}},"required":["deleted","deleted_at","deleted_by","reason"]},"tags":{"type":"array","items":{"type":"string"}}},"required":["id","statement","context_excerpt","unit_type","entities","location","occurred_at","extracted_at","occurrence_count","source","sources","linked_scouts","scout_id","scout_name","verification","usage","deletion","tags"]},"UnitUpdate":{"type":"object","properties":{"verified":{"type":"boolean"},"verification_notes":{"type":"string","maxLength":4000,"nullable":true},"verified_by":{"type":"string","maxLength":200,"nullable":true},"used_in_article":{"type":"boolean"},"used_at":{"type":"string","format":"date-time","nullable":true},"used_in_url":{"type":"string","format":"uri","nullable":true},"deletion_reason":{"type":"string","maxLength":4000,"nullable":true}}},"Project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"visibility":{"type":"string","enum":["private","team"]},"tags":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","format":"date-time"}}},"Paginated":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object"}},"pagination":{"type":"object","properties":{"total":{"type":"integer"},"offset":{"type":"integer"},"limit":{"type":"integer"},"has_more":{"type":"boolean"}}}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"detail":{"type":"string"}}},"ProbeErrorCode":{"type":"string","description":"Closed enum shared by every pre-creation probe and by the POST /scouts create gate. `no_meetings_detected` is an outcome (the probe worked and found no meetings; `candidates` is supplied); every other code is a failure. `criteria_not_met` is advisory and only appears on POST /scouts/test with `ok:true`.","enum":["unreachable","blocked","empty_content","outside_configured_page","page_too_long","no_meetings_detected","no_documents","parse_failed","model_failed","criteria_not_met"]},"ProbeEnvelope":{"type":"object","description":"Probe envelope carried (additively) by POST /scouts/test, POST /civic/discover, POST /civic/test and by the HTTP 422 body of POST /scouts. When `ok` is false both `error_code` and a human-readable `error` are present.","required":["ok","stage"],"properties":{"ok":{"type":"boolean"},"stage":{"type":"string","enum":["reach","detect","sample"],"description":"reach = fetch the page (web probe and web create gate); detect = find council meeting listings (civic discover/validate and civic create gate); sample = resolve and extract from a bounded document sample (civic test)."},"error_code":{"$ref":"#/components/schemas/ProbeErrorCode"},"error":{"type":"string","description":"Stable catalogue sentence for the code, optionally followed by operator detail in parentheses."}}},"CivicSystem":{"type":"string","enum":["moderngov","legistar","generic"],"description":"Committee-system fingerprint detected from the council's URLs."},"CivicListingCandidate":{"type":"object","required":["url","description","confidence","system","documents_visible","recommended"],"properties":{"url":{"type":"string","format":"uri"},"description":{"type":"string"},"confidence":{"type":"number","minimum":0,"maximum":1},"system":{"$ref":"#/components/schemas/CivicSystem"},"documents_visible":{"type":"integer","minimum":1,"description":"Leaf meeting documents the resolver already saw behind this listing. Only candidates with at least one are returned."},"recommended":{"type":"boolean","description":"True on the best candidate (most documents visible, then confidence)."}}},"Snapshot":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"scout_id":{"type":"string","format":"uuid"},"capture_kind":{"type":"string","enum":["baseline","change"]},"fidelity":{"type":"string","enum":["full","rendered_thirdparty","markdown_only"]},"served_by":{"type":"string","nullable":true},"captured_at":{"type":"string","format":"date-time"},"requested_url":{"type":"string"},"final_url":{"type":"string","nullable":true},"http_status":{"type":"integer","nullable":true},"sizes":{"type":"object"},"trust":{"type":"object","properties":{"tsa_status":{"type":"string"},"wayback_status":{"type":"string"},"wayback_url":{"type":"string","nullable":true}}},"artifacts":{"type":"array","items":{"type":"string","enum":["mhtml","screenshot","rawhtml","markdown","manifest","tsr"]}}}}}}}