Windpaint
Overview

Rate limits

Per-organization limits on concurrent jobs, submits per minute and requests per minute, the tiers that set them, the headers that report them, and how to handle 429.

Windpaint limits how fast an organization can submit work and how many jobs it can have running at once. Limits are set by your organization’s tier, which goes up as you buy credits.

Tiers

Your tier depends on the total you’ve paid for credit top-ups over the life of the organization. Free signup credits don’t count.

TierLifetime purchasesConcurrent jobsSubmits per minute
1None (free credits only)560
2$50 or more10120
3$500 or more25300
4$2,000 or more50600

Every tier also allows 1,200 requests per minute per API key across all authenticated endpoints. Requests from the dashboard count per user instead of per key.

A new tier applies from the first request after a top-up is paid. If you need higher limits than tier 4, contact support; Windpaint can set custom limits for your organization, and its tier then reads override.

What counts

LimitCounted perWhat counts toward it
Concurrent jobsOrganizationGeneration jobs in status queued or in_progress.
Submits per minuteOrganizationPOST /v1/generation/capabilities/{capability}, POST /v1/generation/models/{model} and POST /v1/workflows/{slug}/runs.
Requests per minuteAPI key (or dashboard user)Every authenticated request, including submits, uploads and the calls on this page.
  • A submit counts toward both the submits limit and the requests limit. Uploads count only toward requests.
  • A submit is refused while your organization is at its concurrency limit.
  • Starting a workflow run counts as one submit and is refused at the concurrency limit. The jobs its steps start later aren’t limited, so a running workflow can take your organization above its concurrency limit for a while.
  • Minute windows are fixed and aligned to the clock: they reset at the start of each minute, not 60 seconds after your first request.

Headers

Authenticated responses carry the state of the limit that applied to the request:

HeaderMeaning
X-RateLimit-LimitThe allowance of the limit that applied. On submit endpoints this is the submits-per-minute limit; elsewhere it’s requests per minute.
X-RateLimit-RemainingWhat’s left in the current window.
X-RateLimit-ResetSeconds until the window resets. A number of seconds, not a timestamp.
Retry-AfterSeconds to wait before retrying. Only on 429.

On a concurrency 429, X-RateLimit-Limit is your concurrency limit, X-RateLimit-Remaining is 0, and X-RateLimit-Reset equals Retry-After.

Public routes (such as GET /v1/openapi.json and GET /v1/errors) aren’t rate-limited and send none of these headers. Browser clients can read them; they’re exposed through CORS.

429 responses

A request over a limit returns 429 in the standard error envelope:

{
  "error": {
    "code": "rate_limit.concurrency",
    "message": "Too many jobs in flight for the organization's tier. Retry once some finish.",
    "details": { "limit": 5, "current": 5, "retry_after": 5 },
    "request_id": "59630881.7c2e4a"
  }
}
CodeCausedetails
rate_limit.requestsThe requests-per-minute or submits-per-minute allowance is spent.limit, current (the count including this request), retry_after
rate_limit.concurrencyYour organization has as many jobs queued or running as its tier allows.limit, current (jobs queued or running, plus other submits being processed at that moment), retry_after

Nothing is created or charged when a request is refused with 429.

To handle it, wait Retry-After seconds and send the request again. For rate_limit.requests, that’s when the window resets. For rate_limit.concurrency, Retry-After is a polling hint (5 seconds); what you’re waiting for is one of your jobs to finish, so watch your running jobs’ status or webhooks and submit when one completes. If you retry submits, send an Idempotency-Key so a retry never creates a second job.

GET /v1/billing/limits

Your organization’s tier, limits and current usage.

Permission: billing:read

curl https://api.windpaint.ai/v1/billing/limits \
  -H "Authorization: Bearer $WINDPAINT_API_KEY"
{
  "data": {
    "organization_id": "0b7c5e9a-2f4d-4a61-9c3e-8d2f1a6b7c40",
    "tier": 1,
    "tier_source": "purchases",
    "lifetime_purchases_cents": 0,
    "limits": { "concurrent_jobs": 5, "submits_per_minute": 60, "requests_per_minute": 1200 },
    "usage": { "concurrent_jobs": 0, "submits_this_minute": 0, "requests_this_minute": 1, "resets_in": 42 },
    "next_tier": {
      "tier": 2,
      "min_purchases_cents": 5000,
      "limits": { "concurrent_jobs": 10, "submits_per_minute": 120, "requests_per_minute": 1200 }
    },
    "overrides": { "tier": null, "concurrent_jobs": null, "submits_per_minute": null }
  }
}

The full field list is in the Billing reference. A few things to know:

  • usage.requests_this_minute is the calling key’s count (or your user’s, from the dashboard) and includes this call, which itself counts toward requests per minute.
  • usage.concurrent_jobs counts jobs already queued or running. It doesn’t include submits still being processed, so it can read one lower than details.current on a concurrency 429.
  • next_tier is null at tier 4 or when your limits are set by Windpaint (tier_source: "override").

The dashboard shows the same information under Settings → Billing → Rate limits.

Staying under the limits

  • Keep a client-side cap on jobs in flight equal to your tier’s concurrency limit, and submit the next job when one finishes.
  • Use webhooks, the event stream or Prefer: wait instead of tight polling loops. Status polls count toward requests per minute.
  • Spread a large batch over several minutes rather than sending it in one burst.