Rate limits

Four different budgets can each return 429, and every one sets Retry-After in seconds. They are counted on separate ledgers, so a response can be far from the request-rate limit and still be refused by one of the others. The code in the error body says which.

Rate limits
codeCounts
rate_limit_exceededRequests per minute, per credential.
create_rate_limitedRun submissions per minute, per account.
filter_rate_limitedFiltered catalog queries per hour, free tier.
spec_rate_limitedDistinct components read from the datasheet endpoint, per account.
too_many_streamsConcurrent live streams, per account.

Request rate limit

  • 1000 requests per minute per credential, burst allowance 50.
  • Over the limit: HTTP 429, code rate_limit_exceeded, Retry-After header in seconds.
  • Every credentialed /v1/ response, success or error, carries these headers:
Request rate limit
HeaderMeaning
X-RateLimit-LimitSustained requests-per-minute limit for this credential.
X-RateLimit-RemainingTokens remaining in this bucket: 0 on a rate_limit_exceeded 429; the other three 429s use separate budgets and may leave a positive balance here.
X-RateLimit-ResetUnix timestamp marking the end of the current rate-limit window.

The SDK retries 429 with backoff and honors Retry-After. Default: max 3 retries, exponential backoff (0.5 s, 1 s, 2 s) plus jitter. Configure with Client(max_retries=...).

Contact support for higher per-key limits on a specific integration.

Submission limits

Per account, per minute, counted separately by endpoint:

  • POST /v1/sweeps — 30 per minute.
  • POST /v1/dynamic-simulations — 30 per minute.
  • POST /v1/simulations — 120 per minute.

Over the limit: HTTP 429, code create_rate_limited, Retry-After header. The body carries retry_after_s, limit_per_minute, and kind.

The limit counts submissions, not running work. A submission that parks the run (launch_intent: "queue") counts the same as one that dispatches it.

Validating and reserving a large grid is real work on the request path, and an unpaced burst of large sweep submissions degrades latency for every endpoint on the account. A client that submits a queue of studies should pace itself rather than discover the wall.

One sweep carrying a component_axes axis replaces a loop of single-point submissions, and it is one request rather than hundreds. See Sweeps.

Component datasheet budget

GET /v1/components/{id}/specs on a catalog motor or battery is metered on a budget of distinct components, per account, on a ledger separate from the request-rate bucket. Reading your own custom component, or any propeller, is not metered and carries no budget headers.

  • Budget: 60 distinct components per hour and 300 distinct components per day.
  • It is account-wide, across the dashboard and every API key.
  • A granted lease makes re-reads of that component free for 24 hours. Reading the same component again inside the lease costs nothing against the budget.

Response headers on a metered read, on 200 and on 429:

Component datasheet budget
HeaderMeaning
X-Spec-Budget-RemainingDistinct components still readable, the smaller of the hourly and daily remainders.
X-Spec-Budget-ResetSeconds until the binding window rolls over.

Over budget: HTTP 429, code spec_rate_limited, Retry-After header. The body carries retry_after_s, budget_per_hour, budget_per_day, remaining_hour, remaining_day.

The list endpoint costs nothing against this budget and already carries the headline datasheet numbers, including motor R. Rank and filter with GET /v1/components, then request /specs only for the rows you need. See Components.

Catalog filter budget

  • Free tier only: 120 filtered motor and battery catalog queries per hour.
  • Free-tier filter bounds snap to brackets rather than taking an exact value.
  • Over the limit: HTTP 429, code filter_rate_limited, Retry-After header.
  • Paid tiers are not subject to this budget.

Simulation execution concurrency

HTTP request rate and simulation execution capacity are separate controls.

Free runs one simulation at a time. Pro runs up to five.

The execution limit is account-wide. Dashboard submissions and every API key owned by the account share it. Extra dispatched runs stay queued and start when a slot frees. They do not return a concurrency 429.

Service-wide overload or maintenance protection can still pause new submissions. That control is independent of the paid plan's unlimited usage policy and the account execution queue.

GET /v1/users/me returns concurrency: {limit, active, queued} for the account so a client can read the shared queue directly. limit is the account execution-slot ceiling, active is the number of slots in use, and queued is the number waiting.

Live streams

Server-Sent-Events progress streams (GET /v1/simulations/{id}/stream and the sweep and dynamic equivalents) share a limit of 20 concurrent open streams per account. Opening more returns HTTP 429 with code too_many_streams.

Each stream has a maximum lifetime of 30 minutes. If the run is still going when the stream reaches that limit, reconnect to keep watching. A stream always ends on its own when the run reaches a terminal state.

See Errors for the envelope and the per-code anchors, and Compute units for metering, which is a different thing from rate limiting.