Authentication
API keys
All Chatty API requests require a Bearer API key in the Authorization header:
Authorization: Bearer chatty_sk_<your-key>
Keys are created per bot in Dashboard → Settings → API Keys. A bot can have multiple keys — create separate ones for different environments and revoke them independently.
API keys are shown once at creation and cannot be retrieved again. Store them in environment variables, never in source code.
Scopes
Every key carries one or more scopes controlling which endpoints it can call. Assign the minimum scopes needed.
| Scope | Endpoints |
|---|---|
chat | POST /api/v1/chat |
read | GET leads, conversations, knowledge, analytics, usage, bot details |
write | POST/DELETE knowledge, DELETE conversations |
admin | All of the above |
Embed integration
chat. Visitors send messages but can't read your data.CRM sync
read to pull leads and conversations.Content pipeline
write to add/remove knowledge sources.Internal dashboard
admin for full access.IP allowlist
Restrict a key to specific IPs or CIDR ranges. Requests from any other IP return 403.
["203.0.113.42", "10.0.0.0/8", "2001:db8::/32"]Leave the allowlist empty to allow requests from any IP. The embed widget uses a separate mechanism and is not affected.
Key management
| Method | Endpoint | Description |
|---|---|---|
POST | /api/keys | Create a new key |
GET | /api/keys?bot_id=... | List all keys for a bot |
PATCH | /api/keys/{id} | Update name, scopes, allowlist |
DELETE | /api/keys/{id} | Revoke permanently |
These require a Supabase session token (dashboard login), not an API key.
OAuth2 (multi-account access)
An API key is scoped to exactly one bot — it can't create new bots or list every bot on your account. For that, authenticate as your Chatty account instead, via OAuth2. This is what powers the Developer API's bot-management endpoints and the MCP server, and is the right choice for building an integration or connecting an AI agent rather than embedding a single widget.
Chatty implements the standard authorization-code grant with mandatory PKCE (S256) — the same flow GitHub, Google, and most modern APIs use:
- Register your app —
POST https://api.chatty.personaliai.com/oauth/registerwith{"client_name": "...", "redirect_uris": ["https://yourapp.com/callback"]}. Returns aclient_id(and aclient_secretif you asked for a confidential client via"token_endpoint_auth_method": "client_secret_post"— omit it for a public/PKCE-only client, e.g. a CLI tool or desktop app). - Send the user to approve access — redirect their browser to
/oauth/authorizewithclient_id,redirect_uri,scope, a PKCEcode_challenge(S256), and astatevalue you'll verify on return. - Exchange the code — after approval, your
redirect_urireceives?code=...&state=....POST /oauth/token(form-encoded, not JSON) withgrant_type=authorization_code, thecode, yourcode_verifier, andredirect_urito get an access token (1 hour) and refresh token (30 days). - Use the token — same as an API key:
Authorization: Bearer chatty_oat_...on any/api/v1/*request.
curl -X POST https://api.chatty.personaliai.com/oauth/token \
-d grant_type=authorization_code \
-d code=THE_CODE_FROM_STEP_3 \
-d redirect_uri=https://yourapp.com/callback \
-d client_id=YOUR_CLIENT_ID \
-d code_verifier=YOUR_PKCE_VERIFIERapplication/x-www-form-urlencoded (RFC 6749), not JSON — this is what makes off-the-shelf OAuth2 client libraries work against it without modification.Discovery metadata (for OAuth2/MCP client libraries that auto-configure themselves) is served at /.well-known/oauth-authorization-server.
Security headers
Every response includes:
| Header | Value |
|---|---|
X-Request-ID | Unique UUID per request — include in bug reports |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload |
Content-Security-Policy | default-src 'none' |