Request-level failures use an error plus request ID:

JSON
{
  "error": {
    "code": "invalid_input",
    "message": "input.to must contain at least one email address.",
    "retryable": false
  },
  "requestId": "req_01JZ6X..."
}

Failed and cancelled execution records carry the same normalized error shape under error.

HTTP conditionTypical normalized code
Malformed JSON, unknown field, schema failureinvalid_input
Missing/invalid project authauth_missing or auth_expired
Missing scopeauth_insufficient_scope
Scoped resource missingnot_found
Timeouttimeout
Rate limitrate_limited
Upstream 5xx/network failureprovider_unavailable
Other provider rejectionprovider_error
Unsupported deployment/adapter surfacenot_supported
Restart after provider dispatch may have begunexecution_interrupted
User-requested execution cancellationexecution_cancelled

Use the code and retryable field rather than inferring behavior from status alone. The complete remediation guide is Errors.

Rate limits and quotas

The executor applies project-scoped limits after API-key authentication. All keys for the same project share the same buckets, including keys pinned to one end user. This prevents creating extra keys from multiplying a project's capacity. The in-memory implementation is the zero-configuration default; deployments can inject a distributed implementation through the asynchronous RateLimiter seam.

PolicySustained defaultBurst/default limitScope
Standard authenticated requests120 requests/minute240 requestsProject
POST /v1/execute requests60 requests/minute120 requestsProject
Daily execution requestsOffOffProject, UTC day
Outbound adapter concurrencyUnlimitedUnlimitedProject + toolkit

The standard and execute token buckets are independent. A busy execution workload therefore does not exhaust the cheaper read bucket used by routes such as GET /v1/executions. Daily quota windows reset at 00:00 UTC when enabled. Outbound concurrency remains unlimited unless a provider manifest declares limits.maxConcurrentExecutionsPerProject.

When a manifest cap is full, a synchronous request is rejected before allocation with HTTP 429 and a one-second retry hint. An accepted asynchronous execution remains pending and waits for a toolkit permit inside the task queue. Permits are released after both successful and failed adapter calls. The stock semaphore is process-local; multi-replica deployments should inject a distributed implementation if they need a global concurrency ceiling.

Rate-limited request failures use the RFC 001 pre-allocation envelope:

JSON
{
  "error": {
    "code": "rate_limited",
    "message": "Execution request rate limit exceeded.",
    "retryable": true,
    "retryAfter": 1
  },
  "requestId": "req_01JZ6X..."
}

Provider HTTP 429 responses happen after allocation instead. They are normalized to the same rate_limited code, preserve a known retryAfter, and appear on the failed execution record.

Response headers

HeaderMeaning
RateLimit-LimitToken-bucket burst capacity, or the fixed-window quota
RateLimit-RemainingWhole requests remaining at response time
RateLimit-ResetUnix timestamp in seconds when the bucket fully refills or the quota window resets
Retry-AfterWhole seconds to wait when the executor has a retry duration

The same-origin dashboard executor proxy preserves exactly these four rate and retry headers: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, and Retry-After. It does not use wildcard prefix forwarding; unrelated upstream headers remain stripped and responses remain Cache-Control: no-store.

Retry-After is not guaranteed on every HTTP 429. The executor includes it when a retry duration is known; usage-gate quota denial intentionally may omit it. Failed execution records use the normalized JSON field error.retryAfter, measured in seconds.

Environment configuration

VariableDefaultPurpose
EYEBALL_RATE_LIMIT_REQUESTS_PER_MINUTE120Standard-route sustained refill rate
EYEBALL_RATE_LIMIT_REQUEST_BURST240Standard-route token capacity
EYEBALL_RATE_LIMIT_EXECUTE_PER_MINUTE60Execute-route sustained refill rate
EYEBALL_RATE_LIMIT_EXECUTE_BURST120Execute-route token capacity
EYEBALL_RATE_LIMIT_DAILY_EXECUTIONSOffDaily project execution-request quota; a positive integer enables it, while 0 or off disables it

The TypeScript SDK waits for retryAfter and retries exactly once for idempotent GET and HEAD requests. It never automatically retries POST, PATCH, or DELETE, including tool execution and file upload.