> ## Documentation Index
> Fetch the complete documentation index at: https://developers.sapot.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors, Rate Limits & Quotas

> Every failure mode of the Sapot agent API — auth and scope refusals, quota 429s, async caveats — and exactly how to handle each.

Failures on the agent surface are **explicit and predictable**. Most are enforced boundaries — a scope you weren't granted, a quota you've used up, a blast that only a human can send — not transient bugs. This page lists every failure mode and the right way to handle it.

## How errors surface

* **Via MCP:** a failed tool call comes back as a `ToolError` whose message is the Rails error text, **verbatim** (e.g. `Access token is missing the required scope: contacts:manage`). Your agent reads that message and should act on it, not retry blindly.
* **Via REST (direct):** you get the HTTP status plus a JSON body `{ "error": "<message>" }`.

<Info>
  The MCP server surfaces Rails' own reason unchanged. Only when Rails returns an empty/generic auth body does the server append a hint that the scope or `X-Sapot-Account-Id` may be wrong — so a real business-rule refusal is never mislabeled as an auth problem.
</Info>

## Authentication & scope errors (401)

All four are returned as **HTTP 401** and are **not retryable** — the same call will fail identically until you fix the token, its scopes, or the account id.

| Message                                                 | Cause                                                                                                                                   | Fix                                                                                         |
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `Invalid Access Token`                                  | The token is missing, unrecognised, revoked, or expired (not an active agent key).                                                      | Check the key value; mint a fresh key if it was revoked or has expired.                     |
| `This endpoint is not available to agent access tokens` | The action isn't on the agent allowlist at all — an IP-sensitive or unsupported endpoint (e.g. raw flow graph export, autonomous send). | No scope unlocks it. Use a supported tool; these boundaries are deliberate.                 |
| `Access token is missing the required scope: <scope>`   | The key authenticated fine but lacks the scope that endpoint requires.                                                                  | Add the named scope to the key in **Settings → API & Agents** (or leave it off on purpose). |
| `Access token is not valid for this account`            | The `X-Sapot-Account-Id` header doesn't match the account the key was minted in.                                                        | Send the account id the key belongs to — a key is bound to exactly one account.             |

<Warning>
  Never retry a scope or boundary refusal. If a tool reports a missing scope, tell the user which scope to add rather than looping. See [Authentication & Scopes](/ai-agents/authentication).
</Warning>

## Business-rule refusals (403 / 422)

Some calls authenticate and carry the right scope but are refused by a product rule. The Rails message surfaces verbatim so you know why. Common cases:

* **Editing a non-draft campaign** — an agent key may only edit its own **draft** campaigns; touching a scheduled/sending campaign is refused. Draft a new one or ask the human to manage the live campaign from the dashboard.
* **WhatsApp template CRUD on a non-eligible inbox** — create/update/delete require a **WhatsApp Cloud** inbox and an admin-level token.
* **Validation errors (422)** — a malformed payload (missing required field, bad enum value). Fix the arguments and resend.

These are not retryable as-is: change the request, don't repeat it.

## Quota exceeded (429)

<Warning>
  `Agent action quota exceeded for the current billing period` (HTTP **429**) means your account has used up its plan's MCP-action allowance for the current billing period.
</Warning>

How the quota works:

* It is a **per-plan, per-billing-period** allowance on total agent actions.
* It **resets automatically** at the next billing-period renewal (on Stripe renewal, and on calendar rollover).
* **Upgrading** to a higher plan raises the cap and clears the block once the higher cap sits above current usage. A **downgrade or same-tier change does NOT clear it** — you still wait for the reset.
* Accounts with **no finite cap** (unlimited plans) never hit this — the quota is dormant until a plan sets a finite `agent_actions` limit.

When you see a 429, **stop and tell the user** — back off rather than hammering the endpoint.

## What counts against your usage

Metering is precise: only work that actually happened is charged.

* **Only allowed + successful (2xx) calls count** — against both your key's usage and the account quota.
* **Refused or errored calls consume nothing:** `401` (auth/scope), `403` (business rule), `404` (not found), `422` (validation), `429` (over quota), and `500` (server error) are all free.
* Per-key usage is shown as the **Calls** column in **Settings → API & Agents**.
* The account-level counter is claimed **atomically**, so concurrent bursts can never push usage past the cap.

This means a scope-denied or failed request never inflates your bill or your key's call count — you're only ever metered for real, successful actions.

## Asynchronous caveats

A few operations complete **out of band**. A "nothing happened yet" result here is expected, not a failure:

* **WhatsApp templates** — `create_whatsapp_template` / `update_whatsapp_template` return status `PENDING`. Meta approves out of band and approval **cannot be forced** via the API. Poll `list_whatsapp_templates` to see the status change.
* **Template sync** — `sync_whatsapp_templates` runs in the **background** and returns `{ message: "..." }` immediately. Call `list_whatsapp_templates` afterwards to see the refreshed set.
* **Campaign drafts never auto-send** — a campaign drafted or edited by an agent key is forced into a non-sending `draft` state, no matter how it's scheduled. Only a human clicking **Send** in the dashboard fires the blast. "It didn't send" is the enforced boundary, not a bug — see [Campaigns & Blasts](/ai-agents/campaigns).

## Retry guidance

| Situation                                        | Retry?     | How                                                                                           |
| ------------------------------------------------ | ---------- | --------------------------------------------------------------------------------------------- |
| Scope / boundary refusal (401), account mismatch | No         | Fix the token, scope, or account id — never loop.                                             |
| Business-rule 403 / validation 422               | No         | Change the request payload, then send once.                                                   |
| Quota 429                                        | Not soon   | Back off; wait for the period reset or upgrade the plan.                                      |
| Read (GET) after a transient network / 5xx error | Yes        | Safe to retry with backoff — reads are idempotent and cost nothing until they succeed.        |
| Write after a non-2xx failure                    | Cautiously | Nothing was metered, but check for partial effects before re-issuing a non-idempotent create. |

## Rate limits (planned)

<Note>
  There is **no per-minute rate limit today** — the per-plan quota above is the only cap on total usage. A per-minute throttle is a planned addition; when it ships it will return `429` and follow the same back-off guidance.
</Note>
