---
title: Create an agent
url: https://developers.wpengine.com/docs/ai/api/reference/operations/createagent/
image: https://developers.wpengine.com/og/docs/ai/api/reference/operations/createagent.png
---

# Create an agent

POST

/v1/agents

Select code sample

```php
<?php


$client = new \GuzzleHttp\Client();


$response = $client->request('POST', 'https://api.ai.wpengine.com/v1/agents', [
  'body' => '{ "name": "Site Concierge", "model": "anthropic/claude-sonnet-4-6", "profile": "example", "guardrails": { "moderation": "disabled" }, "description": "example", "system_prompt": "example", "project_id": "example", "temperature": 1, "max_tokens": 1, "reasoning_effort": "example", "scopes": [ "example" ], "settings": {}, "mcp_servers": [ {} ], "tools": [ { "type": "custom", "name": "example", "description": "example", "input_schema": {}, "label": "example", "defer_loading": false } ], "skills": [ {} ], "metadata": { "additionalProperty": "example" } }',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);


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

- Current environment

Creates a managed agent under `project_id`, which must be a project the caller is authorized for; when omitted, a site caller uses the project its registration is bound to, and otherwise the account’s project is used, with an account with more than one project using the one named “Default project”, or its oldest when none has that name. The agent stores the model, optional system prompt, inference parameters, knowledge-base collections, MCP servers, tools, skills, scopes, and feature settings (e.g. `disable_kb_rag`). The model must support the `tools` capability; models without this capability return HTTP 400. Accepts a service-token bearer authenticating a site (the project must belong to the site’s account) or an API key scoped to `project_id` — an API key’s own project must match exactly. Multiple agents in the same project may share a display name; identity is keyed by `id`.

## Authorizations

- **[bearerAuth](https://developers.wpengine.com/docs/ai/api/reference/#bearerauth)**

## Request Bodyrequired

Media typeapplication/json

object

**name**

required

Human-readable agent name.

string

<= 255 characters

### Example

```
Site Concierge
```

**model**

Identifier of the model that powers the agent (e.g. `anthropic/claude-sonnet-4-6`). Required unless `profile` is set.

string

<= 255 characters

### Example

```
anthropic/claude-sonnet-4-6
```

**profile**

Profile that owns some of the agent’s fields, listed in the response’s `managed_fields`. Owned fields cannot be written; `model` may be set to one of the profile’s `allowed_models`, and when omitted the profile’s default model is used. Reserved for WP Engine first-party clients: API-key callers cannot set it. Current product profiles include `chatbot-v1`, `standalone-coworker-v1`, and `power-coworker-v1`.

string

<= 64 characters

**guardrails**

object

**moderation**

Best-effort custom-agent moderation setting. Product profiles own this value: writes are rejected and the field is omitted from responses for profiled agents.

string

default: disabled

Allowed values: disabled enabled

**description**

Free-form description of what the agent does.

string

<= 4096 characters

**system_prompt**

User prompt template (site prompt) prepended to every conversation powered by this agent.

string

<= 32768 characters

**project_id**

The project the agent belongs to. Optional: when omitted, a site caller uses the project its registration is bound to; otherwise the account’s project is used — created for an account that has none — and an account with more than one project uses the one named “Default project”, or its oldest when none has that name. For a service-token caller authenticating a site, the project must be owned by the same account that owns the site. For an API key, it must match the key’s own project exactly.

string

**temperature**

Sampling temperature applied to chat completions.

number format: float

<= 2

**max_tokens**

Hard upper bound on output tokens per agent turn, forwarded to the model provider as max_tokens on each turn. Generation stops when the limit is reached; the response may be truncated mid-content rather than rejected. On reasoning-capable models (see capabilities on GET /v1/models), internal reasoning tokens may count against this budget alongside visible output. Omitted values leave the limit unset so provider-specific defaults apply (Anthropic defaults to 8192 when unset; OpenAI-compatible routes omit the field). Compare with the model’s max_output_tokens on GET /v1/models, which is the catalog ceiling for a single response. Values above the catalog-wide maximum are rejected with 400. When a turn exhausts this budget, any partial agent.message is followed by session.error with error_code max_tokens rather than being treated as a complete reply.

integer

\>= 1 <= 200000

**reasoning_effort**

Catalog-native reasoning effort for this agent’s model. Allowed values are the model’s `parameters.reasoning_effort` on GET /v1/models. Omit to use the provider default. Models that do not publish an enum reject any non-empty value.

string

**scopes**

Permission scopes the agent operates under. Must be drawn from the site’s existing scopes (e.g. `chat`, `search`).

Array\<string>

**settings**

Feature toggles for the agent (e.g. `disable_kb_rag: true` to skip knowledge-base retrieval). Capped at 32 keys and 4 KiB serialized.

object

<= 32 properties

**_key_**

additional properties

any

**mcp_servers**

MCP server definitions the agent may connect to.

Array\<object>

<= 20 items

object

**_key_**

additional properties

any

**tools**

Tool declarations available to the agent. See `AgentTool` for the per-entry contract.

Array

<= 128 items

One of: discriminator: type

- [object](https://developers.wpengine.com/docs/ai/api/reference/operations/createagent/#tab-panel-0-0)
- [object](https://developers.wpengine.com/docs/ai/api/reference/operations/createagent/#tab-panel-0-1)
- [object](https://developers.wpengine.com/docs/ai/api/reference/operations/createagent/#tab-panel-0-2)
- [object](https://developers.wpengine.com/docs/ai/api/reference/operations/createagent/#tab-panel-0-3)

object

**type**

Discriminator for the tools\[] union. Omit for a plain client tool (the default); `custom` is the only value this schema accepts.

string

Allowed values: custom

**name**

required

Unique tool name. Must be unique within the agent’s `tools` array (across every entry, regardless of type).

string

<= 64 characters /^\[a-zA-Z0-9\_-]{1,64}$/

**description**

Human-readable description of what the tool does.

string

**input_schema**

JSON Schema defining the tool’s input parameters.

object

**label**

Optional short human-readable name for the tool, used alongside `name` and `description` when the gateway matches a search query against deferred tools. Supply it when the tool’s `name` is a slug whose wording a natural-language query would not contain.

string

<= 64 characters

**defer_loading**

When true, the tool is declared but kept out of the model’s tool list until it is needed, so a large catalogue costs no context up front. The gateway offers the agent a search over the deferred tools and loads the matches, and a deferred tool the model names directly is loaded on demand. Once loaded, its input is enforced strictly against its schema — unlike a non-deferred tool, which keeps today’s lenient handling — so a malformed call is rejected with a model-readable error instead of failing downstream. Leave false (the default) for tools that must be callable from the first turn.

boolean

**skills**

Skills enabled for the agent.

Array\<object>

<= 64 items

object

**_key_**

additional properties

any

**metadata**

Arbitrary string key-value metadata stored alongside the agent. The keys “scopes” and “settings” are reserved.

object

<= 16 properties

**_key_**

additional properties

string

<= 512 characters

## Responses

### 201

The created agent

Media typeapplication/json

object

**id**

required

Unique identifier for the agent.

string

**type**

required

string

Allowed values: agent

**project_id**

required

The project the agent belongs to.

string

**name**

required

string

**model**

required

string

**description**

Free-form description of what the agent does. On an agent with a `profile` this is the profile’s current description.

string

**system_prompt**

User prompt template (site prompt). On an agent with a `profile` this is the profile’s current prompt.

string

**profile**

Server-owned profile the agent follows (see `CreateAgentRequest.profile`). Omitted when the client owns every field.

string

**guardrails**

object

**moderation**

Best-effort custom-agent moderation setting. Product profiles own this value: writes are rejected and the field is omitted from responses for profiled agents.

string

default: disabled

Allowed values: disabled enabled

**managed_fields**

Fields owned by the agent’s profile, which the client cannot write. Omitted when the agent has no profile.

Array\<string>

**allowed_models**

Models the agent’s profile allows as a `model` choice. Omitted when the agent has no profile.

Array\<string>

**temperature**

number format: float

**max_tokens**

Output-token limit per turn (see CreateAgentRequest.max_tokens). Omitted when unset on the agent.

integer

**reasoning_effort**

Catalog-native reasoning effort stored on the agent. Omitted when unset. Allowed values follow the agent’s current model on GET /v1/models.

string

**scopes**

Permission scopes the agent operates under (round-trips the request value).

Array\<string>

**settings**

Feature toggles for the agent (round-trips the request value).

object

**_key_**

additional properties

any

**mcp_servers**

Array\<object>

object

**_key_**

additional properties

any

**tools**

Tool declarations (client tools, knowledge_base grounding entries, and echo/random_number evaluation tool opt-ins) round-tripped from the request.

Array

One of: discriminator: type

- [object](https://developers.wpengine.com/docs/ai/api/reference/operations/createagent/#tab-panel-1-0)
- [object](https://developers.wpengine.com/docs/ai/api/reference/operations/createagent/#tab-panel-1-1)
- [object](https://developers.wpengine.com/docs/ai/api/reference/operations/createagent/#tab-panel-1-2)
- [object](https://developers.wpengine.com/docs/ai/api/reference/operations/createagent/#tab-panel-1-3)

object

**type**

Discriminator for the tools\[] union. Omit for a plain client tool (the default); `custom` is the only value this schema accepts.

string

Allowed values: custom

**name**

Unique tool name. Must be unique within the agent’s `tools` array (across every entry, regardless of type). Always present for tools created since name validation was added; may be absent on an agent whose tools were stored before that validation existed.

string

<= 64 characters /^\[a-zA-Z0-9\_-]{1,64}$/

**description**

Human-readable description of what the tool does.

string

**input_schema**

JSON Schema defining the tool’s input parameters.

object

**label**

Optional short human-readable name for the tool, used alongside `name` and `description` when the gateway matches a search query against deferred tools. Supply it when the tool’s `name` is a slug whose wording a natural-language query would not contain.

string

<= 64 characters

**defer_loading**

When true, the tool is declared but kept out of the model’s tool list until it is needed, so a large catalogue costs no context up front. The gateway offers the agent a search over the deferred tools and loads the matches, and a deferred tool the model names directly is loaded on demand. Once loaded, its input is enforced strictly against its schema — unlike a non-deferred tool, which keeps today’s lenient handling — so a malformed call is rejected with a model-readable error instead of failing downstream. Leave false (the default) for tools that must be callable from the first turn.

boolean

**skills**

Array\<object>

object

**_key_**

additional properties

any

**metadata**

User-supplied string metadata. Reserved keys (“scopes”, “settings”) are surfaced on their own top-level fields rather than appearing here.

object

**_key_**

additional properties

string

<= 512 characters

**created_at**

required

string format: date-time

**updated_at**

string format: date-time

nullable

#### Example

```json
{
  "type": "agent",
  "guardrails": {
    "moderation": "disabled"
  },
  "managed_fields": [
    "model",
    "system_prompt",
    "description"
  ],
  "tools": [
    {
      "type": "custom",
      "defer_loading": false
    }
  ]
}
```

### 400

Invalid request body or parameters. On agent create and update, error type “profile_unavailable” means the requested profile is not available to the caller.

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

### 401

Missing or invalid bearer token

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

#### Headers

**WWW-Authenticate**

string

##### Example

```
Bearer realm="ai-services"
```

### 403

The token is valid, but the request is forbidden. Error type “permission_error” means the token’s scope/permission is insufficient or the authenticated user is not authorized for the target account; “entitlement_required” means the account may not use WP Engine AI, with error_code giving why when known (not_eligible, terms_required, not_enabled or suspended) and portal_url where to fix it; “account_required” means no billing account could be resolved; and “billing_account_pending” means billing isn’t set up for the account yet, usually because its setup after AI was turned on hasn’t finished. Retry shortly. On /v1/chat/completions when content moderation is enabled, and on /v1/sessions/{session_id}/events, error type “content_policy_violation” instead means the request content was blocked by content moderation policy; that response never identifies which filter or category matched.

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

### 413

Request body exceeds the 64 KiB limit

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

### 429

Rate limit exceeded. The request was throttled at the edge; wait and retry later. The response body is a short HTML page generated by the load balancer, not a structured JSON error — clients should rely on the 429 status code rather than parsing the body.

Media typetext/html

string

### 500

The server encountered an unexpected error while handling the request. Safe to retry once; if it persists, the cause is server-side and the request payload was not the problem.

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

### 503

A required dependency is temporarily unavailable. Retry after a short delay with exponential back-off. An entitlement lookup failure uses error type “entitlement_unavailable”. A model without complete rate coverage uses error type “model_unpriced”. A credit balance that can’t be read uses error type “balance_unavailable”.

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

## Was this helpful?

Thanks for the feedback!

```json
{"@context":"https://schema.org","@graph":[{"@type":"WebSite","@id":"https://developers.wpengine.com/#website","name":"WP Engine Product Documentation","url":"https://developers.wpengine.com/","description":"Resources for developing, deploying, and scaling on WP Engine.","inLanguage":"en-US","keywords":["API documentation","product documentation","headless WordPress","managed WordPress hosting"],"publisher":{"@id":"https://wpengine.com/#organization"}}]}
```
