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, Agent 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.
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.