Cheela Labs
REFERENCE

Errors and limits

Every failure Cheela can report, and every ceiling it enforces.

Error shape

Error
{
  "error": {
    "code": "validation_error",
    "message": "Invalid execution request",
    "details": { "fieldErrors": { "messages": ["Required"] } }
  }
}

details appears on validation failures and is otherwise absent. Internal errors never carry it — an unhandled failure returns a generic message rather than echoing anything about the infrastructure that produced it.

API error codes

CODESTATUSCAUSE
validation_error400The body failed its schema. `details` names the fields.
unauthorized401Missing or invalid key, a key not valid on this route, or a capability requiring an end user called anonymously.
forbidden403The resource belongs to another owner, or creating it would exceed your plan's runtime ceiling.
not_found404Unknown runtime, capability, execution, or project. Also returned instead of 403 where a 403 would confirm an id exists.
rate_limit_exceeded429Too many requests, or the hourly execution allowance is spent.
execution_error502A capability call through the public broker failed.
internal_error500Something unexpected. Logged server-side.
404 is sometimes deliberate

Fetching another owner’s execution returns 404, not 403 — a 403 would confirm the id exists and turn the route into an enumeration oracle. The public broker does the same for unknown runtimes, unknown capabilities, and runtimes with no endpoint: one shape for all three, so an anonymous caller cannot probe which runtimes exist.

Two that look like errors and are not

  • A failed execution returns 200. The request succeeded; the execution did not. Read status and error from the body.
  • A capability that throws returns 200 from your handler. Cheela turns it into a tool_result error the model can read and recover from.

Signature failures

Returned by verifyCheelaSignature as result.reason, and by the handlers as a 401 body.

REASONWHAT TO CHECK
missing_headersOne of the four x-cheela-* headers did not arrive. Usually a proxy or CDN stripping unknown headers.
runtime_mismatchThe signature was issued for a different runtime than the one you pinned with `runtimeId`.
timestamp_invalidThe timestamp header was not a number.
timestamp_outside_toleranceMore than five minutes of clock skew, in either direction. Check NTP on your server.
nonce_replayedA genuine replay — or several instances behind a load balancer without a shared nonce store.
signature_mismatchThe wrong secret, or far more often a body that was parsed and re-serialized before verification.

The last one is the one to suspect first. Full detail in Serve capability calls.

Handler responses

What the runtime handlers return to Cheela.

STATUSBODYMEANING
200{ output }Ran and returned.
200{ output: null, error }The capability threw. The loop continues.
400{ error: "invalid_json" }Body was not JSON.
400{ error: "missing_capability" }No capability named in the body.
400{ error: "raw_body_required" }Express only. Mounted with express.json() instead of express.raw().
401{ error: reason }Verification failed. One of the six reasons above.

Client errors

CLASSTHROWN WHEN
CheelaNetworkErrorThe request never completed. Carries the cause.
CheelaAuthError401 or 403. Carries `status`.
CheelaApiErrorAny other non-2xx. Carries `status`.
CheelaConfigErrorClient config failed to parse — usually a missing key.
ValidationErrorFrom @cheela/sdk, when a schema rejects a value.

CheelaClientError is the base for the first three. Catch it to catch everything transport-related.

Plan limits

FREEPROENTERPRISE
Executions / hour1002,000unlimited
Rollover window2 hours24 hours
Burst capacity20048,000
Runtimes110unlimited
Capability calls are counted, not capped

An execution counts once no matter how many steps the model takes. Capability calls appear in usage and analytics, but they are not a limit — a ceiling nobody enforces is worse than no ceiling, because it drifts away from what was sold.

How quota works

A token bucket, not a per-hour counter. Capacity is your hourly rate multiplied by your rollover window, so a quiet hour pays for a busy one — which is the only way to express “you may burst to 48,000 but only sustain 2,000/hour”.

Usage responses carry periodStart and periodEnd so an interface can say when the allowance resets rather than leaving people to guess.

Broker sub-allowance

Anonymous calls through the public broker draw on a smaller share as well as the main allowance. Without it, traffic against a public manifest could exhaust the owner’s whole quota and take their own widget down with it — a remote denial of service against anyone who publishes one.

Runtime ceilings

Only creating a new runtime counts. Re-registration never does, socheela deploy keeps working for someone sitting exactly at their limit. Owners already over a limit keep everything they have — the check gates creation, it never deletes.

Execution bounds

BOUNDDEFAULTEFFECT
Steps per execution25The agent loop stops with finishReason: "length".
Parallel capability callscappedTool calls in one step are dispatched concurrently up to a fixed ceiling.
Signature tolerance5 minutesClock skew allowed in either direction.
Page size200Maximum `limit` on executions and traces.
Allowed origins50Maximum entries per runtime.

There is no quota check between steps, deliberately. A step consumes nothing a quota could refuse, so the cost of one execution is bounded structurally by the step budget and the parallel-call cap instead — re-checking per step meant aggregating the owner’s whole usage once per step rather than once per execution.

Reaching the step budget is "length", not "error": the loop hit its ceiling, which is a budget outcome rather than a failure.