---
title: "Create an agent"
method: POST
path: "/v1/agents"
tags: ["Agents"]
---

# Create an agent

`POST /v1/agents`

## Request

- Base URL: `https://api.mails.ai`
- URL: `https://api.mails.ai/v1/agents`
- Auth: HTTP bearer

**Requires scope:** `manage`. Creates an agent (a send/receive identity) with the API address `name@<workspace>.mails.ai`, or at an `address` on a domain your workspace has verified for sending. On mails.ai it receives mail at `name.<workspace>@in.mails.ai` (its `receiving_address`) and its mail goes out from `name.<workspace>@send.mails.ai`. Add `forwarding: true` when the mail service that already hosts that address will forward its mail to the agent's `forwarding_address` (no MX change), then test it with `POST /v1/agents/{id}/forwarding/verify`. Past your plan's agent limit it answers 402 `resource_cap_exceeded`, however many requests arrive together, on your own domain as on your mails.ai address (a send whose `from` names a new address answers `plan_limit_exceeded` instead). While the workspace has an agent suspended by abuse prevention, archived or not, it answers 422 `agent_paused`.

## Request body

- CreateAgentRequest
  - `name` string: The agent's name and the part of its address before the `@`: 1 to 32 lowercase letters, digits, `.`, `_` or `-`. Required unless `address` is given, which names the agent after its local part. A name already used on that domain, by an archived agent too, is refused with 400 `duplicate_resource`. On the workspace's own mails.ai subdomain the agent sends and receives as `<name>.<slug>`, and an address may have at most 64 characters before the `@`, so a name that makes it longer is refused with 400 `invalid_param_value`.
  - `domain` string: The domain of the agent's address. Left out, it is your workspace's `<slug>.mails.ai`, or the domain of `address` when that is given. Any other value must be a custom domain this workspace has verified (400 `domain_not_verified` otherwise).
  - `address` string, email: The agent's address, on a domain your workspace has verified for sending (support@acme.com). The agent is named after its local part, so `name` may be left out. Past your plan's agent limit it answers `402 resource_cap_exceeded`, as any agent create does.
  - `forwarding` boolean: Forwarding mode, for an address on your own domain: the mail service hosting it forwards its mail to the agent's `forwarding_address`, so no MX record changes. `POST /v1/agents/{id}/forwarding/verify` then sends a test through the forward. Turning it on or off starts `forwarding_status` again from `not_verified`.
  - `allowlist_domains` string[]: When set, this agent sends only to these domains and their subdomains (`acme.com` covers `mail.acme.com`). A recipient in `to`, `cc` or `bcc` outside the list refuses the whole send with 422 `recipient_not_allowed`, in test mode too. A leading `@` is ignored. The test address reply@test.mails.ai is always allowed.
  - `blocklist_domains` string[]: This agent never sends to these domains or their subdomains. A recipient in `to`, `cc` or `bcc` on the list refuses the whole send with 422 `recipient_not_allowed`, in test mode too. The test address reply@test.mails.ai is never blocked.
  - `daily_send_limit` integer: This agent's own cap on recipients in any 24 hours, on top of your plan's cap: each live send counts once per `to`, `cc` and `bcc` address. Past it a send is refused with 429 `daily_limit_exceeded`, whose `resets_at` says when the send fits again (with a `Retry-After` header only when that is a minute or less away). Test-mode sends are neither counted nor refused. Leave it out to use only your plan's cap.
  - `hourly_send_limit` integer: This agent's own cap on recipients in any hour, on top of your plan's cap: each live send counts once per `to`, `cc` and `bcc` address. Past it a send is refused with 429 `hourly_limit_exceeded`, whose `resets_at` says when the send fits again (with a `Retry-After` header only when that is a minute or less away). Test-mode sends are neither counted nor refused. Leave it out to use only your plan's cap.
  - `classify_inbound` boolean: Also read this agent's new mail for intent, entities and urgency (never a reply in a thread). Needs a paid plan (402 `feature_not_enabled`). Off unless set. Its `message.received` events then carry an `intent`, `entities` and `urgency`, and on `reply.received` the `intent` is always `general_reply`. The injection scan runs either way.

## Response `201`

The created agent.

- Agent
  - `id` string, required: Agent id (`agt_…`).
  - `name` string, required: The agent's name, the part of `email` before the `@`. The agent routes, and `agent` on sends and drafts, take it in place of the id; agents on different domains can share a name, so the id is the safer reference.
  - `email` string, email, required: The agent's address, `<name>@<domain>`, unique across mails.ai: the identity it sends as. It receives mail at `receiving_address`.
  - `domain` string, required: The domain part of `email`: your workspace's `<slug>.mails.ai`, or a custom domain that was verified when the agent was created.
  - `receiving_address` string, email, required: Where this agent receives mail. Every send's Reply-To is this address unless the send sets `reply_to`, so replies reach the agent. `<name>.<slug>@in.mails.ai` for an agent on your mails.ai address (`<name>@in.mails.ai` when the name is the slug); for an agent on your own domain, the name and the whole domain with its dots written as underscores, such as `billing.example_com@in.mails.ai`. Mail to any plus address of it reaches the agent too: add `+` and anything before the `@` (`qa.acme+github@in.mails.ai`, `qa.acme+run-1842@in.mails.ai`), so one agent can give every service or test run its own address. The address used is kept as the message's `delivered_to`, and the `to` filter on received mail matches it.
  - `workspace_id` string, required: Id of the workspace the agent belongs to (`wsk_…`).
  - `status` 'active' | 'paused' | 'archived', required: `active`; `paused`, when its send requests get 422 `agent_paused` and `pause_reason` says why; or `archived` after a DELETE, when it cannot send and the default list leaves it out.
  - `pause_reason` string: Why the agent is paused or archived: `manual` (paused with PATCH), `archived_via_api` (archived with DELETE), or a reason the automatic bounce, complaint and abuse checks set, such as `bounce_rate=0.0612`. Absent when there is none. The list leaves it out.
  - `daily_send_limit` integer: This agent's own cap on recipients in any 24 hours, on top of your plan's cap: each live send counts once per `to`, `cc` and `bcc` address. Past it, a send gets 429 `daily_limit_exceeded`. Absent when unset. The list leaves it out.
  - `hourly_send_limit` integer: This agent's own cap on recipients in any hour, on top of your plan's cap: each live send counts once per `to`, `cc` and `bcc` address. Past it, a send gets 429 `hourly_limit_exceeded`. Absent when unset. The list leaves it out.
  - `allowlist_domains` string[]: When present, this agent sends only to these domains and their subdomains; any other recipient in `to`, `cc` or `bcc` gets 422 `recipient_not_allowed`. The test address reply@test.mails.ai is always allowed. The list leaves it out.
  - `blocklist_domains` string[]: Domains (and their subdomains) this agent never sends to; a recipient on the list gets 422 `recipient_not_allowed`. The test address reply@test.mails.ai is never blocked. The list leaves it out.
  - `classify_inbound` boolean: Whether this agent's new mail is read for intent; a reply in a thread never is. The list leaves it out.
  - `unread_count` integer: Unread threads in the agent's inbox folder, on the list and on the single-agent GET and PATCH.
  - `forwarding` boolean: Forwarding mode: the agent's address is on your own domain, and the mail service that already hosts it forwards its mail to `forwarding_address`, so no MX record changes.
  - `forwarding_address` string, email, nullable: Where to forward the agent's mail: its `receiving_address`. Mail that arrives here is stored, threaded and sent to your webhooks like any received mail, with the agent's own address as the recipient. Null when forwarding is off.
  - `forwarding_status` 'not_verified' | 'verified' | 'failing' | 'null', nullable: `verified` once a test from `POST /v1/agents/{id}/forwarding/verify` arrived through the forward; `failing` when the last test had not arrived 15 minutes after it was sent; `not_verified` before that. Null when forwarding is off.
  - `forwarding_test_sent_at` string, date-time, nullable: When the last forwarding test was sent. Null when none was, or forwarding is off.
  - `forwarding_verified_at` string, date-time, nullable: When a forwarding test last arrived through the forward. Null when none has, or forwarding is off.
  - `created_at` string, date-time: When the agent was created.
  - `updated_at` string, date-time: When the agent last changed: an update, an archive, or an automatic pause or resume. The list leaves it out.

## Other responses

- `400`: Invalid request — malformed JSON or a field failed validation. Also `feature_not_enabled` when this server has a feature switched off (custom domains before they are available).
- `401`: Missing or invalid API key.
- `402`: Payment required: a plan limit (`plan_limit_exceeded`) or a feature the current plan does not include (`feature_not_enabled`). The error object carries `upgrade_url`, the dashboard page where the plan can be changed.
- `403`: The API key lacks the required scope (`insufficient_scope`), or the token belongs to an app connected by sign-in and this operation is closed to connected apps (`connected_app_not_allowed`).
- `422`: Unprocessable — agent paused, recipient suppressed, a recipient at test.mails.ai other than reply@test.mails.ai (`unknown_test_address`), or abuse guard.
- `500`: Internal server error.

---

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