> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.v2.rasen.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.v2.rasen.ai/_mcp/server.

# Create an agent

POST https://api.v2.rasen.ai/v1/agents
Content-Type: application/json

Creates identity and an optional initial draft in one atomic transaction; any refused initial field leaves no agent and no idempotency claim. The operation does not publish, assign a number or call anyone. Model readiness remains separate. Authored tools, knowledge, variables and credentials use their separate operations.

Reference: https://docs.v2.rasen.ai/api-reference/agents/create

## Authentication

- `Authorization` header (bearer token, required) — Create and manage workspace-owned keys through the existing app session routes, outside this public API. Creator and manager identities are audit actors only. Key validity follows workspace policy, expiry and explicit revocation, independently of creator membership. Do not include a workspace-selection header.

## Request

### Headers

- `Idempotency-Key` (string, required) — Stable key per logical intent, 1–255 printable characters. Persist it before sending. Retry the same key/body after an uncertain result for 24 hours. A changed body conflicts. Beyond retention, reconcile the resource before retrying; this is not permanent deduplication. Current authorization is rechecked on replay. Evaluation order: authenticate and check current authorization; look up intent; replay its stored receipt before checking the current revision; only a new intent evaluates If-Match and executes. Fingerprint includes method, path, body and original If-Match. A changed request returns idempotency_key_reused. The server stores only a keyed (HMAC) fingerprint, and no error or response ever returns a fingerprint or hash of a request. In-flight intent returns 409 idempotency_in_progress with Retry-After; retry the same intent after reconciliation. Receipts describe that attempt, not current resource state. A replay returns the original status code and body, with the response header Idempotent-Replayed: true.

### Body (application/json)

This endpoint expects an AgentCreate.

- `name` (string, required)
- `description` (string, optional)
- `resource_key` (string, optional) — Optional caller-chosen resource key: an opaque external identifier that matches your own record (for example a CRM ID, a configuration-file ID or a UUID) to this resource. Rasen still generates the canonical id; use id in paths and references. Case-sensitive, compared exactly, never normalized or parsed. 1-128 characters from A-Z a-z 0-9 . _ : ~ - and starting with a letter or digit; UUIDs and ULIDs are valid. Unique per resource type within one workspace; separate environment workspaces can reuse the same key. Set only at creation and immutable afterwards. A create with a key that already exists returns 409 resource_key_conflict with details.existing_id; it never updates or returns the existing resource. Not an Idempotency-Key: that header identifies one request intent for 24 hours, and a same-intent replay returns its original receipt before the resource-key check runs. Uniqueness includes archived resources. A key becomes free again only after its resource is permanently deleted or erased (a deleted secret or a deleted knowledge source); archive never frees it.
- `initial_draft` (AgentInitialDraft, optional) — Optional initial draft configuration stored atomically with the new agent. It remains a draft. Knowledge, authored tools, variables, credentials and publication use their separate operations.

## Response

### 201

Create an agent.

- `id` (string, required) — Opaque agent identifier. Do not parse its contents.
- `name` (string, required)
- `description` (string, required, nullable)
- `state` (string, required) — draft: never published. live: admits calls. paused: refuses new calls but keeps configuration and history. archived: terminal; hidden from listAgents by default, readable by ID, and draft edits, publication, rollback and call start are refused. Open response vocabulary. Preserve unknown values; never assume they mean success.
- `live_version_id` (string, required, nullable)
- `created_at` (datetime, required)
- `updated_at` (datetime, required)
- `resource_key` (string, required, nullable) — Optional caller-chosen resource key: an opaque external identifier that matches your own record (for example a CRM ID, a configuration-file ID or a UUID) to this resource. Rasen still generates the canonical id; use id in paths and references. Case-sensitive, compared exactly, never normalized or parsed. 1-128 characters from A-Z a-z 0-9 . _ : ~ - and starting with a letter or digit; UUIDs and ULIDs are valid. Unique per resource type within one workspace; separate environment workspaces can reuse the same key. Set only at creation and immutable afterwards. A create with a key that already exists returns 409 resource_key_conflict with details.existing_id; it never updates or returns the existing resource. Not an Idempotency-Key: that header identifies one request intent for 24 hours, and a same-intent replay returns its original receipt before the resource-key check runs. Uniqueness includes archived resources. A key becomes free again only after its resource is permanently deleted or erased (a deleted secret or a deleted knowledge source); archive never frees it.

## Errors

### 400 Bad Request Error

Invalid input or cursor. Correct the request.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 401 Unauthorized Error

Invalid, expired or revoked API key. Creator departure or membership changes do not invalidate a workspace key.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 403 Forbidden Error

Operation not allowed by current credential or workspace access.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 409 Conflict Error

Conflict. Not retryable: read the current state, fix the request and send a new request. The one exception is idempotency_in_progress, which carries Retry-After: wait, then resend the identical request with the same Idempotency-Key. The operation lists its codes in x-error-codes; branch on error.code and, for conflict, error.details.reason.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 413 Content Too Large Error

Body or upload exceeds its allowed limit.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 429 Too Many Requests Error

Rate limited. Respect Retry-After and retry the same intent where permitted.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 500 Internal Server Error

Internal error. The effect may have happened. Retry the same request with the same Idempotency-Key; read the resource before creating a new intent. For an operation that takes an Idempotency-Key, a client retry with the same key is safe: the server replays the stored outcome if the first attempt took effect; if the first attempt had no effect, the retry performs the operation once. Never retry with a new key.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

### 503 Service Unavailable Error

Service unavailable. Respect Retry-After where present. For an operation that takes an Idempotency-Key, a client retry with the same key is safe: the server replays the stored outcome if the first attempt took effect; if the first attempt had no effect, the retry performs the operation once. Never retry with a new key.

- `error` (ErrorResponseError, required) — Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

## Types

### AgentInitialDraft

Optional initial draft configuration stored atomically with the new agent. It remains a draft. Knowledge, authored tools, variables, credentials and publication use their separate operations.

- `prompt` (string, optional)
- `voice` (VoiceSelection, optional)
- `settings` (AgentInitialSettings, optional) — Initial values for these named settings. Absent or nullable fields use their platform default or stay unset. No serving-model, checkpoint, temperature or campaign retry knobs. The draft excludes inactive noise-suppression controls.
- `default_tools` (DefaultToolsPatch, optional) — Per-key merge: each policy you include merges field by field; policies you omit stay unchanged. New built-in policies may be added later; older clients that omit them keep them unchanged. end_call is enabled by default for every agent; only an explicit end_call.enabled=false turns it off. transfer starts disabled with a null destination.

### ErrorResponseError

Only code and message are required. Rasen sends details, request_id and retry on every error, but clients must parse an error that lacks them, so a typed error is never lost to a parsing failure.

- `code` (string, required) — Stable code. Branch on code (and details.reason for conflict), never on message. Each code has one HTTP status; see components.x-error-catalog.
- `message` (string, required) — Safe description; no provider payload or submitted secrets.
- `details` (map from string to any, optional) — Safe code-specific details. The public fields for each code are listed in components.x-error-catalog. Details never contain a request fingerprint, a hash of a request, secret material or another tenant's data. forbidden may include required_capabilities (a list).
- `request_id` (string, optional) — Same value as the X-Request-Id header. Quote it to support.
- `retry` (string, optional) — Retry class of this code: no (fix the request or state first), yes (retry the same request and the same Idempotency-Key), retry_after (wait for Retry-After, then retry the same request). Open response vocabulary. Preserve unknown values; never assume they mean success.

### VoiceSelection

- `voice_id` (string, required, nullable) — Choose from GET /voices. Null explicitly resets to the supported default.

### AgentInitialSettings

Initial values for these named settings. Absent or nullable fields use their platform default or stay unset. No serving-model, checkpoint, temperature or campaign retry knobs. The draft excludes inactive noise-suppression controls.

- `purpose` (string, optional, nullable) — What the agent is for. Null or omission leaves it unset at creation.
- `goals` (list of string, optional, nullable) — Ordered goals for the agent. An empty array, null or omission starts with no goals.
- `call_direction` (enum, optional, nullable) — The intended call direction. Null or omission leaves it unset at creation.
  - Allowed values: `inbound`, `outbound`
- `grammatical_gender` (enum, optional, nullable) — The grammatical gender used for language generation and compatible voice selection. Null or omission leaves it unset at creation.
  - Allowed values: `female`, `male`
- `additional_context` (string, optional, nullable) — Additional bounded context for the agent. Null or omission leaves it unset at creation.
- `language` (enum, optional) — Create-time language. Use one of the three supported values; omission keeps the platform default.
  - Allowed values: `english`, `hindi`, `hinglish`
- `agent_name` (string, optional, nullable)
- `first_message` (string, optional)
- `timezone` (string, optional) — IANA timezone.
- `max_call_duration_seconds` (integer, optional, nullable) — Platform default on create/reset: 1800; omission in PATCH keeps the existing value.
- `inactivity_timeout_seconds` (integer, optional, nullable) — Platform default on create/reset: 30; omission in PATCH keeps the existing value.
- `user_turn_timeout_ms` (integer, optional, nullable)
- `ivr_hangup` (boolean, optional)
- `voicemail_detection` (boolean, optional)
- `dtmf_enabled` (boolean, optional)
- `dtmf_timeout_seconds` (integer, optional)
- `dtmf_digit_limit` (integer, optional)
- `dtmf_termination_key` (string, optional, nullable)
- `bg_audio` (enum, optional, nullable)
  - Allowed values: `office`, `call_centre`, `cafe`
- `bg_volume` (integer, optional)
- `extraction_fields` (list of ExtractionField, optional)

### DefaultToolsPatch

Per-key merge: each policy you include merges field by field; policies you omit stay unchanged. New built-in policies may be added later; older clients that omit them keep them unchanged. end_call is enabled by default for every agent; only an explicit end_call.enabled=false turns it off. transfer starts disabled with a null destination.

- `end_call` (EndCallPolicyPatch, optional) — Only enable or disable. Name, prompt, description and parameters are not editable. Independent duration limits and external hangup still apply. Default enabled=true; only an explicit false disables it. In a PATCH, fields merge one by one: an omitted field keeps its value.
- `transfer` (TransferPolicyPatch, optional) — Enabled requires a destination_number. Disabled may retain a destination for later use. Fixed E164 transfer only in this version. In a PATCH, fields merge one by one: an omitted field keeps its value. The merged result must still pass the check that an enabled transfer has a destination_number.

### ExtractionField

Names are unique. choices is required only for selector. required is an extraction instruction, not a guarantee the caller supplies the fact. Missing evidence must not invent a value.

- `name` (string, required)
- `type` (enum, required)
  - Allowed values: `text`, `boolean`, `number`, `selector`, `datetime`
- `description` (string, optional)
- `required` (boolean, optional)
- `choices` (list of string, optional)

### EndCallPolicyPatch

Only enable or disable. Name, prompt, description and parameters are not editable. Independent duration limits and external hangup still apply. Default enabled=true; only an explicit false disables it. In a PATCH, fields merge one by one: an omitted field keeps its value.

- `enabled` (boolean, optional)

### TransferPolicyPatch

Enabled requires a destination_number. Disabled may retain a destination for later use. Fixed E164 transfer only in this version. In a PATCH, fields merge one by one: an omitted field keeps its value. The merged result must still pass the check that an enabled transfer has a destination_number.

- `enabled` (boolean, optional)
- `destination_number` (string, optional, nullable)

## Examples

**Request**

```json
{
  "name": "Appointment reminders",
  "description": "Confirms next-day appointments.",
  "resource_key": "crm:agent-appointments"
}
```

**Response**

```json
{
  "id": "agent_01j00000000000000000000000",
  "name": "example",
  "description": "example",
  "state": "draft",
  "live_version_id": "live_version_01j00000000000000000000000",
  "created_at": "2026-09-25T10:00:00Z",
  "updated_at": "2026-09-25T10:06:00Z",
  "resource_key": "3f8e5a0c-9d1b-4c7e-a2f4-6b0d9e1c7a55"
}
```

**SDK Code**

```python agents_create_example
import requests

url = "https://api.v2.rasen.ai/v1/agents"

payload = {
    "name": "Appointment reminders",
    "description": "Confirms next-day appointments.",
    "resource_key": "crm:agent-appointments"
}
headers = {
    "Idempotency-Key": "intent-appointment-001",
    "Authorization": "Bearer <api_key>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript agents_create_example
const url = 'https://api.v2.rasen.ai/v1/agents';
const options = {
  method: 'POST',
  headers: {
    'Idempotency-Key': 'intent-appointment-001',
    Authorization: 'Bearer <api_key>',
    'Content-Type': 'application/json'
  },
  body: '{"name":"Appointment reminders","description":"Confirms next-day appointments.","resource_key":"crm:agent-appointments"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```ruby agents_create_example
require 'uri'
require 'net/http'

url = URI("https://api.v2.rasen.ai/v1/agents")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = 'intent-appointment-001'
request["Authorization"] = 'Bearer <api_key>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"name\": \"Appointment reminders\",\n  \"description\": \"Confirms next-day appointments.\",\n  \"resource_key\": \"crm:agent-appointments\"\n}"

response = http.request(request)
puts response.read_body
```

```java agents_create_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.v2.rasen.ai/v1/agents")
  .header("Idempotency-Key", "intent-appointment-001")
  .header("Authorization", "Bearer <api_key>")
  .header("Content-Type", "application/json")
  .body("{\n  \"name\": \"Appointment reminders\",\n  \"description\": \"Confirms next-day appointments.\",\n  \"resource_key\": \"crm:agent-appointments\"\n}")
  .asString();
```

```php agents_create_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.v2.rasen.ai/v1/agents', [
  'body' => '{
  "name": "Appointment reminders",
  "description": "Confirms next-day appointments.",
  "resource_key": "crm:agent-appointments"
}',
  'headers' => [
    'Authorization' => 'Bearer <api_key>',
    'Content-Type' => 'application/json',
    'Idempotency-Key' => 'intent-appointment-001',
  ],
]);

echo $response->getBody();
```

```csharp agents_create_example
using RestSharp;

var client = new RestClient("https://api.v2.rasen.ai/v1/agents");
var request = new RestRequest(Method.POST);
request.AddHeader("Idempotency-Key", "intent-appointment-001");
request.AddHeader("Authorization", "Bearer <api_key>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"name\": \"Appointment reminders\",\n  \"description\": \"Confirms next-day appointments.\",\n  \"resource_key\": \"crm:agent-appointments\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift agents_create_example
import Foundation

let headers = [
  "Idempotency-Key": "intent-appointment-001",
  "Authorization": "Bearer <api_key>",
  "Content-Type": "application/json"
]
let parameters = [
  "name": "Appointment reminders",
  "description": "Confirms next-day appointments.",
  "resource_key": "crm:agent-appointments"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.v2.rasen.ai/v1/agents")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```