Authentication¶
There's no single API host¶
AiFlow is self-hosted and single-tenant: every client runs their own
copy, on their own infrastructure, with their own database. There's no
shared, multi-tenant AiFlow API to sign up for. Your organization's
AiFlow instance lives at a URL your team controls, typically something
like https://api.your-domain.com.
Every example on this site uses https://api.your-domain.com as a
placeholder. Substitute your own deployment's URL wherever you see it.
Two kinds of credentials¶
AiFlow has two completely separate authentication mechanisms, built for
two different audiences. Mixing them up is the most common cause of a
confusing 401.
| Admin session token | API key | |
|---|---|---|
| Who it's for | A person logged into the admin dashboard | A system integrating with your AiFlow instance |
| How you get one | POST /api/v1/auth/login with an admin email and password |
Created by an admin, in the dashboard or via the API |
| Lifetime | Short-lived (8 hours by default), meant to be refreshed by logging in again | Long-lived until revoked (or an optional expiry date you set) |
| What it's for | The admin dashboard itself, and everything in it | Everything documented on this site: sending events, the MCP server, and any other client integration |
This site is about the second one. If you're building an integration, a CRM sending events, a workflow tool receiving webhooks, an MCP client, you want an API key, not an admin login.
Creating an API key¶
API keys are created by an admin, either from the admin dashboard's
Settings → API keys page or with POST /api/v1/api-keys (itself an
admin-authenticated call: creating a key is an admin action, using one
day-to-day is not).
Every key has:
- A scope list: the specific permissions it grants.
events:writelets the key send events to the events endpoint. This is the default scope for a new key.- The
mcp:*scopes each unlock one tool on AiFlow's MCP server (mcp:calls,mcp:knowledge,mcp:transcripts,mcp:agents,mcp:events,mcp:recall,mcp:custom_tools,mcp:orchestrate).mcp:toolsis a legacy alias for the first three together and still works. - An optional agent scope. A key can be tied to one specific agent,
so it can only act on that agent's behalf, or left unscoped (
agent_idomitted), in which case it works for every agent in the deployment. - An optional expiry date.
const response = await fetch("https://api.your-domain.com/api/v1/api-keys", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${adminToken}`,
},
body: JSON.stringify({
name: "crm-integration",
agent_id: 1,
scopes: ["events:write"],
}),
});
const { key } = await response.json();
The response looks like:
{
"id": 7,
"name": "crm-integration",
"agent_id": 1,
"key_prefix": "af_live_a1b2c",
"scopes": ["events:write"],
"is_active": true,
"last_used_at": null,
"expires_at": null,
"key": "af_live_a1b2c3d4e5f6..."
}
The key field is shown exactly once
AiFlow stores only a salted hash of the key, never the raw value. If
you lose it, there's no way to retrieve it again: create a new key
instead. Everything else in the response (id, key_prefix,
scopes, and so on) stays readable later through
GET /api/v1/api-keys, but key itself does not.
Using an API key¶
Include it as a bearer token on every request:
A request with a missing, invalid, expired, or wrongly-scoped key gets
back a 401 or 403. See
Errors & rate limits for the exact
shape.
Revoking a key¶
curl -X DELETE https://api.your-domain.com/api/v1/api-keys/7 \
-H "Authorization: Bearer $ADMIN_TOKEN"
Revoking a key takes effect immediately: any request already using it
starts failing with 401 on its next call.