Errors
The error envelope every failed call returns, every code it can carry, and whether it's worth retrying.
Every failed call — through Arc0 MCP, the SDK, or
REST — returns the same shape. The SDK throws this as an Arc0Error; over
REST it's the JSON body.
{
"error": {
"code": "denied_by_policy",
"message": "salesforce:destructive is denied by pol_acme_support.",
"retryable": false
}
}approval_required and connection_expired add their own fields on top of this shape,
shown below.
Codes
| Code | HTTP status | Retryable | What to do |
|---|---|---|---|
denied_by_policy | 403 | No | A policy rule denies this call outright. Change the rule in Policies if it's wrong, or stop calling it. |
approval_required | 403 | Yes, after it's decided | An ask rule is holding the call. Wait for the approval.decided webhook or poll GET /v1/approvals/{id}, then retry the same call. |
connection_required | 409 | No | The user has no connection to this app. Send them an Arc0 Connect link. |
connection_expired | 409 | No, until reconnected | The refresh token is dead. The response includes a reconnect_url; send it to the user and retry once they finish. |
upstream_rate_limited | 429 | Yes | The app's own API rate-limited this call. Back off and retry. |
upstream_invalid_request | 400 | No | The app rejected the request shape. Fix input and try again. |
upstream_timeout | 504 | Yes | The app didn't respond in time. Retry with backoff. |
upstream_unavailable | 503 | Yes | The app's API is down. Retry with backoff. |
invalid_api_key | 401 | No | The key is wrong, revoked, or for the wrong environment. Check API keys. |
insufficient_access | 403 | No | The key's access level doesn't cover this call — for example, a Read-only key calling actions.run. Use a key with a higher level. |
The upstream_* codes also carry upstream_status, the HTTP status the app itself
returned, since it's often more specific than Arc0's own status.
approval_required
{
"error": {
"code": "approval_required",
"message": "stripe:destructive requires approval under pol_acme_support.",
"approval": {
"id": "apr_9k2m",
"requested_from": ["ops@acme.com"],
"status": "pending",
"expires_at": "2026-09-27T15:02:11Z"
},
"retryable": true
}
}See Approvals for how the request gets decided and what happens when you retry.
connection_expired
{
"error": {
"code": "connection_expired",
"message": "The connection to salesforce needs to be reconnected.",
"reconnect_url": "https://connect.arc0.ai/r/rc_7384l23otyjm",
"retryable": false
}
}The link works once and expires in 7 days, same as the reconnect links sent from the dashboard.
What isn't an error
A call that reaches the app and gets a normal application-level failure back — an invalid
Salesforce record id, for instance — isn't wrapped in this envelope. Arc0 only wraps calls
it stops or holds itself; whatever the app returns on a call that actually ran is passed
through as data.