# Creating and preparing agents

**Availability: Console only**

Agents are created, configured and published in the RelationsAI console. Through the API you read your agents and check whether each one is ready to place calls. This guide covers both halves so your integration only dials with an agent that can actually call.

By the end you will have the `agent_...` id to use in calls and campaigns, and a readiness check you can run before every batch of calls or as part of a deploy.

## Before you begin

| You need | Why |
|---|---|
| Access to the RelationsAI console as a member who can edit agents | Agents are built and published there |
| An API key with `agents:read` | To list agents and check readiness (every new key has it by default) |
| A test key (`rai_test_...`) to start with | Test mode has a built-in Sample agent, so you can follow this guide before your own agent is ready |

## Build the agent in the console

An agent is what the person you call hears: its instructions, voice, language and goal. In the console:

1. **Create the agent** and write its instructions. The instructions must tell the people the agent calls that they are speaking with an AI: an agent without that disclosure cannot be published. Put it at the start, so it is the first thing callers hear.
2. **Choose a voice, language and call settings**, and configure the fields you want extracted from each conversation and the goal the call should achieve. These become the [analysis results](/guides/working-with-analysis-results) of each call.
3. **Assign a phone number for outbound calls.** A call shows this number to the person called. Numbers are added and assigned in the console.
4. **Publish the agent and switch it on.** Calls always use the agent's published version. Editing a draft does not change calls until you publish again.

In test mode the **Sample agent** (`agent_1ZltiWzxrXmMprGD6q63`) and the sample number `+12025550150` are always available and always ready, so every example in these guides works with a brand-new workspace. They do not exist in live mode. See [Test mode](/getting-started/test-mode).

## List agents from the API

[`GET /agents`](/api/agents#list-agents) returns your agents, newest first. Agents are shared configuration: a test key and a live key see the same agents (plus the Sample agent in test mode).

```bash
curl -X GET "https://voice-api.relationsai.com/v1/agents?limit=100" \
     -H "Authorization: Bearer <token>"
```
```js
const response = await fetch("https://voice-api.relationsai.com/v1/agents?limit=100", {
  method: "GET",
  headers: {
    "Authorization": "Bearer <token>",
  },
});
const agents = await response.json();
```
```python

response = requests.get(
    "https://voice-api.relationsai.com/v1/agents",
    headers={
        "Authorization": "Bearer <token>",
    },
    params={
        "limit": 100,
    },
    timeout=30,
)
agents = response.json()
```

Response `200` (one agent shown):

```json
{
  "object": "list",
  "data": [
    {
      "id": "agent_3hT9kQ2mVx7RbN4pLc8Wz1",
      "object": "agent",
      "livemode": false,
      "name": "Loan renewal reminder",
      "description": "Reminds customers that their loan is due for renewal and books a callback.",
      "status": "active",
      "published": true,
      "published_version": 4,
      "is_default": true,
      "sample": false,
      "created_at": "2026-09-02T10:12:44.000Z",
      "updated_at": "2026-10-18T14:03:10.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

Complete runnable example: [cURL script](/examples/guides/creating-and-preparing-agents/list-agents.sh) · [JavaScript](/examples/guides/creating-and-preparing-agents/list-agents.mjs) · [Python](/examples/guides/creating-and-preparing-agents/list-agents.py)

What the fields mean:

| Field | Meaning |
|---|---|
| `status` | `active` agents are switched on. `paused` and `draft` agents cannot place calls. Open enum: treat any other value as "not callable". |
| `published` | `true` when the agent has a published version. `null` means the publication state could not be read right now: use the readiness check for a definite answer. |
| `published_version` | The version calls use, when known. |
| `is_default` | The workspace's default agent in the console. The API never picks an agent for you: `agent_id` is always required when you place a call. |
| `sample` | `true` only for the built-in Sample agent in test mode. |

To read one agent, use [`GET /agents/{agent_id}`](/api/agents#retrieve-an-agent).

## Check readiness

[`GET /agents/{agent_id}/readiness`](/api/agents#check-whether-an-agent-can-place-calls) checks, right now, everything about the agent and the workspace that would stop a call, and returns **every** problem found rather than the first one. The example exits with status 1 when the agent is not ready, so you can use it as a gate in a deploy or before a batch job.

```bash
curl -X GET https://voice-api.relationsai.com/v1/agents/agent_1ZltiWzxrXmMprGD6q63/readiness \
     -H "Authorization: Bearer <token>"
```
```js
const response = await fetch("https://voice-api.relationsai.com/v1/agents/agent_1ZltiWzxrXmMprGD6q63/readiness", {
  method: "GET",
  headers: {
    "Authorization": "Bearer <token>",
  },
});
const readiness = await response.json();
```
```python

response = requests.get(
    "https://voice-api.relationsai.com/v1/agents/agent_1ZltiWzxrXmMprGD6q63/readiness",
    headers={
        "Authorization": "Bearer <token>",
    },
    timeout=30,
)
readiness = response.json()
```

Complete runnable example: [cURL script](/examples/guides/creating-and-preparing-agents/check-readiness.sh) · [JavaScript](/examples/guides/creating-and-preparing-agents/check-readiness.mjs) · [Python](/examples/guides/creating-and-preparing-agents/check-readiness.py)

A ready agent returns `200` with an empty `reasons` list:

```json
{
  "object": "agent_readiness",
  "livemode": true,
  "agent_id": "agent_3hT9kQ2mVx7RbN4pLc8Wz1",
  "ready": true,
  "reasons": [],
  "checked_at": "2026-10-21T09:30:00.000Z"
}
```

An agent that is not ready also returns `200`, with `ready: false` and one entry per problem:

```json
{
  "object": "agent_readiness",
  "livemode": true,
  "agent_id": "agent_7Yx2Wq9Lp4Mn8Bv3Kc6Hd1",
  "ready": false,
  "reasons": [
    { "code": "agent_not_published", "message": "Publish the agent in the console before placing calls." },
    { "code": "no_outbound_number", "message": "Assign a phone number to this agent for outbound calls in the console." }
  ],
  "checked_at": "2026-10-21T09:30:00.000Z"
}
```

Each reason code and what a console admin does about it:

| `reasons[].code` | Meaning | Fix in the console |
|---|---|---|
| `agent_paused` | The agent is switched off | Switch the agent on |
| `agent_not_connected` | The agent has not been saved for calling yet | Save the agent; if the reason stays, contact support |
| `agent_not_published` | No published version, or its live version is paused | Publish the agent |
| `no_outbound_number` | No phone number is assigned to this agent for outbound calls | Assign an outbound number |
| `calling_not_configured` | The workspace has no telephony account or no number | Finish calling setup (Numbers) |
| `telephony_disconnected` | The workspace's telephony connection is not working | Reconnect the telephony account |
| `account_inactive` | The trial ended or the account is suspended | Upgrade, or contact support |
| `spend_limit_reached` | The monthly spend limit set by an admin is reached | Raise the limit |
| `insufficient_balance` | The balance is too low to start a call | Top up the balance |

The list is an open enum: show `message` to an operator for codes you do not recognise. Never parse `message`.

> **Readiness is not a guarantee for a specific number:** Readiness checks the agent and the workspace. It does not check per-destination rules: the do-not-call list, consent, calling hours and the per-number daily limit are applied to each call when it is placed (see [Consent and DNC](/guides/consent-and-dnc)). A ready agent can still be refused for a particular number, and readiness can change at any time, so always handle errors from [`POST /calls`](/api/calls#place-a-call) too.

In test mode the Sample agent is always ready. Your own agents are checked for configuration reasons only (agent and calling setup); balance, spend and account checks apply in live mode.

## Configuring agents through the API

**Availability: Planned** Creating, editing and publishing agents through the API is planned for a later version. Today the workspace's `capabilities.agent_editing` (from [`GET /workspace`](/api/workspace#retrieve-the-workspace)) is `false`; check it rather than assuming. Until then, keep agent changes in the console and treat the API's agent list as read-only configuration.

## What can go wrong

- **Placing a call with an agent that cannot call**: [`agent_not_ready`](/errors/agent_not_ready) (422). `details.reasons` uses the readiness codes above. Fix the agent in the console, then send the same request again (nothing was dialled).
- **The agent id is unknown, malformed, from another workspace, or the Sample agent with a live key**: [`resource_not_found`](/errors/resource_not_found) (404). Use an id from `GET /agents` with a key of the same workspace and mode.
- **The agent has several outbound numbers**: [`phone_number_required`](/errors/phone_number_required) (422). Send `phone_number_id` with the call, or leave one outbound number on the agent.
- **The key lacks `agents:read`**: [`insufficient_scope`](/errors/insufficient_scope) (403). Ask an admin for a key with `agents:read`.

## Next steps

- [Making an outbound call](/guides/making-an-outbound-call) with the agent you just checked.
- [`GET /phone-numbers`](/api/phone-numbers#list-phone-numbers) to choose a caller number for `phone_number_id`.
