Authentication.
Two ways in, same permissions model underneath. A token for scripts and headless agents, an OAuth connection for MCP clients that sign a human in.
API tokens
Mint one in the app under Settings, MCP access. Tokens start with si_, are shown once, and are scoped to the account that created them.
Authorization: Bearer si_live_...
Scopes
- read: list and inspect campaigns, speakers, drafts, QA state, credits.
- write: create campaigns, start runs, curate selections, edit drafts, request rewrites.
- send: push approved sequences to a sending tool. Only ever granted deliberately, and a human still arms the sending campaign.
Ask a key what it may do, rather than guessing:
curl https://sayintel.com/api/public/v1/capabilities \ -H "Authorization: Bearer $SAYINTEL_TOKEN"
OAuth, for MCP clients
Claude, ChatGPT and other connector-based clients use the OAuth door at https://sayintel.com/mcp. The client registers itself, the human approves it on a consent screen, and tools then run as that user with read and write. Send stays off the OAuth path by design. MCP setup.
Agent sign-up, with a human confirming
An agent with no key can ask for an account on its human's behalf. The request creates nothing by itself: the person gets an email, opens it, types the code their agent showed them (it is not in the email) and presses Confirm. Only then is the account created (or, for an existing account, attached after they sign in) and a key minted with read and write. The send scope is never granted this way.
curl -X POST https://sayintel.com/api/public/v1/agent-signup \
-H "Content-Type: application/json" \
-d '{"email": "human@their-company.com", "agent_name": "Research Assistant"}'
curl https://sayintel.com/api/public/v1/agent-signup/$REQUEST_ID \
-H "Authorization: Bearer $POLL_SECRET"The poll answers pending_confirmation, expired, denied or confirmed, and returns the key exactly once. Requests expire after 24 hours and are rate-limited per IP and per email. If the door is switched off it answers 503 agent_signup_disabled. The full flow is in the agent quickstart.
Idempotency
Every POST accepts an Idempotency-Key header. Retrying with the same key and payload replays the first result instead of doing the work twice, which matters for anything that spends credits.
Idempotency-Key: 5c1b2f0e-2a9c-4a4b-9a3a-3c8a1f7b1d21
Dry runs and cost
Spending actions accept dry_run: true and always return a cost_preview, so an agent can show its human the bill before committing.
Rate limits
Limits are per token and per capability kind. Over the limit you get 429 with error.code = rate_limited. Back off and retry; do not hammer. Prefer webhooks or the event stream over polling.