Skip to content
MeridFlow AiFlow v8.x • self-hosted

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:write lets 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:tools is 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_id omitted), in which case it works for every agent in the deployment.
  • An optional expiry date.
curl -X POST https://api.your-domain.com/api/v1/api-keys \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{
        "name": "crm-integration",
        "agent_id": 1,
        "scopes": ["events:write"]
      }'
import httpx

response = httpx.post(
    "https://api.your-domain.com/api/v1/api-keys",
    headers={"Authorization": f"Bearer {admin_token}"},
    json={
        "name": "crm-integration",
        "agent_id": 1,
        "scopes": ["events:write"],
    },
)
response.raise_for_status()
key = response.json()["key"]
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:

curl https://api.your-domain.com/api/v1/agents/1/events \
  -H "Authorization: Bearer af_live_a1b2c3d4e5f6..."
import httpx

client = httpx.Client(
    base_url="https://api.your-domain.com",
    headers={"Authorization": f"Bearer {api_key}"},
)
const headers = {
  Authorization: `Bearer ${apiKey}`,
  "Content-Type": "application/json",
};

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.