> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended**:
> ```bash
> npx @scalekit-inc/cli setup
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for other Agent Skills-compatible agents.
> Skills: integrate-agentkit, implement-saaskit, add-mcp-oauth, implement-sso, implement-scim.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Errors and rate limits

An error returns an HTTP status and a JSON body. `code` is the gRPC status code and `message` says what went wrong. `details` holds an `ErrorInfo` whose `error_code` is a stable name to match on. Match on `error_code`, not on the message text.

## Scalekit or the app?

A tool call can fail in Scalekit or in the app the tool calls, such as Gmail or Slack. `error_code` tells you which:

| error_code | Who rejected the call | What to do |
| --- | --- | --- |
| `TOOL_ERROR` | The app. `tool_error_info` says why. | Read `tool_error_info.tool_error_code`, below. |
| Anything else, such as `RESOURCE_NOT_FOUND`, `UNAUTHENTICATED`, `INVALID_ARGUMENT` | Scalekit, before the app was called. | Fix the request. Each endpoint page lists the errors it returns. |

## Tool errors

`tool_error_info` has three fields: `tool_error_code`, `tool_error_message` (what the app returned) and `execution_id` (quote it to support).

| tool_error_code | HTTP | Meaning | What to do |
| --- | --- | --- | --- |
| `REAUTHENTICATION_NEEDED`, `UNAUTHENTICATED` | 401 | The user's access to the app was revoked or expired, or the connected account is `EXPIRED` | The user must authorize again: send them a new [authorization link](https://docs.scalekit.com/agentkit/reference/authorization/get-an-authorization-link/). |
| `FORBIDDEN`, `PERMISSION_DENIED` | 403 | The user's account lacks the scope this tool needs | Add the scope to the connection, then have the user authorize again. |
| `RESOURCE_NOT_FOUND` | 404 | The app couldn't find what the call refers to, such as an event ID | Check the IDs in `params`. |
| `RATE_LIMITED` | 429 | The app's rate limit or quota | Back off and retry. See [Rate limits](#rate-limits). |
| `INVALID_ARGUMENT` | 400 | The app or the tool rejected the input, or a tool from an MCP server returned an error | Read `tool_error_message` and fix the parameters. |
| `EXECUTION_ERROR` | 400 | The app returned an error for this call | Read `tool_error_message`, fix the parameters or the account, and retry. |
| `INTERNAL_ERROR` | 400 or 500 | `400`: the app rejected the call. `500`: Scalekit couldn't run the tool | For `400`, read `tool_error_message`. For `500`, retry later, and quote `execution_id` to support if it persists. |
| `TOOL_ERROR` | 400 or 500 | From a tool on an app's MCP server: `400` when the server rejected the call, `500` when it failed, rate-limited the call or couldn't be reached | For `400`, read `tool_error_message`. For `500`, back off before you retry. |

## Tools from an MCP server

A tool from an app's own MCP server maps what the server returned to these codes. The server's status and message are in `tool_error_message`.

- **`401`** with `REAUTHENTICATION_NEEDED`: the server rejected the user's credentials. The user must authorize again.
- **`403`** with `FORBIDDEN`: the server refused the call.
- **`400`** with `INVALID_ARGUMENT`: the tool returned an error result, or the server rejected the input. **`400`** with `TOOL_ERROR`: the server returned any other 4xx status.
- **`500`** with `TOOL_ERROR`: the server returned `408`, `429` or a 5xx status, or Scalekit couldn't reach it in time. A retry can run the tool twice, so retry only tools that are safe to repeat.

## Rate limits

When an app rate-limits a call, Scalekit returns HTTP 429 with `error_code` `TOOL_ERROR` and `tool_error_code` `RATE_LIMITED`. Each app sets its own limits, per user or per OAuth app. With [your own OAuth app](https://docs.scalekit.com/agentkit/advanced/bring-your-own-oauth/), you get your own quota at apps that count per app.

The response has no `Retry-After` header. Retry with exponential backoff and jitter, for example 1, 2, then 4 seconds, and stop after a few tries.

**Two exceptions.** A tool from an app's own MCP server returns HTTP 500 with `TOOL_ERROR` when the server rate-limits the call, as [Tools from an MCP server](#tools-from-an-mcp-server) describes. The [API proxy](https://docs.scalekit.com/agentkit/tools/custom-tools/) returns the app's status and headers unchanged, including its `Retry-After`.

[Troubleshooting](https://docs.scalekit.com/agentkit/troubleshooting/) covers the errors users see while connecting.

## Examples

### Scalekit rejected it · HTTP 404

```json
{
  "code": 5,
  "message": "connector token not found for the given connector and environment id",
  "details": [
    {
      "@type": "type.googleapis.com/scalekit.v1.errdetails.ErrorInfo",
      "error_code": "RESOURCE_NOT_FOUND"
    }
  ]
}
```

### The app's rate limit · HTTP 429

```json
{
  "code": 8,
  "message": "tool execution failed - rate limited",
  "details": [
    {
      "@type": "type.googleapis.com/scalekit.v1.errdetails.ErrorInfo",
      "error_code": "TOOL_ERROR",
      "tool_error_info": {
        "tool_error_code": "RATE_LIMITED",
        "tool_error_message": "{\"error\":\"ratelimited\"}",
        "execution_id": "6f1c2e4a-a0b3-11f1-9c2a-0242ac120002"
      }
    }
  ]
}
```

### Tell them apart

```bash
curl -sS ... | jq -r '.details[0]
  | if .error_code == "TOOL_ERROR"
    then "app: \(.tool_error_info.tool_error_code)"
    else "scalekit: \(.error_code)" end'
```

### Handle errors

Python:

```python
from scalekit.common.exceptions import (
    ScalekitServerException,
    ScalekitToolException,
    ScalekitToolUnauthorizedException,
)

try:
    result = actions.execute_tool(
        tool_name="gmail_fetch_mails",
        connection_name="gmail",
        identifier="user_123",
        tool_input={"query": "is:unread"},
    )
except ScalekitToolUnauthorizedException:
    # The user's access was revoked or expired: they must authorize again
    link = actions.get_authorization_link(
        connection_name="gmail",
        identifier="user_123",
    ).link
except ScalekitToolException as e:
    # The app rejected the call
    print(e.tool_error_code, e.tool_error_message, e.execution_id)
except ScalekitServerException as e:
    # Scalekit rejected the request
    print(e.http_status, e.error_code, e.message)
```

Node.js:

```ts
import {
  ScalekitServerException,
  ScalekitToolUnauthorizedException,
  isToolException,
} from '@scalekit-sdk/node';

try {
  const result = await actions.executeTool({
    toolName: 'gmail_fetch_mails',
    connector: 'gmail',
    identifier: 'user_123',
    toolInput: { query: 'is:unread' },
  });
} catch (e) {
  if (e instanceof ScalekitToolUnauthorizedException) {
    // The user's access was revoked or expired: they must authorize again
    const { link } = await actions.getAuthorizationLink({
      connectionName: 'gmail',
      identifier: 'user_123',
    });
  } else if (isToolException(e)) {
    // The app rejected the call
    console.log(e.toolErrorCode, e.toolErrorMessage, e.executionId);
  } else if (e instanceof ScalekitServerException) {
    // Scalekit rejected the request
    console.log(e.httpStatus, e.errorCode, e.message);
  } else {
    throw e;
  }
}
```

The SDKs raise a tool exception for `TOOL_ERROR`: `ScalekitToolUnauthorizedException` for 401, `ScalekitToolForbiddenException` for 403, `ScalekitToolRateLimitException` for 429, and `ScalekitToolException` for the rest. Each carries `tool_error_code` and `execution_id` (`toolErrorCode` and `executionId` in Node.js).

Next: [Pagination](https://docs.scalekit.com/agentkit/reference/pagination.md)


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
