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. |
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.
401withREAUTHENTICATION_NEEDED: the server rejected the user's credentials. The user must authorize again.403withFORBIDDEN: the server refused the call.400withINVALID_ARGUMENT: the tool returned an error result, or the server rejected the input.400withTOOL_ERROR: the server returned any other 4xx status.500withTOOL_ERROR: the server returned408,429or 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, 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 returns the app's status and headers unchanged, including its Retry-After.
Troubleshooting covers the errors users see while connecting.