> **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 40+ 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/)

---

# Error handling

Catch and respond to invalid tokens, bad requests, missing resources, and rate limits on the Scalekit REST API.
{/* Keep #error-handling for the URL Patrick opened. */}
<div id="error-handling"></div>

Catch and respond to invalid tokens, bad requests, missing resources, and rate limits.

A failed REST call returns an HTTP status and a JSON body. Official SDKs raise typed exceptions for the same failures. Use the body, or the exception fields, to choose a response.

For the endpoint catalog, open the [API reference](/apis/#description/overview). Product catalogs live at [SaaSKit APIs](/saaskit/apis/#description/overview) and [AgentKit APIs](/agentkit/apis/#description/overview).

## Read the error body

| Field | Meaning |
|---|---|
| `code` | gRPC (remote procedure call) status **number**. This is not the HTTP status. Example: `16` means Unauthenticated. |
| `message` | Human-readable reason. Safe for logs. Do not show raw messages to end users. |
| `details[].error_code` | Stable slug to switch on, such as `UNAUTHENTICATED`. |
| `details[].validation_error_info` | Present on some `400` responses. Lists field violations (`field`, `description`, `constraint`). |
| `details[].tool_error_info` | Present when a tool call fails. Includes `execution_id`, `tool_error_message`, and `tool_error_code`. |
| `details[].help_info` | Optional documentation links when the server attaches them. |

Scalekit does not return a request ID or a dashboard log URL in this envelope.

```json title="401 when the bearer token is missing"
{
  "code": 16,
  "message": "Token empty",
  "details": [
    {
      "@type": "type.googleapis.com/scalekit.v1.errdetails.ErrorInfo",
      "error_code": "UNAUTHENTICATED"
    }
  ]
}
```

Switch on `details[].error_code`. Do not switch on `code` alone.

## HTTP status codes

- Codes in the `2xx` range mean success.
- Codes in the `4xx` range mean the request failed given the data you sent. Fix the request. Do not retry the same payload, except for `429`.
- Codes in the `5xx` range mean a server fault. Retry with backoff. Treat the result as unknown until a later call succeeds.

| HTTP | Typical `error_code` | Meaning |
|---|---|---|
| `200` / `201` | n/a | Success |
| `400` | `INVALID_ARGUMENT`, `BAD_REQUEST` | Invalid request or validation failure |
| `401` | `UNAUTHENTICATED` | Missing or invalid bearer token |
| `403` | `PERMISSION_DENIED`, `FORBIDDEN` | Authenticated, but not allowed |
| `404` | `NOT_FOUND`, `RESOURCE_NOT_FOUND` | Resource does not exist |
| `409` | `RESOURCE_ALREADY_EXISTS` | Duplicate resource |
| `429` | `RATE_LIMITED`, `TOO_MANY_REQUESTS` | Rate limit exceeded |
| `500` | `INTERNAL_ERROR` | Server error |

## Catch exceptions

SDKs translate non-success HTTP responses into exceptions. Catch the specific type first, then fall back to the base server exception.

### Node.js

```ts title="catch-api-errors.ts"

  ScalekitNotFoundException,
  ScalekitUnauthorizedException,
  ScalekitTooManyRequestsException,
  ScalekitServerException,
} from '@scalekit-sdk/node'

try {
  // Your Scalekit SDK call
  await scalekit.organization.listOrganization({ pageSize: 30 })
} catch (err) {
  if (err instanceof ScalekitUnauthorizedException) {
    // Get a new access token. Do not retry the same bearer token.
  } else if (err instanceof ScalekitNotFoundException) {
    // Create the resource or return not-found to the caller.
  } else if (err instanceof ScalekitTooManyRequestsException) {
    // Back off. Read err.errorCode: RATE_LIMITED vs TOOL_ERROR.
  } else if (err instanceof ScalekitServerException) {
    // Log err.message and err.errorCode. Retry only on 5xx or 429.
  } else {
    throw err
  }
}
```

### Python

```py title="catch_api_errors.py"
from scalekit.common.exceptions import (
    ScalekitNotFoundException,
    ScalekitUnauthorizedException,
    ScalekitTooManyRequestsException,
    ScalekitServerException,
)

try:
    # Your Scalekit SDK call
    scalekit_client.organization.list_organizations(page_size=30)
except ScalekitUnauthorizedException:
    # Get a new access token. Do not retry the same bearer token.
    pass
except ScalekitNotFoundException:
    # Create the resource or return not-found to the caller.
    pass
except ScalekitTooManyRequestsException as e:
    # Back off. Read e.error_code: RATE_LIMITED vs TOOL_ERROR.
    print(e.error_code, e.message)
except ScalekitServerException as e:
    # Log e.message and e.error_code. Retry only on 5xx or 429.
    print(e.error_code, e.http_status)
```

### Go

```go title="catch_api_errors.go"
_, err := scalekitClient.Organization().ListOrganization(ctx, &scalekit.ListOrganizationOptions{
    PageSize: 30,
})
if err != nil {
    // Inspect the error. Retry only on HTTP 429 or 5xx.
    // Do not retry the same payload on 400, 401, 403, 404, or 409.
    log.Printf("scalekit API error: %v", err)
    return err
}
```

### Java

```java title="CatchApiErrors.java"
try {
    scalekitClient.organizations().listOrganizations(30, "");
} catch (ScalekitException error) {
    // Inspect the error. Retry only on HTTP 429 or 5xx.
    // Do not retry the same payload on 400, 401, 403, 404, or 409.
    System.err.println(error.getMessage());
}
```

Typed exception names and tool-specific types are listed on [AgentKit Node error handling](/agentkit/sdks/node/errors/) and [AgentKit Python error handling](/agentkit/sdks/python/errors/).

## Choose a response

| `error_code` | Problem | What to do |
|---|---|---|
| `UNAUTHENTICATED` | Missing header (`Token empty`), bad `Bearer` value (`Invalid Token`), or expired token (`token expired`) | Request a new token. Do not retry the same token. See [Authenticate with the Scalekit API](/guides/authenticate-scalekit-api/). |
| `INVALID_ARGUMENT` / `BAD_REQUEST` | The request is malformed or fails validation | Fix the fields. Do not retry the same payload. |
| `PERMISSION_DENIED` / `FORBIDDEN` | The caller is authenticated but not allowed | Change roles, scopes, or the target resource. |
| `NOT_FOUND` / `RESOURCE_NOT_FOUND` | The resource does not exist | Create it, or return not-found to your user. |
| `RESOURCE_ALREADY_EXISTS` | A create conflicts with an existing resource | Use the existing ID, or pick a new unique value. |
| `RATE_LIMITED` / `TOO_MANY_REQUESTS` | A Scalekit service rejected the call | Back off, then retry. See [API rate limits](/reference/rate-limits/). |
| `TOOL_ERROR` | A tool call failed | Read `tool_error_info.tool_error_code`. An inner `RATE_LIMITED` is the upstream provider. |
| `INTERNAL_ERROR` | A server fault | Retry with backoff. Treat the result as unknown until a later call succeeds. |

`/api/v1` accepts a bearer access token from client credentials. It does not accept an API key header.

## Validation errors

Some `400` responses include `validation_error_info.field_violations`. Each violation names the field, a description, and the constraint that failed. The JSON below is an example of that shape.

```json title="Example validation error shape"
{
  "code": 3,
  "message": "Validation error",
  "details": [
    {
      "@type": "type.googleapis.com/scalekit.v1.errdetails.ErrorInfo",
      "error_code": "INVALID_ARGUMENT",
      "validation_error_info": {
        "field_violations": [
          {
            "field": "page_size",
            "description": "value must be less than or equal to 30",
            "constraint": "lte"
          }
        ]
      }
    }
  ]
}
```

## Tool errors

Tool execution sets the **outer** `error_code` to `TOOL_ERROR`. The inner slug is `tool_error_info.tool_error_code`. An upstream provider `429` still returns HTTP `429`.

```json title="Upstream provider rate limit on a tool call"
{
  "code": 8,
  "message": "tool execution failed - rate limited",
  "details": [
    {
      "@type": "type.googleapis.com/scalekit.v1.errdetails.ErrorInfo",
      "error_code": "TOOL_ERROR",
      "tool_error_info": {
        "execution_id": "exec_123",
        "tool_error_message": "rate limit",
        "tool_error_code": "RATE_LIMITED"
      }
    }
  ]
}
```

Read the outer code first. Then read `tool_error_info.tool_error_code` when the outer code is `TOOL_ERROR`.

## Handle 429s

When a service exceeds its limit, Scalekit returns HTTP `429`. The response body uses the same error envelope.

| Outer `error_code` | Meaning |
|---|---|
| `RATE_LIMITED` | A Scalekit service rejected the call |
| `TOO_MANY_REQUESTS` | Email, one-time password (OTP), or time-based one-time password (TOTP) traffic hit a limit |
| `TOOL_ERROR` with inner `tool_error_code` `RATE_LIMITED` | The upstream provider rate-limited the tool call |

Back off and retry with exponential backoff. Do not retry immediately.

Scalekit does not send a `Retry-After` header on 429 responses. Limits vary by service and account. For 429 handling in the SDKs, see [API rate limits](/reference/rate-limits/).

## Fix common 400s

Some `400` `INVALID_ARGUMENT` responses come from list and lookup URLs.

| Case | HTTP | `error_code` | Message |
|---|---|---|---|
| Bad `page_token` | `400` | `INVALID_ARGUMENT` | `invalid page token` or `Invalid cursor` |
| `:external` path plus an `id` query | `400` | `INVALID_ARGUMENT` | `ExternalId is required` |

Fix the URL or the query. Do not retry the same request.

For list parameters, see the operation in the [API reference](/apis/#description/overview). For external IDs, see [organization identifiers](/guides/external-ids-and-metadata/).


---

## 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 |
