{"openapi":"3.1.0","info":{"title":"sayintel API","version":"1.0.0","description":"Every capability in sayintel, callable by an agent. Authenticate with an si_ API key as a bearer token. Responses always use the same envelope {ok, tool, kind, data, error}: branch on ok, read data.*, never parse prose. Scopes: read (lists and dossiers), write (curation, campaign creation, runs, rewrites), send (pushing approved sequences outbound)."},"servers":[{"url":"https://sayintel.com/api/public/v1"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"si_ API key"}}},"security":[{"bearerAuth":[]}],"paths":{"/whoami":{"get":{"operationId":"whoami","summary":"Whoami","description":"Return the SayIntel user id and granted scopes for this MCP token.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/credits":{"get":{"operationId":"get_credit_balance","summary":"Get credit balance","description":"Return the user's current credit balance and the last 20 ledger entries.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/qa-codes":{"get":{"operationId":"list_qa_codes","summary":"List qa codes","description":"Enumerate the machine-readable vocabulary this API answers with: every QA hard-fail code with its remediation action and hint, plus the envelope error codes and their HTTP statuses. Static reference — safe to cache. Use it to map a get_draft_qa hard_fail_code to the fix action without guessing.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/campaigns":{"get":{"operationId":"list_campaigns","summary":"List campaigns","description":"List the user's outbound campaigns (conferences). Paginated: pass the returned next_cursor to fetch the next page while has_more is true.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page.","schema":{"type":"string","maxLength":32,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page."}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}},"post":{"operationId":"create_campaign","summary":"Create campaign","description":"Create a new outbound campaign, optionally attach a conference URL (public https only) and auto-start the pipeline. Seeds ICP, style memory, and sender defaults from the user's profile. Scraping runs asynchronously via cron; poll get_run_status to follow progress. Pass dry_run=true to get cost_preview without creating anything. Returns { campaign_id, run_started, cost_preview }.","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Accepted and ignored: this operation is naturally idempotent.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":200},"pitch":{"type":"string","maxLength":2000},"cta":{"type":"string","maxLength":1000},"your_name":{"type":"string","maxLength":200},"your_role":{"type":"string","maxLength":200},"tone":{"type":"string","maxLength":80},"conference_url":{"type":"string","format":"uri","maxLength":500},"auto_start":{"type":"boolean"},"dry_run":{"type":"boolean","description":"When true nothing is written, started, or spent. The tool returns only cost_preview so an agent can estimate credits first."}}}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/campaigns/{campaign_id}":{"get":{"operationId":"get_campaign","summary":"Get campaign","description":"Get full campaign config: positioning, ICP, pitch, CTA, conference URLs, totals.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/campaigns/{campaign_id}/stats":{"get":{"operationId":"campaign_stats","summary":"Campaign stats","description":"Funnel stats for a single campaign.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/campaigns/{campaign_id}/qa":{"get":{"operationId":"get_draft_qa","summary":"Get draft qa","description":"Machine-readable QA state for drafts. Pass speaker_id alone to inspect one speaker, or campaign_id for the whole campaign. Returns per-message qa_score, qa_evaluated_at, hard-fail codes, actionable fix hints ({code, action, hint}), needs_human / needs_redo, approvable flag, plus per-speaker buckets (ready_to_send | needs_rewrite | needs_human | awaiting_qa | skipped | sent) and a rollup of the most common fail codes. Use this to decide what to rewrite (request_rewrite) before pushing. Filter with only_failing=true. Paginated: pass next_cursor while has_more is true.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"description":"Required unless speaker_id is given.","schema":{"type":"string","format":"uuid","description":"Required unless speaker_id is given."}},{"name":"speaker_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"only_failing","in":"query","required":false,"description":"Only return unsent drafts that are not approvable (hard fail, low score, or unevaluated).","schema":{"type":"boolean","description":"Only return unsent drafts that are not approvable (hard fail, low score, or unevaluated)."}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page.","schema":{"type":"string","maxLength":32,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page."}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/campaigns/{campaign_id}/conference-urls":{"post":{"operationId":"add_conference_url","summary":"Add conference url","description":"Add a new conference URL to a campaign. Must be a public https URL — private/internal hostnames are rejected.","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Accepted and ignored: this operation is naturally idempotent.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","maxLength":500}},"required":["url"]}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/campaigns/{campaign_id}/runs":{"post":{"operationId":"start_campaign_run","summary":"Start campaign run","description":"Start the full pipeline run for a campaign (prequalify, enrich, qualify, research, draft). Idempotent: if a run is already queued or running, returns it instead of stacking a second. Already-enriched speakers are not re-billed. Pass dry_run=true to get cost_preview (credits this run would spend) without starting anything. Poll get_run_status for progress.","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Retry-safe key. A retry with the same key and payload replays the first result instead of repeating the work.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"dry_run":{"type":"boolean","description":"When true nothing is written, started, or spent. The tool returns only cost_preview so an agent can estimate credits first."},"idempotency_key":{"type":"string","minLength":1,"maxLength":200,"description":"Optional retry guard. Reusing the same key with the same arguments replays the first result instead of running the action twice; reusing it with different arguments is rejected."}}}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/campaigns/{campaign_id}/runs/latest":{"get":{"operationId":"get_run_status","summary":"Get run status","description":"Pipeline status for a campaign: latest run (stage, status, progress, error), live counts (speakers, drafts, signals), and per-URL scrape status. The pipeline is cron-driven, so progress arrives in batches; poll every 15-30 seconds while a run is active, no faster.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/speakers":{"get":{"operationId":"search_speakers","summary":"Search speakers","description":"Search speakers across the user's campaigns. Filter by campaign_id, name (ilike), company (ilike), prequalify_verdict, reply_status, or selected. Paginated: pass next_cursor while has_more is true.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[{"name":"campaign_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"name","in":"query","required":false,"schema":{"type":"string","maxLength":200}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":200}},{"name":"prequalify_verdict","in":"query","required":false,"schema":{"type":"string","enum":["qualified","unsure","disqualified"]}},{"name":"reply_status","in":"query","required":false,"schema":{"type":"string","maxLength":40}},{"name":"selected","in":"query","required":false,"schema":{"type":"boolean"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page.","schema":{"type":"string","maxLength":32,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page."}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/speakers/{speaker_id}":{"get":{"operationId":"get_speaker_dossier","summary":"Get speaker dossier","description":"Full dossier on a speaker: bio, talk, enrichment, AI research signals, drafted messages.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/speakers/{speaker_id}/selected":{"post":{"operationId":"set_speaker_selected","summary":"Set speaker selected","description":"Mark a speaker as selected (true) or excluded (false) from the outbound list.","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Accepted and ignored: this operation is naturally idempotent.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"selected":{"type":"boolean"}},"required":["selected"]}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/speakers/{speaker_id}/reply-status":{"post":{"operationId":"set_speaker_reply_status","summary":"Set speaker reply status","description":"Update reply status for a speaker. Allowed: replied, booked, no_reply, or null to clear.","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Accepted and ignored: this operation is naturally idempotent.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reply_status":{"type":"string","enum":["replied","booked","no_reply"]}},"required":["reply_status"]}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/messages":{"get":{"operationId":"list_messages","summary":"List messages","description":"List drafted/sent messages across a campaign, with QA fields (qa_score, qa_evaluated_at, needs_human, needs_redo). Filter by sent. Paginated: pass next_cursor while has_more is true.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[{"name":"campaign_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"sent","in":"query","required":false,"schema":{"type":"boolean"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page.","schema":{"type":"string","maxLength":32,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page."}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/messages/{message_id}":{"post":{"operationId":"update_message","summary":"Update message","description":"Edit drafted message content. Cannot edit a message after it is sent.","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Accepted and ignored: this operation is naturally idempotent.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","minLength":1,"maxLength":4000}},"required":["content"]}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/messages/{message_id}/sent":{"post":{"operationId":"mark_message_sent","summary":"Mark message sent","description":"Mark a drafted message as sent (records sent_at = now). Idempotent unless force=true.","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Accepted and ignored: this operation is naturally idempotent.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"force":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/campaigns/{campaign_id}/rewrites":{"post":{"operationId":"request_rewrite","summary":"Request rewrite","description":"Rewrite drafted messages per an instruction (e.g. 'shorter, drop the exclamation, lead with the talk'). Targets specific speakers via speaker_ids, specific drafts via message_ids (rewrites all slots for those speakers), or the whole campaign if neither is given. Rewrites pass the same hard-fail QA gate as fresh drafts; a rewrite that violates it is skipped, not saved. Pass dry_run=true to see cost_preview (model calls, no credits) before committing.","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Retry-safe key. A retry with the same key and payload replays the first result instead of repeating the work.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"instruction":{"type":"string","minLength":1,"maxLength":2000},"speaker_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"message_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"dry_run":{"type":"boolean","description":"When true nothing is written, started, or spent. The tool returns only cost_preview so an agent can estimate credits first."},"idempotency_key":{"type":"string","minLength":1,"maxLength":200,"description":"Optional retry guard. Reusing the same key with the same arguments replays the first result instead of running the action twice; reusing it with different arguments is rejected."}},"required":["instruction"]}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/intent-leads":{"get":{"operationId":"list_intent_leads","summary":"List intent leads","description":"List intent-based leads (LinkedIn signals) for a campaign with intent_enabled=true. Paginated: pass next_cursor while has_more is true.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[{"name":"campaign_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"selected","in":"query","required":false,"schema":{"type":"boolean"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page.","schema":{"type":"string","maxLength":32,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page."}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/intent-leads/{lead_id}/selected":{"post":{"operationId":"set_intent_lead_selected","summary":"Set intent lead selected","description":"Mark an intent lead as selected (true) or excluded (false).","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"lead_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Accepted and ignored: this operation is naturally idempotent.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"selected":{"type":"boolean"}},"required":["selected"]}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/campaigns/{campaign_id}/push":{"post":{"operationId":"push_approved","summary":"Push approved","description":"Push every send-ready, not-yet-pushed sequence in a campaign to its wired HeyReach campaign (optionally narrowed to speaker_ids). Only already-approved sequences go: all three slots drafted, QA-evaluated and cleared, not flagged needs_human/needs_redo, LinkedIn URL present, freshness guard passed. Messages are marked sent only after HeyReach confirms, and a human still arms the HeyReach campaign itself. Pass dry_run=true to see how many sequences would go, without pushing. Requires the 'send' token scope.","tags":["send"],"security":[{"bearerAuth":["sayintel:send"]}],"parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Retry-safe key. A retry with the same key and payload replays the first result instead of repeating the work.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"speaker_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"dry_run":{"type":"boolean","description":"When true nothing is written, started, or spent. The tool returns only cost_preview so an agent can estimate credits first."},"idempotency_key":{"type":"string","minLength":1,"maxLength":200,"description":"Optional retry guard. Reusing the same key with the same arguments replays the first result instead of running the action twice; reusing it with different arguments is rejected."}}}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/webhooks":{"get":{"operationId":"list_webhooks","summary":"List webhooks","description":"List the caller's registered webhook endpoints with their subscribed events, active flag and recent failure count. Secrets are never returned.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}},"post":{"operationId":"create_webhook","summary":"Create webhook","description":"Register an https endpoint to receive signed event callbacks. Events: run.started, run.completed, run.failed, drafts.ready, message.ready, message.approved, push.completed, or \"*\" for all. Returns the signing secret ONCE — deliveries carry `X-Sayintel-Signature: t=<unix>,v1=<hex hmac_sha256(secret, `${t}.${rawBody}`)>`. Reject deliveries older than 5 minutes.","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Accepted and ignored: this operation is naturally idempotent.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","maxLength":1000,"description":"Public https URL that will receive POST callbacks."},"events":{"type":"array","items":{"type":"string","maxLength":60},"description":"Event names to subscribe to. Defaults to [\"*\"] (all events)."},"description":{"type":"string","maxLength":200}},"required":["url"]}}}},"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/webhooks/{webhook_id}/delete":{"post":{"operationId":"delete_webhook","summary":"Delete webhook","description":"Delete a webhook endpoint and its pending deliveries.","tags":["write"],"security":[{"bearerAuth":["sayintel:write"]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Accepted and ignored: this operation is naturally idempotent.","schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}},"/webhook-deliveries":{"get":{"operationId":"list_webhook_deliveries","summary":"List webhook deliveries","description":"Inspect recent webhook deliveries (event, status, attempts, response status, error) so a failing integration is debuggable without guessing.","tags":["read"],"security":[{"bearerAuth":["sayintel:read"]}],"parameters":[{"name":"webhook_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["pending","delivered","failed","cancelled"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page.","schema":{"type":"string","maxLength":32,"description":"Opaque cursor from a previous call's next_cursor. Omit for the first page."}}],"responses":{"200":{"description":"Capability envelope.","content":{"application/json":{"schema":{"type":"object","required":["ok","tool","kind","data","error"],"properties":{"ok":{"type":"boolean","description":"true when the call succeeded."},"tool":{"type":"string","description":"Capability name."},"kind":{"type":"string","enum":["read","write","send"]},"data":{"description":"Payload on success, null on failure."},"error":{"type":["object","null"],"properties":{"code":{"type":"string","enum":["unauthenticated","forbidden_scope","rate_limited","not_found","invalid_request","tool_error"]},"message":{"type":"string"}}}}}}}},"400":{"description":"invalid_request"},"401":{"description":"unauthenticated"},"403":{"description":"forbidden_scope"},"404":{"description":"not_found"},"429":{"description":"rate_limited"}}}}}}