Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fern/pages/integrations/agent-onboarding.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ agentmail agent verify --otp-code 123456


<Warning>
**Unverified accounts can only send email to the signup address.** Until you complete OTP verification, outbound emails to any other address will fail without any error message. Always call `agent.verify()` before sending to external recipients.
**Unverified accounts can only send email to the signup address.** Until you complete OTP verification, sends to any other address are rejected with a `403` carrying `code: "message_rejected"`, whose `fix` explains that sending is restricted until verification and points at `POST /v0/agent/verify`. Always call `agent.verify()` before sending to external recipients. See the [Error Reference](/errors#message_rejected).
</Warning>

<Warning>
Expand Down
16 changes: 10 additions & 6 deletions fern/pages/knowledge-base/api-403-error.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ slug: knowledge-base/api-403-error

A `403 Forbidden` response from the AgentMail API means your request was rejected. This can happen for several reasons, and the fix depends on the cause.

**Check the response body first.** Application-level 403s carry a machine-readable `code`, a `fix` describing the concrete next action, and a `docs` link into the [Error Reference](/errors) — the `fix` usually resolves the issue directly. The one exception is a bare `{"message":"Forbidden"}` with none of those fields: that response comes from the API gateway rejecting your credential before it reaches the API, which almost always means cause 1 below.

## 1. Incorrect API key

The most common cause. Your API key may be missing, incomplete, or invalid.
Expand All @@ -29,9 +31,9 @@ A common mistake is copying only part of the key. API keys are long strings. Dou

## 2. Accessing a resource you do not own

You will get a 403 if you try to access an inbox, message, thread, or domain that belongs to a different organization. Each API key is scoped to one organization, and you can only access resources created under that organization.
Accessing an inbox, message, thread, or domain that belongs to a different organization — or one outside your key's pod or inbox scope — returns a `404` with `code: "not_found"`, not a 403: resources outside your credential's scope are deliberately indistinguishable from ones that don't exist. The 403 you can hit in this family is using an inbox-scoped key on a pod route.

**How to fix:** Verify that the `inbox_id`, `message_id`, `thread_id`, or `domain_id` in your request actually belongs to your account.
**How to fix:** Verify that the `inbox_id`, `message_id`, `thread_id`, or `domain_id` in your request actually belongs to your account and is within your key's scope. See [not_found](/errors#not_found) in the Error Reference.

## 3. Missing required parameters

Expand All @@ -41,13 +43,15 @@ Some endpoints return a 403 when required path parameters are missing or malform

## 4. Sending to a suppressed address

AgentMail automatically suppresses addresses that have previously bounced, been rejected, or filed a spam complaint. If you try to send to a suppressed address, the API returns a 403.
AgentMail automatically suppresses addresses that have previously bounced, been rejected, or filed a spam complaint. If you try to send to a suppressed address, the API returns a `403` with `code: "message_rejected"`; the `fix` names the matched block entry and the exact remedy (deletable entries get a `DELETE` path; suppression entries added from bounces or complaints are read-only and go through support review).

**How to fix:** Read the `fix` field on the response. See [message_rejected](/errors#message_rejected) in the Error Reference and [Emails bouncing](/knowledge-base/emails-bouncing) for how suppression works.

**How to fix:** Check if the recipient address has been suppressed. See [Emails bouncing](/knowledge-base/emails-bouncing) for more details on how suppression works.
## 5. Your API key lacks a permission

## We do not restrict your API key
API keys can be created with granular permissions (for example, a key that can read messages but not send them). A denial from a permission gate returns `code: "missing_permission"`, and the `fix` names the exact missing permission and how to obtain it — including when a new key at the same scope *cannot* help (the permission is outside your key's scope, or the organization has not completed agent verification).

AgentMail does not place any feature restrictions on your API key. Every API key has access to all endpoints. If you are getting a 403, it is not because your key lacks permissions. It is one of the causes listed above.
**How to fix:** Read the `fix` field, then retry with a key that holds the named permission — created from a credential that already holds it, since a key cannot grant a permission it lacks. Keys created without a permissions object are unrestricted. See [missing_permission](/errors#missing_permission) in the Error Reference.

## Test your API key

Expand Down
27 changes: 16 additions & 11 deletions fern/pages/resources/errors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,25 @@ slug: errors
description: "Every AgentMail API error code, what it means, and how to fix it."
---

Every error response from the AgentMail API includes a stable, machine-readable `code` you can branch on, a `message` describing what went wrong, a `fix` describing the concrete next action that resolves it, and a `docs` link pointing to the matching entry on this page.
Every error response from the AgentMail API includes a stable, machine-readable `code` you can branch on, a `message` describing what went wrong, and a `docs` link pointing to the matching entry on this page. Most responses also include a `fix` describing the concrete next action that resolves it; `fix` is omitted when no generic remedy applies.

```json
{
"name": "ForbiddenError",
"code": "missing_permission",
"message": "Missing required permission: message_send",
"fix": "This API key does not have the 'message_send' permission. Retry with an API key that includes 'message_send', or create one via POST /v0/api-keys (or at https://console.agentmail.to) with that permission enabled.",
"message": "Forbidden",
"fix": "This API key does not have the 'message_send' permission. Retry with an API key that includes 'message_send' — note a key cannot create a new key with permissions it lacks itself, so the new key must come from a credential that already holds 'message_send' (an unrestricted key via POST /v0/api-keys, or the console at https://console.agentmail.to). If no such key exists, the permission may be outside your credential's scope — use a key with a broader scope (organization-scoped keys hold the widest permissions).",
"docs": "https://docs.agentmail.to/errors#missing_permission"
}
```

Branch on `code`, not on `message`: messages may be reworded over time, but codes are stable.
Branch on `code`, not on `name` or `message`: the `name` and `message` fields keep their long-standing pre-`code` values for backward compatibility (a permission denial still reads `Forbidden`), so they neither identify the specific cause nor carry the remedy — `code` and `fix` do.

## Authentication (401)

### missing_authorization

No `Authorization` header was sent. Send your API key in the `Authorization: Bearer <api_key>` header. Create a key at [console.agentmail.to](https://console.agentmail.to) or via `POST /v0/api-keys`.
The `Authorization` header was missing, did not use the required case-sensitive `Bearer ` scheme, used the scheme correctly but carried no key value after `Bearer `, or carried whitespace inside the key value (no credential contains whitespace) — the message states which. Send your API key as `Authorization: Bearer <api_key>`. Create a key at [console.agentmail.to](https://console.agentmail.to) or via `POST /v0/api-keys`.

### invalid_token_type

Expand All @@ -40,15 +40,15 @@ Generic authentication failure. Send a valid API key in the `Authorization: Bear

### missing_permission

The authenticated API key lacks the specific permission named in the message (for example `message_send`). Retry with an API key that includes that permission, or create one via `POST /v0/api-keys` with the permission enabled.
The credential lacks a specific required permission (for example `message_send`). For backward compatibility the `message` stays `Forbidden` (event-type permission checks on webhooks and WebSocket subscriptions keep their own legacy form, `<permission> is forbidden`); the missing permission is named in the `fix`. The `fix` also names the remedy for the actual cause: a restricted key may need the permission granted (retry with a key that includes it, or create one via `POST /v0/api-keys` from a credential that already holds it — a key cannot grant a permission it lacks), while other denials come from the credential's scope or the organization's state, where creating another key at the same scope cannot help — for example `organization_read` requires an organization-scoped key, some permissions (like creating API keys or send-list entries) require completing agent verification via `POST /v0/agent/verify` first, and `agent_verify` itself is only available while an unverified agent organization still needs verification.

### permission_escalation

A create or update API key request asked for a permission the calling key does not hold itself. A key cannot grant more than it has. Remove the extra permissions from the request, or retry with a key that already holds them.

### unrestricted_key_required

Clearing a key's permissions (`permissions: null`) or downgrading an unrestricted key requires an unrestricted credential: a dashboard session or an API key stored without a permissions attribute. Retry with one, or pass an explicit permissions object instead.
Creating an unrestricted child key (`permissions: null`), clearing a key's permissions, or changing the permissions of a currently-unrestricted key requires an unrestricted credential: a dashboard session or an API key stored without a permissions attribute. Retry with one; the `fix` on the response says which gate fired.

### forbidden

Expand All @@ -62,7 +62,7 @@ One or more request fields failed validation. Inspect the `errors` array in the

### not_found

No resource matched the given identifier. Check that the ID is correct and belongs to your organization; use the corresponding list endpoint to discover valid IDs.
No resource with the given identifier is visible to this credential. That can mean the ID is wrong — but the same response also deliberately hides resources outside your credential's scope (organization, pod, or inbox) and resources behind a restricted-label read permission you lack (`spam`, `blocked`, `unauthenticated`, `trash`), so a correct ID can still return `not_found`. Check the ID, your credential's scope, and your label-read permissions; list endpoints return only the IDs visible to you.

### unprocessable

Expand All @@ -84,15 +84,15 @@ The requested value (for example, an inbox username) is already in use. Choose a

### limit_exceeded

A resource limit has been reached (for example, the webhook endpoint limit). Remove an existing resource before adding another, or email support@agentmail.to to raise your limit.
A resource limit has been reached (for example, the webhook endpoint limit). Remove an existing resource before adding another, or email support@agentmail.to to raise your limit. For agent-verification OTP attempts, the `fix` distinguishes the two states: while the exhausted code is still live, every submission is rejected (even the correct code) until it expires 24 hours from issue, after which `POST /v0/agent/sign-up` issues a fresh code with a reset attempt count; if the code has already expired, sign up again immediately.

### domain_not_verified

The sending domain has not completed DNS verification. Add the DNS records returned by `GET /v0/domains/{domain}`, then call `POST /v0/domains/{domain}/verify` before sending from an address on this domain.

### conflict

The request conflicts with another request under the same `Idempotency-Key`. Either the original send is still in progress (wait briefly and retry the identical request) or the key was already used for a different message (generate a new key for new messages). See [Idempotency](/idempotency).
The request conflicts with another request under the same `Idempotency-Key`. Either the original send is still in progress wait briefly and retry the identical request: once the first attempt completes the retry returns its send, and if the first attempt failed without completing, the key becomes retryable again after a short window — or the key was already used for a different message (generate a new key for new messages). See [Idempotency](/idempotency).

### race_condition

Expand All @@ -110,7 +110,12 @@ The resource cannot be deleted yet, usually because dependent resources still ex

### message_rejected

The message was not sent. The message names the reason: blocked recipients (remove them or unblock via `DELETE /v0/lists/send/{type}/{entry}`), a suspended account (email support@agentmail.to), or an unfetchable attachment URL (use a URL that returns 200 without authentication, or send the attachment inline as base64).
The message was not sent. The message names the reason, and the `fix` states which case applies:

- **Recipients on a send block list** — remove them from the request, or delete the matched block entry. The `fix` names the stored entry (which may be a domain rather than the recipient address) and the exact `DELETE /v0/…/lists/send/block/{entry}` path at the scope where it lives. Deleting an entry at a broader scope than your key (an organization-level block hit by a pod- or inbox-scoped key) requires a key scoped at that level. Read-only entries — added automatically from bounces, complaints, and unsubscribes — cannot be deleted via the API; email support@agentmail.to to have one reviewed.
- **Recipients missing from an active send allow list** — add them via `POST /v0/lists/send/allow` (there is no block entry to delete). On an agent organization that has not completed verification, sending is restricted to the human's email and list entries cannot be created yet — complete `POST /v0/agent/verify` instead.
- **A suspended account** — email support@agentmail.to; retrying will keep failing until the suspension is resolved.
- **An unfetchable attachment URL** — use a URL that returns 200 without authentication, or send the attachment inline as base64.

## Transient errors (429 / 500 / 503)

Expand Down
Loading