Rate limits

Rate limits describe request limits for API use.

Rate limits protect Dreamstate API and MCP access from request bursts. Treat a 429 response as a request-level refusal, not as an empty catalog result, a missing provider record, or a completed capability run.

The active limit depends on the authenticated identity and on the capability family. Discovery, ordinary API work, externally effective work, and credential or root work can therefore receive different refusal codes even when they originate from the same workspace.

Limit families and their scopes

The API-key middleware applies a per-key limiter, a workspace limiter, and additional family limiters for external-effect and credential or root capabilities. The pre-auth middleware runs before API-key lookup. It keys verified signed-in sessions by user id and other traffic by client IP.

All listed API and pre-auth limits use a 60,000 millisecond window. The table gives the actual families exported by the middleware, not a suggested client configuration. A client should read the returned refusal rather than assume that its next request used the ordinary per-key bucket.

FamilyLimitKeyed byRefusal codeWhy this bucket exists
Per API key100 requests per minuteOne authenticated API keyrate_limitedPrevents a single key from exhausting the standard request budget.
Per workspace120 requests per minuteAll authenticated keys in one workspaceworkspace_rate_limitedKey rotation does not multiply a workspace budget.
External effect20 requests per minuteA workspace calling a capability classified as external effectexternal_effect_rate_limitedCalls that create an external effect use a lower family budget.
Credential or root10 requests per minuteA workspace calling a credential or root capabilitycredential_root_rate_limitedCredential and root capability requests use the lowest family budget.
Pre-auth IP300 requests per minuteAnonymous, invalid, or API-key-shaped traffic from one client IPip_rate_limitedThe pre-auth path limits traffic before API-key lookup.
Pre-auth session2400 requests per minuteA verified signed-in session usersession_rate_limitedA verified session is keyed by user id instead of a shared network address.

Two API keys in one workspace share the workspace budget. The middleware test sends seventy ordinary requests with one key and fifty with another, then verifies that the next workspace request receives workspace_rate_limited. A second key is not a way to continue after a workspace refusal.

An external-effect capability can be refused before the ordinary per-key or workspace ceiling. The middleware identifies external-effect ids from catalog effect classification, then applies the workspace family bucket. Credential and root capability ids use a separate family bucket with their own code.

Read the 429 response

Every refusal from this middleware returns HTTP 429 and sets Retry-After. The JSON body carries the stable code, a readable message, the same retry interval in seconds, the window duration, and the limit that applied. Persist the code with your request telemetry so a client can distinguish a key issue from a workspace or family burst.

Representative API-key refusal
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{
  "error": {
    "code": "rate_limited",
    "message": "100 requests per minute per API key exceeded",
    "retry_after_seconds": 60,
    "window_ms": 60000,
    "limit": 100
  }
}
Response fieldTypeWhen presentMeaningDefault
HTTP statusnumberAlways on refusal429 identifies a rate-limit refusal.Not applicable
Retry-AfterHTTP response header, integer stringAlways on refusalThe number of seconds before a retry can be attempted.Not applicable
error.codestringAlways on refusalThe stable code for the limiter that refused the request.Not applicable
error.messagestringAlways on refusalA readable explanation of the exceeded family budget.Not applicable
error.retry_after_secondsnumberAlways on refusalThe retry interval represented in the JSON response.Not applicable
error.window_msnumberAlways on refusalThe window duration used by the limiter.60000
error.limitnumberAlways on refusalThe request count allowed for the family in that window.Family-specific

The retry interval is calculated from the limiter reset time when one exists, otherwise from the configured window. It is at least one second. In the API-key middleware test, a per-key refusal returns a 60 second JSON interval and a Retry-After: 60 response header.

Use the fields in the response instead of deriving an interval from local clock assumptions. The server may be shared across instances, and the middleware is designed so that its enforced budget is not only a local process counter.

The response limit names the bucket that stopped the request. Keep that number with the error code when you report an integration issue. A later request may reach a different bucket, so an earlier ordinary-read refusal does not prove that an external-effect call will receive the same code.

Retry without changing the request

Wait for the returned retry interval before scheduling another request. Preserve the same capability parameters, mode, quote, turn token, and idempotency identity when the operation itself is a retry. Rate limiting changes when a request can be retried. It does not change the fetched contract for the capability.

If your client owns a queue, pause only the work that shares the refused bucket. A per-key refusal concerns that API key. A workspace or family refusal can affect other workers in the same workspace, so continuing those workers immediately can create more 429 responses.

This run returned HTTP 429. Read its error code and retry_after_seconds, identify whether the key, workspace, external-effect, or credential/root family was limited, then wait for that interval before retrying the same request.

The example intentionally retries the same request after the server-provided interval. For a live mutation, pair this behavior with Idempotency so a request that was accepted before the network response is not treated as a new operation.

Understand the classification

A reply through unibox.context.thread_reply_send is classified as an external effect; tables.contacts.list is standard. The middleware classifies workspace.api_keys.create and workspace.account.delete as credential or root work. These examples explain the family model, not a substitute for discovering the current capability contract before a run.

REST run paths expose the capability id in the path. MCP requests expose it inside a tools_run call body. The limiter extracts that id to classify the request family, which keeps the same family logic available through both doors.

When it goes wrong

A 429 is recoverable after the stated interval. The correct repair is to use the returned code and retry data. Do not fall back to a guessed capability id, do not silently switch the workspace, and do not treat the refusal as a completed run.

If the code is rate_limited, reduce or queue requests made by the relevant API key. If it is workspace_rate_limited, coordinate work across keys in that workspace. If it is external_effect_rate_limited or credential_root_rate_limited, pause the respective family rather than ordinary read work.

If an unauthenticated or invalid credential request receives ip_rate_limited, wait before retrying from that client IP. A verified signed-in session uses session_rate_limited instead, because the pre-auth middleware can key the request by its verified session user. Authentication errors and capability input errors have their own responses, so retain the exact code.

After the interval, send one controlled retry and inspect its response. The test advances the clock beyond one window and verifies that the per-key request succeeds again. That is the client behavior to model: wait, retry the same intended operation, then decide from the new envelope.

Dreamstate documentation

Start

Guides

Surfaces

Workspace

Knowledge

Data

Execution

Providers

Plays and templates

Safety and cost

API reference