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

# Errors

> Every error the API can return, where it arrives, and what to do about it.

On the REST API, errors arrive in one of two places, and the difference matters when you're writing the handling code.

> [card] Before the stream
>
> Validation, auth, and capacity are checked up front. These are ordinary JSON responses with a 4xx status. **No credit is charged.**

> [card] On the stream
>
> Anything that goes wrong during generation arrives as a terminal `error` event, after HTTP 200 has already been sent. Whether a credit was charged depends on the code.

## Shape

Rejections before the stream:

```json theme={null}
{
  "error": {
    "code": "missing_prompt",
    "message": "`prompt` is required."
  }
}
```

Failures during generation, as the final event:

```text theme={null}
event: error
data: {"code":"no_credits","message":"…"}
```

Match on `code`. The `message` is for humans and may be reworded.

## Before the stream

| Code | Status | Meaning | What to do |
| - | - | - | - |
| `invalid_body` | 400 | Body isn't a JSON object | Check `Content-Type: application/json` and that you're sending a body |
| `missing_prompt` | 400 | `prompt` absent or empty | Supply a non-empty prompt |
| `prompt_too_long` | 400 | Over 4000 characters | Shorten it. See [limits](https://docs.alpacarelay.com/developers/limits.md) |
| `missing_api_key` | 401 | No key in any accepted header | Add `Authorization: Bearer ar_live_…` |
| `invalid_api_key` | 401 | Key unrecognised or deleted | Check it, or create a new one |
| `key_not_scoped` | 403 | Key isn't attached to a workspace, or its owner has left the workspace | Create a fresh key from the workspace you intend to bill |
| `rate_limited` | 429 | Over the key's 60-calls-a-minute budget, **or** three generations already running | Retry with a backoff |
| `auth_error` | 500 | We couldn't check the key | Retry. Contact support if it persists |

> [!NOTE]
>
> `rate_limited` has two causes with the same code: the per-key call budget and the per-workspace concurrency limit. Either way it's safe to retry after a short wait, and nothing has been charged. See [Credits and limits](https://docs.alpacarelay.com/developers/limits.md).

## On the stream

| Code | Meaning | Credit | What to do |
| - | - | - | - |
| `no_credits` | Workspace is out of credits | Not charged | Top up under **Settings** → **Billing & usage** |
| `generation_failed` | Generation went wrong | Usually refunded | Retry. If it persists, contact support with the `request_id` |
| `timed_out` | Generation passed the server's backstop | Charged | Check **Emails** in the app first. The email often finishes anyway |

> [!TIP]
>
> Keep the `request_id` from the `accepted` event and log it next to anything you report. It's what identifies your generation in our logs.

## Handling them

The main thing to get right is handling the two places separately:

```js theme={null}
const res = await fetch(url, { method: "POST", headers, body });

// Before the stream: JSON, nothing charged, safe to retry after fixing.
if (!res.ok) {
  const { error } = await res.json();
  if (error.code === "rate_limited") return retryAfterBackoff();
  throw new Error(`${error.code}: ${error.message}`);
}

// On the stream: the generation was under way. Retry on purpose, not in a loop.
// (parse frames as shown in "Generate an email")
if (event === "error") {
  if (data.code === "no_credits") return alertBilling();
  throw new Error(data.code);
}
```

A reasonable default: retry `rate_limited` automatically with backoff, retry `generation_failed` once, look for the email before retrying `timed_out`, and never retry the 400s or 401s, because they'll fail the same way until you change something.

## Over MCP

MCP tools don't return codes. A failed tool call comes back as an error result whose text is meant for the assistant to read and act on, and most assistants relay it to you. The common ones:

| Message | Means |
| - | - |
| `Too many tool calls: the limit is 60 a minute. Retry in N seconds.` | The call budget is spent. Wait the stated time |
| `Too many concurrent generations for your organization. Please retry shortly.` | Three generations are already running |
| `Your plan has no remaining credits.` | Out of credits. Nothing was charged |
| `Invalid input — <field>: <reason>` | A tool argument failed validation. The field is named so the assistant can fix it |
| `<Provider> is not connected — connect it in Settings first.` | An ESP tool was used before connecting that ESP |
| `You belong to N organizations, so this connection is ambiguous…` | Signed in with OAuth to more than one workspace. See [Connect](https://docs.alpacarelay.com/developers/mcp/connect.md#choose-a-workspace) |
| `The tool hit an unexpected error. Please try again.` | Something failed on our side. Details are logged for us, not shown to you |

An expired OAuth sign-in isn't a tool error. Your client receives HTTP 401 and refreshes on its own, or asks you to sign in again.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.