---
title: "Create an actor"
method: POST
path: "/v1/projects/{project_id}/actors"
tags: ["Actors"]
---

# Create an actor

`POST /v1/projects/{project_id}/actors`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/actors`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Creates a new actor in the project named in the path.

## Request body

- object
  - `name` string, required
  - `external_id` string: Optional external identifier (e.g. WhatsApp phone number). If provided and an actor with this externalId already exists in the project, the existing actor is returned (idempotent — 200 OK).
  - `instructions` string, nullable: Persona-specific instructions composed into the effective system prompt during conversation generation.
  - `agent_id` string: Agent to link this actor to. Mutually exclusive with chat_id.
  - `chat_id` string: Chat to link this actor to. Mutually exclusive with agent_id.

## Response `200`

Actor already exists — returned when externalId matches an existing actor in this project (idempotent)

- ActorRecord
  - `id` string: Actor ID
  - `project_id` string: Project ID
  - `name` string
  - `external_id` string, nullable: External identifier (e.g. WhatsApp phone number)
  - `instructions` string, nullable: Persona-specific instructions composed into the effective system prompt during conversation generation.
  - `agent_id` string, nullable: Agent this actor is linked to (mutually exclusive with chatId).
  - `chat_id` string, nullable: Chat this actor is linked to (mutually exclusive with agentId).
  - `tags` TagBag: Key-value labels on a resource. A flat object of string values — an array, a nested object or a number is rejected with `400 VALIDATION_FAILED`, never coerced. Keys are opaque and stored verbatim, so `cost_center` and `costCenter` are two different tags. Matched by JSONB containment wherever tags are read: the `?tags=` filter and knowledge search. Keys beginning `system.` are reserved: the platform writes them to record which conversation, actor, agent and role a row came from, and a write naming one is refused with `400 RESERVED_TAG_KEY`. They are read and filtered like any other tag. The bag is bounded, because every pair reaches the IAM context of every access check on the resource: at most 50 keys, each key at most 128 characters and each value at most 256. A write past a bound — including a merge that would grow the stored bag past the key count — is `400 VALIDATION_FAILED` with `meta.limit` naming the bound it crossed. `system.*` keys are the platform's and do not count against the 50.
  - `created_at` string, date-time
  - `updated_at` string, date-time

## Other responses

- `201`: Actor created
- `400`: Invalid request body
- `401`: Unauthorized
- `403`: Forbidden

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
