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

Agents

An Agent is AiFlow's configured persona: a voice, a base system prompt, a model, concurrency limits, and optionally a phone number, all bundled under one id and slug. This page is the full CRUD reference for managing that resource through the API. For the conceptual explanation of why agent_id shows up in nearly every other endpoint, see Agents & channels. Once an Agent exists, Context & knowledge and Tools cover what to configure on it next.

These are admin actions

Every endpoint on this page needs an admin session token (see Authentication), not an API key. Configuring an Agent's persona, voice, and limits is an admin task. Day-to-day traffic against an already-configured agent runs through Sending events, the widget, and phone calls instead.

Admin role required, per operation

Unlike most CRUD on this site, these four operations don't all require the same role tier. Creating or disabling an agent is more consequential than editing one that already exists, and the role requirements reflect that:

Operation Endpoint Minimum role
List agents GET /api/v1/agents Any logged-in admin (Owner, Admin, Editor, or Viewer)
Get an agent GET /api/v1/agents/{agent_id} Any logged-in admin
Create an agent POST /api/v1/agents Owner or Admin
Update an agent PATCH /api/v1/agents/{agent_id} Owner, Admin, or Editor
Disable an agent DELETE /api/v1/agents/{agent_id} Owner or Admin

A request from a role below the listed minimum returns 403. See Errors & rate limits.

Field reference

These fields apply to both create and update. Differences between the two are called out in the Create and Update columns.

Field Type Create Update Description
slug string Required Not settable URL-safe identifier, must match ^[a-z0-9]+(-[a-z0-9]+)*$ (lowercase letters, digits, single hyphens between segments), 1-80 characters. Immutable once the agent is created; there is no endpoint that renames a slug.
name string Required Optional Display name, 1-120 characters.
description string or null Optional Optional Free-text note, up to 500 characters. Not shown to callers, admin-facing only.
status string Optional (default active) Optional One of draft, active, disabled. draft and disabled agents still exist and can be edited, but see Status and reachability below for what actually reaches them.
voice_name string Optional (default "Kore") Optional The Gemini Live voice this agent speaks with.
model string or null Optional Optional Overrides the platform's default Gemini model for this agent. Leave unset to use the deployment default.
temperature float Optional (default 0.8) Optional Sampling temperature, 0.0 to 2.0.
greeting_instruction string Optional (default "Greet the caller and ask how you can help them.") Optional Up to 500 characters. Instruction for how the agent should open a session, not a literal script.
base_system_prompt string or null Optional Optional The agent's core persona and instructions. Context documents (see Context & knowledge) are appended ahead of and after this, not a replacement for it.
twilio_phone_number string or null Optional Optional E.164 phone number this agent answers inbound calls on. More than one agent can share the same number; see max_concurrent_inbound_calls below.
max_concurrent_inbound_calls integer Optional (default 1) Optional 1 to 50. How many simultaneous inbound calls this agent accepts before it's treated as busy.
allowed_origins list of string or null Optional Optional Origins permitted to embed this agent's widget. null/omitted allows any origin, see Embedding the widget.
max_concurrent_outbound_calls integer Optional (default 3) Optional 1 to 50. Caps how many outbound calls this agent can have in flight at once, independent of the deployment-wide MAX_CONCURRENT_OUTBOUND_CALLS setting.
cross_session_lookback_enabled boolean Optional (default false) Optional When true, the agent recalls its last completed conversation with the same caller, visitor, or email sender, and opens the next one with a short recap. Applies to phone, widget, and a connected mailbox alike.

Status and reachability

status: "active" gates two of the three channels described in Agents & channels: an inbound call to this agent's twilio_phone_number, and a new widget session against its slug. Unless status is exactly active, both are rejected as though the agent didn't exist. draft and disabled agents still exist, remain fully editable, and still appear in GET /api/v1/agents.

The events endpoint does not check status

POST /api/v1/agents/{agent_id}/events (see Sending events) has no status gate. A draft or disabled agent still accepts events, still evaluates its Triggers against them, and still queues an outbound call or runs a silent agent_task on a match. Disabling an agent stops people from reaching it by phone or widget, but it does not stop an upstream system's events from continuing to drive it. To stop that traffic for good, disable or delete the Trigger(s) on that agent, or revoke the API key sending the events, rather than relying on the agent's own status.

The distinction between the two inactive states is purely informational: draft signals "not finished being configured yet," and disabled signals "was active, then turned off." Nothing in the API behaves differently based on which of the two an inactive agent is in.

List agents

GET /api/v1/agents
curl https://api.your-domain.com/api/v1/agents \
  -H "Authorization: Bearer $ADMIN_TOKEN"
import httpx

response = httpx.get(
    "https://api.your-domain.com/api/v1/agents",
    headers={"Authorization": f"Bearer {admin_token}"},
)
response.raise_for_status()
agents = response.json()
const response = await fetch("https://api.your-domain.com/api/v1/agents", {
  headers: { Authorization: `Bearer ${adminToken}` },
});
const agents = await response.json();

Returns every agent in the deployment, ordered by id, regardless of status.

Create an agent

Capped by your licence

This deployment's licence sets an agents limit. Creating one more past that cap returns 402 rather than succeeding; see Licence limits.

POST /api/v1/agents
curl -X POST https://api.your-domain.com/api/v1/agents \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{
        "slug": "support-bot",
        "name": "Support Bot",
        "voice_name": "Kore",
        "base_system_prompt": "You are a friendly support agent for Acme Corp.",
        "greeting_instruction": "Greet the caller warmly and ask how you can help."
      }'
import httpx

response = httpx.post(
    "https://api.your-domain.com/api/v1/agents",
    headers={"Authorization": f"Bearer {admin_token}"},
    json={
        "slug": "support-bot",
        "name": "Support Bot",
        "voice_name": "Kore",
        "base_system_prompt": "You are a friendly support agent for Acme Corp.",
        "greeting_instruction": "Greet the caller warmly and ask how you can help.",
    },
)
response.raise_for_status()
agent = response.json()
const response = await fetch("https://api.your-domain.com/api/v1/agents", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${adminToken}`,
  },
  body: JSON.stringify({
    slug: "support-bot",
    name: "Support Bot",
    voice_name: "Kore",
    base_system_prompt: "You are a friendly support agent for Acme Corp.",
    greeting_instruction: "Greet the caller warmly and ask how you can help.",
  }),
});
const agent = await response.json();

Returns 201 Created with the full agent:

{
  "id": 1,
  "slug": "support-bot",
  "name": "Support Bot",
  "description": null,
  "status": "active",
  "voice_name": "Kore",
  "model": null,
  "temperature": 0.8,
  "greeting_instruction": "Greet the caller warmly and ask how you can help.",
  "base_system_prompt": "You are a friendly support agent for Acme Corp.",
  "twilio_phone_number": null,
  "max_concurrent_inbound_calls": 1,
  "allowed_origins": null,
  "max_concurrent_outbound_calls": 3,
  "cross_session_lookback_enabled": false
}

A slug already in use by another agent, active or not, returns 409.

Get an agent

GET /api/v1/agents/{agent_id}
curl https://api.your-domain.com/api/v1/agents/1 \
  -H "Authorization: Bearer $ADMIN_TOKEN"
import httpx

response = httpx.get(
    "https://api.your-domain.com/api/v1/agents/1",
    headers={"Authorization": f"Bearer {admin_token}"},
)
response.raise_for_status()
agent = response.json()
const response = await fetch("https://api.your-domain.com/api/v1/agents/1", {
  headers: { Authorization: `Bearer ${adminToken}` },
});
const agent = await response.json();

A nonexistent agent_id returns 404.

Update an agent

PATCH /api/v1/agents/{agent_id}

Every field is optional. Send only what you're changing; everything else stays as-is. slug cannot be changed through this endpoint (see the field reference above).

curl -X PATCH https://api.your-domain.com/api/v1/agents/1 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{
        "cross_session_lookback_enabled": true,
        "max_concurrent_inbound_calls": 3
      }'
import httpx

response = httpx.patch(
    "https://api.your-domain.com/api/v1/agents/1",
    headers={"Authorization": f"Bearer {admin_token}"},
    json={
        "cross_session_lookback_enabled": True,
        "max_concurrent_inbound_calls": 3,
    },
)
response.raise_for_status()
agent = response.json()
const response = await fetch("https://api.your-domain.com/api/v1/agents/1", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${adminToken}`,
  },
  body: JSON.stringify({
    cross_session_lookback_enabled: true,
    max_concurrent_inbound_calls: 3,
  }),
});
const agent = await response.json();

Returns the full updated agent, in the same shape as create.

Disable an agent

DELETE /api/v1/agents/{agent_id}
curl -X DELETE https://api.your-domain.com/api/v1/agents/1 \
  -H "Authorization: Bearer $ADMIN_TOKEN"
import httpx

response = httpx.delete(
    "https://api.your-domain.com/api/v1/agents/1",
    headers={"Authorization": f"Bearer {admin_token}"},
)
response.raise_for_status()
const response = await fetch("https://api.your-domain.com/api/v1/agents/1", {
  method: "DELETE",
  headers: { Authorization: `Bearer ${adminToken}` },
});

Returns 204 No Content.

This is a soft-disable, not a deletion

DELETE sets status to disabled. It does not remove the agent row, and it does not touch any Call, Event, context document, or tool configuration attached to it. History stays intact and queryable; the agent simply stops being reachable by phone, widget, or outbound trigger (see Status and reachability above). There is no separate hard-delete endpoint. To bring the agent back, PATCH its status back to active.

Next

  • Context & knowledge: upload documents and URLs the agent can draw on.
  • Tools: enable built-in capabilities like send_webhook or search_knowledge_base.
  • Agents & channels: the conceptual model behind why agent_id and slug show up in almost every other endpoint on this site.