Authentication and permissions
Every request to https://api.kaily.ai/v1 carries one header:
Authorization: Bearer kly_<env>_<prefix>_<secret>
The credential must be in the header. Credentials supplied as query parameters (authorization, access_token, api_key, apikey, token) are rejected with 403 access_denied, so your key never lands in an access log or a browser history.
What can go wrong
| Situation | Response |
|---|---|
No Authorization header, or not a Bearer value | 401 invalid_credentials — "Missing or malformed Authorization header" |
| Unknown, revoked or expired key | 401 invalid_credentials — "Invalid API key" |
| A credential in the query string | 403 access_denied |
| Key is valid but lacks the scope the endpoint declares | 403 access_denied, naming the scope |
| Key is restricted to certain AI agents and the route names another | 403 access_denied |
| The AI agent in the path is not in your account | 404 not_found |
Scopes
A key carries an explicit list of scopes. Every endpoint declares exactly one scope, and the request is refused unless the key holds it. There is no implicit inheritance: aiagents:write does not imply aiagents:read, so a key that both reads and writes needs both.
Each scope names a resource you can reach at /v1/<resource>, so a refusal always points at something you can look up. Note the two distinct senses of "agent": aiagents:* are AI agents (copilots), staff:* are human support agents.
This release exposes two scopes. More arrive as their resources are released, so the console only ever offers you scopes that map to a real endpoint.
| Scope | Grants |
|---|---|
aiagents:read | List and get your AI agents, and read their stats |
aiagents:write | Create and update an AI agent, and enable or disable one |
Presets in the console:
| Preset | Scopes |
|---|---|
| AI agent read | aiagents:read |
| Full access | every scope above |
| Custom | pick individual scopes |
GET /v1/me needs no scope — any valid key can describe itself.
Helpdesk
| Scope | Covers |
|---|
Helpdesk configuration
| Scope | Covers |
|---|
AI agents
| Scope | Covers |
|---|---|
aiagents:read | List and get AI agents; read agent stats |
aiagents:write | Create and update AI agents; enable and disable them |
aiagents:query | Ask an AI agent a question |
The scope catalogue is the full vocabulary and is stable; the set of endpoints behind it grows as resources are released. The API reference is generated from the running service and is therefore the authoritative list of what is live right now — if a scope's endpoints are not in the reference yet, they are not callable yet.
Delivery
| Scope | Covers |
|---|
Webhooks use one scope for both reading and writing, because a subscription is only ever managed by the integration that owns it.
Presets
The console's create-key dialog offers four bundles as a starting point. You can tick individual scopes instead, or start from a preset and adjust.
| Preset | Scopes |
|---|---|
aiagent-query | aiagents:read, aiagents:query |
full-access | Every scope above |
helpdesk-read is the right choice for a reporting or warehouse-sync job. aiagent-query is the minimum for embedding an agent's answers in your own product.
Reading a 403
When a key is missing a scope, the message names it:
{
"error": {
"code": "access_denied",
"message": "This API key is missing the required scope: aiagents:write"
}
}
You never have to guess. Add that scope to the key — or create a new key with it — and retry.
A key reaches every AI agent in the account it belongs to. An id from another account is reported as 404, not 403, so a key cannot be used to discover which ids exist elsewhere.
Rotating a key
Because scopes and lifecycle live on the key while everything durable lives on the key, rotation is a rolling deploy rather than a migration:
- Create a second key with the same scopes.
- Deploy it.
- Watch
last_used_aton the old key in the console (or inGET /v1/mefrom a process still holding it) until it stops moving. - Revoke the old key.
Revoking a key takes effect immediately — the next request with it gets 401. Deactivating the key kills every key beneath it at once, which is the switch to reach for if a credential is leaked and you are not sure which one.