Skip to main content
An application makes one call. What happens when the provider rate-limits it, returns a 503, or splits the answer across forty pages is decided here — once, on the workspace service, and inherited by every SDK and MCP server that uses it. The outcome: no feature anyone on your team writes needs its own retry loop, token bucket, or page walker.
This is workspace config, and only workspace config. rate_limit, retry, and pagination are rejected in sdk.yaml and mcp.yaml — generated clients contain no local window, semaphore, retry loop, jitter, or sleep, and that is deliberate.

Where it goes

A version-level policy wins over the service default for that version. Apply it the ordinary way:

Rate limits and concurrency

rate_limit.policies is a list of dimensions enforced at the same time — a per-minute cap and a concurrency ceiling can coexist.
Start with mode: observe to learn a provider’s real ceiling before enforcing one. And because identity can be a shared credential family, a limit can be coordinated across every caller using the same provider account — something client-side limiting cannot do.

Retries

retry.rules is evaluated in order. Values within one predicate field are ORed, fields are ANDed, and the first matching rule’s action applies.
Retrying a write needs explicit body_replayability and idempotency_key predicates. A POST is not automatically idempotent, and guessing wrong duplicates real records.
retry_config is an accepted alternative spelling. Setting both is rejected.

Pagination

The Engine walks the pages and streams each one as a separate chunk; your client makes a single call.
A next-URL continuation needs an explicit same-origin or allowlist policy. Without one, a provider response could redirect the Engine at an unreviewed origin while carrying your credential.
Callers may only lower the ceiling, via max_pages. Strategy, tokens, paths and next URLs are never caller-owned.

Fixing a wrong base URL

When a spec declares the wrong server, or none at all:
base_url layers on top of the spec-derived value, which stays intact and inspectable underneath. server_variables binds imported OpenAPI server-template variables — keys must name variables declared on the effective operation server, and at most 128 entries are allowed.
These values are literal only. They are validated against ^[A-Za-z0-9._~-]{1,512}$, so a ${bucket.env.KEY} reference is rejected here. Dynamic bucket values are an app-level feature: use an SDK or MCP injection with location: server_variable. Where both exist, this workspace map is the final authority.
A connection-resource forced binding (${resource.base_url}) still wins at dispatch time. execution_policy.base_url is the static fallback beneath it, not a way to override a live connection’s routing. server_variables never leaves your Engine, even with public: true, and workspace sync preserves the local map. There is no owner-editable path for default_headers today.

Local effect vs publishing

These are two independent things, and confusing them is the most common mistake here. So a non-owner declaring execution_policy without public is not a no-op — it is the normal case: enforce this here, affect nobody else. Only the owning account can set public: true; the Engine rejects it from anyone else at apply. Other Engine deployments that inherit a published value get a registry_execution_policy_changed notification the next time their poller runs.
Do not confuse this public with a service’s own top-level public, which controls Registry visibility of the service page. Both are owner-only and both are called public, but they publish entirely different things.

Clearing an override

reset drops your local override for that tier back to the Registry-sourced snapshot. It touches nothing you have published — there is no unpublish; a prior publish stands until superseded. Pair reset with nothing else.

Know when a policy changes

Inheriting a Registry default means someone else can change it. That is what the notification is for.