AI agents

An AI agent is a voice assistant that talks to callers. The same agent can answer a phone line (AI receptionist), place AI calls (Send an AI call), and run in campaigns. It answers from your knowledge bases and speaks with any of your voices.

API keys work on every route. An OAuth token issued for a user also needs that user's role to allow managing AI agents (see Role denials).

Routes

Method Route Scope What it does
GET /agents/public/templates agents:read List templates
POST /agents/public/agents/from-template agents:write Create an agent from a template
GET /agents/public/agents agents:read List your agents
POST /agents/public/agents agents:write Create an agent from scratch
GET /agents/public/agents/{agent_id} agents:read Get an agent
POST /agents/public/agents/{agent_id} agents:write Update an agent
POST /agents/public/agents/{agent_id}/publish agents:write Publish an agent
DELETE /agents/public/agents/{agent_id} agents:write Delete an agent

Build order

An AI receptionist is an agent answering one of your phone lines. Each step needs an id from the one before:

  1. Knowledge base. Create one and add your hours, services and FAQs as documents. Wait until each document's ingest_status is ready, then test it with a query. See Knowledge bases.
  2. Voice. Pick a voice_id from GET /voice/public/voices, or clone your own.
  3. Agent. Create it from a template, with the voice and think.knowledge pointing at your knowledge base.
  4. Publish. Calls use the last published version, never the working copy.
  5. Phone line. Create a line that sends calls to the agent and assign a number to it. See AI receptionist.
  6. Test. Call the number, then watch for the call results.

For outbound calls, skip the phone line and pass the published agent's agent_id to POST /ai-broadcast (see Send an AI call).

List templates

Templates give you a working prompt, tools and defaults to start from.

Field Type Required Description
direction string No inbound or outbound.
category string No For example scheduling, sales or support.
curl "https://api-v2.dropcowboy.com/agents/public/templates?direction=inbound" \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
{
  "data": {
    "templates": [
      {
        "template_id": "inbound-appointment-setting",
        "name": "Appointment setting",
        "description": "Book callback appointments and capture preferred date, time, and contact details.",
        "direction": "inbound",
        "category": "scheduling"
      }
    ]
  },
  "meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}

Start from a template

Field Type Required Description
template_id string Yes From the template list.
name string Yes The agent's name.
description string No A note for your team.
active boolean No Whether the agent takes calls.
curl -X POST https://api-v2.dropcowboy.com/agents/public/agents/from-template \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "content-type: application/json" \
  -d '{
    "template_id": "inbound-appointment-setting",
    "name": "Front desk",
    "active": true
  }'

The route answers 201 with the agent. The template's prompt, tools and defaults are copied into it, so later changes to the template don't affect it. An unknown template_id answers 404.

List your agents

Field Type Required Description
search_term string No Matches the agent name.
limit integer No Page size. Default 50.
curl "https://api-v2.dropcowboy.com/agents/public/agents?search_term=front" \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"

The response carries data.agents, an array of agents, and data.total.

Create an agent from scratch

Field Type Required Description
name string Yes The agent's name.
description string No A note for your team.
voice_id string No From GET /voice/public/voices, including your clones.
language string No For example en.
mode string No Stored as defaults.mode.
think object No Prompt, tools and knowledge. See The think object.
routing_targets array No Where the agent may transfer calls.
contact_tools object No Which contact fields the agent may read and write.
identity object No Caller identity checks.
defaults object No Behaviour defaults.
active boolean No Default true.
curl -X POST https://api-v2.dropcowboy.com/agents/public/agents \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "content-type: application/json" \
  -d '{
    "name": "Front desk",
    "voice_id": "7d2a9e4b-1c6f-4b3a-8e5d-2f9c7a1b4e63",
    "language": "en",
    "think": {
      "prompt_spec": {
        "role": "Receptionist for Example Dental",
        "tone": "Warm and brief",
        "objectives": ["Answer questions about hours and location", "Book cleanings"],
        "intake_fields": ["first_name", "email"]
      }
    }
  }'

The route answers 201 with the agent, including its agent_id.

The think object

think holds how the agent reasons and what it can do.

Key Purpose
prompt_spec A structured prompt: role, tone, objectives, intake_fields, prohibitions, business_context, additional_instructions. Drop Cowboy® compiles it into the system prompt, including guidance for the agent's tools and knowledge bases.
llm_instructions The system prompt itself. It's generated for you when prompt_spec is set, so write one or the other. Placeholders such as {{contact.first_name}} are filled on each call.
llm_functions HTTP tools the agent can call during a conversation: name, description, url, method, headers, and a JSON Schema in parameters.
mcp_servers MCP servers the agent can use: name, transport (streamable-http or sse), url, headers.
call_tools Built-in call controls such as hangup and send_dtmf.
knowledge Knowledge bases to search. See Attach to an agent.

Get an agent

curl https://api-v2.dropcowboy.com/agents/public/agents/4c8e2a6f-3b9d-4e1a-9c7b-5d2f8a3e6b19 \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
{
  "data": {
    "agent_id": "4c8e2a6f-3b9d-4e1a-9c7b-5d2f8a3e6b19",
    "team_id": "3f6c2a1e-8b4d-4c7a-9e2f-5a1b3c4d6e7f",
    "name": "Front desk",
    "description": null,
    "active": true,
    "is_draft": false,
    "voice_id": "7d2a9e4b-1c6f-4b3a-8e5d-2f9c7a1b4e63",
    "language": "en",
    "think": { "prompt_spec": { "role": "Receptionist for Example Dental" } },
    "defaults": { "mode": "receptionist" },
    "published_version": 3,
    "published_at": 1774041600000,
    "created_at": 1773955200000
  },
  "meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}

published_version and published_at are null until the first publish. Timestamps are epoch milliseconds. A draft (is_draft: true) can't take calls.

Update an agent

Send only the fields you want to change. The fields are those of Create an agent from scratch, plus these:

Field Type Required Description
asr_enabled boolean No Turns the live call transcript on or off. Only JSON true turns it on; any other value, including the string "true", turns it off. Leave it out to keep the current setting.
post_call_automation_flow_ids array No Automation flows to run after each call.

Changes reach calls only after you publish. The one exception is active: false, which stops the agent taking new calls immediately.

Updating think replaces the whole object. If you send only {"think": {"knowledge": {...}}}, the agent loses its prompt and tools. Read the agent first, change the keys you need, and send the complete think back:

const base = 'https://api-v2.dropcowboy.com/agents/public/agents/' + agentId;
const headers = {
  'x-key': process.env.DC_KEY,
  'x-secret': process.env.DC_SECRET,
  'content-type': 'application/json'
};

const { data: agent } = await (await fetch(base, { headers })).json();
const think = Object.assign({}, agent.think, {
  knowledge: {
    mode: 'selected',
    knowledge_base_ids: ['9d3f6a2c-4e8b-4b1d-a7c5-2f6e9b3d8a14'],
    audience: 'public'
  }
});

await fetch(base, { method: 'POST', headers, body: JSON.stringify({ think }) });
await fetch(base + '/publish', { method: 'POST', headers });

Publish an agent

Publishing freezes the working copy as a new version. Phone lines, AI calls and campaigns use the latest published version.

curl -X POST https://api-v2.dropcowboy.com/agents/public/agents/4c8e2a6f-3b9d-4e1a-9c7b-5d2f8a3e6b19/publish \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"

The response is the agent with its new published_version and published_at. Publishing checks the agent first, and answers 400 with the reason in details.code (see Errors) when:

  • a tool definition is invalid;
  • the agent references a tool connection or automation flow your account no longer has;
  • a selected knowledge base reads like instructions for your staff rather than answers for callers.

Fix the problem and publish again.

Delete an agent

curl -X DELETE https://api-v2.dropcowboy.com/agents/public/agents/4c8e2a6f-3b9d-4e1a-9c7b-5d2f8a3e6b19 \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"

AI calls queued for the agent are cancelled. Point any phone line that uses it at another agent first.

Call results

Subscribe to these webhooks to learn how calls went. ai_agent.call.started fires when a call connects to the agent. Then exactly one of these fires:

Event When
ai_agent.call.failed The call failed, for example nobody answered.
ai_agent.outcome.* The agent recorded an outcome: confirm, opt_out, transfer, voicemail, incomplete, timeout or identity_failed.
ai_agent.call.completed Any other answered call.

An inbound call looks like this. The payload also carries team, call and contact objects, left out here:

{
  "event_id": "d1f3a8e2-7c4b-4f9a-9d22-9c1e2f3a4b5c",
  "event": "ai_agent.call.completed",
  "event_at": 1774041600000,
  "data": {
    "team_id": "3f6c2a1e-8b4d-4c7a-9e2f-5a1b3c4d6e7f",
    "agent_id": "4c8e2a6f-3b9d-4e1a-9c7b-5d2f8a3e6b19",
    "call_id": "4e8c2a6d-1f9b-4d3e-a5c7-2b6f8d4a9e31",
    "contact_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
    "ivr_id": "e2b6f9a3-5c1d-4e8b-a4f7-9c3e1b5d7a28",
    "outcome": null,
    "answered": true,
    "duration": 184.32,
    "direction": "inbound"
  }
}

ivr_id is the line_id of the phone line that was called. duration is in seconds. Use call_id with GET /phone/public/calls/{call_id} and GET /phone/public/calls/{call_id}/recording for details and the recording (see Call history).

An AI call you send has no direction, agent_id or call_id. Instead it carries campaign_id, session_id, drop_id, phone_number and campaign_session, plus ai_duration_sec, ai_cost, transcript_length and function_calls_count. Its outcome can also be voicemail_delivered, no_response or a failure reason such as no_answer. Each contact's delivery result also arrives on contact.rvm.status with campaign_type: ai_broadcast.

Errors

The errors specific to agents are below. For the error format, 402 and everything else, see Responses, errors and limits.

When a rejection has a specific reason, it's in details.code:

Status details.code Cause What to do
400 invalid_agent_tools A tool definition is invalid. Fix the tool in think.llm_functions or think.mcp_servers.
400 invalid_handoff_targets A transfer target is invalid. Fix routing_targets.
400 unusable_knowledge_base A selected knowledge base reads like staff instructions. Rewrite its documents as answers for callers, or deselect it.
400 unresolved_agent_references A tool connection or automation flow no longer exists. Remove the reference or recreate what it points at.
400 unresolved_voice_flow, voice_compile_error The agent's voice script isn't published, or doesn't compile. Open the agent in the dashboard, fix its voice script and publish it.
409 stale_published_version Another publish finished first. Read the agent and publish again.

Branch on status and details.code, not on detail. A detail of 200 characters or more is replaced by a generic message such as Invalid request.

Agents that Drop Cowboy manages for your account aren't listed. Getting, updating, publishing or deleting one answers 404, the same as an unknown agent_id.

Role denials

API keys are never limited by role. A token issued for a signed-in user is checked against that user's role. A denial is a 403 with type ending forbidden and one of these detail values:

detail Cause
You do not have permission to manage AI Agents. The role has no AI agent access. Applies to any agent route.
Not authorized to perform this AI chat action The role may not create, change, delete or publish agents.

Ask an account admin to change the user's role, or use an API key.

{
  "type": "https://api-v2.dropcowboy.com/errors/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "You do not have permission to manage AI Agents.",
  "instance": "/agents/public/agents",
  "request_id": "8a2c4e6f-0b1d-4f3a-9c5e-7d9b1f3a5c84"
}

If the role can't be looked up, the answer is 500. Retry.

Code samples

The requests from this page, ready to copy. Set DC_KEY and DC_SECRET to your API key pair first.

List templates
curl "https://api-v2.dropcowboy.com/agents/public/templates?direction=inbound" \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
Start from a template
curl -X POST https://api-v2.dropcowboy.com/agents/public/agents/from-template \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "content-type: application/json" \
  -d '{
    "template_id": "inbound-appointment-setting",
    "name": "Front desk",
    "active": true
  }'
List your agents
curl "https://api-v2.dropcowboy.com/agents/public/agents?search_term=front" \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
Create an agent from scratch
curl -X POST https://api-v2.dropcowboy.com/agents/public/agents \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "content-type: application/json" \
  -d '{
    "name": "Front desk",
    "voice_id": "7d2a9e4b-1c6f-4b3a-8e5d-2f9c7a1b4e63",
    "language": "en",
    "think": {
      "prompt_spec": {
        "role": "Receptionist for Example Dental",
        "tone": "Warm and brief",
        "objectives": ["Answer questions about hours and location", "Book cleanings"],
        "intake_fields": ["first_name", "email"]
      }
    }
  }'
Get an agent
curl https://api-v2.dropcowboy.com/agents/public/agents/4c8e2a6f-3b9d-4e1a-9c7b-5d2f8a3e6b19 \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
Update an agent
const base = 'https://api-v2.dropcowboy.com/agents/public/agents/' + agentId;
const headers = {
  'x-key': process.env.DC_KEY,
  'x-secret': process.env.DC_SECRET,
  'content-type': 'application/json'
};

const { data: agent } = await (await fetch(base, { headers })).json();
const think = Object.assign({}, agent.think, {
  knowledge: {
    mode: 'selected',
    knowledge_base_ids: ['9d3f6a2c-4e8b-4b1d-a7c5-2f6e9b3d8a14'],
    audience: 'public'
  }
});

await fetch(base, { method: 'POST', headers, body: JSON.stringify({ think }) });
await fetch(base + '/publish', { method: 'POST', headers });
Publish an agent
curl -X POST https://api-v2.dropcowboy.com/agents/public/agents/4c8e2a6f-3b9d-4e1a-9c7b-5d2f8a3e6b19/publish \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
Delete an agent
curl -X DELETE https://api-v2.dropcowboy.com/agents/public/agents/4c8e2a6f-3b9d-4e1a-9c7b-5d2f8a3e6b19 \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"