Rate Limits

To ensure fair usage and system stability, Priostack limits how many requests each client IP address may send. The limit is applied by the firewall in front of the API, across all paths, whatever API key the requests carry.

Current Limits

LimitValue
Requests per minute300 per client IP
Window typeSliding 60-second window
ScopePer client IP address (not per API key), across all paths. Workers behind one IP share it.
Job activationNot exempt: every POST /api/v1/jobs/activate counts as 1 request, and it answers at once (there is no long polling)
Not countedThe agent network transports /mcp and /rpc, operator paths, and GET requests on public pages from recognised search crawlers
Over the limit429 and a strike. Three strikes ban the IP for 2 hours, doubling on each repeat offence up to 7 days

Rate Limit Headers

Every /api/ response that reaches the API includes the following headers:

HeaderDescriptionExample
X-RateLimit-Limit Maximum requests allowed per window 300
X-RateLimit-Window Window duration in seconds 60
X-RateLimit-Reset The next whole minute, as a Unix timestamp in seconds. Advisory only: the real window slides, so it does not tell you when your own requests age out 1746456780

No header reports how many requests you have left.

# Inspect rate limit headers
curl -i -H "X-API-Key: your_key" \
  https://priostack.com/api/v1/topology

HTTP/1.1 200 OK
X-RateLimit-Limit: 300
X-RateLimit-Window: 60
X-RateLimit-Reset: 1746456780
Content-Type: application/json
...

Handling 429 Responses

When the limit is exceeded, the firewall answers before the request reaches the API: HTTP 429 with a plain-text body, no JSON, no Retry-After and no rate-limit headers.

HTTP/1.1 429 Too Many Requests
Content-Type: text/plain; charset=utf-8

Too Many Requests

Rejected requests still count in the window, so retrying at once keeps you limited, and every 429 is a strike. After three strikes the IP is banned for 2 hours (doubling on each repeat, up to 7 days), and during a ban every request answers 403 with an empty body. Stop sending and wait about 60 seconds. A request that got 429 never reached the API, so it is safe to send again.

Retry Logic in Go

A retry must build a fresh *http.Request for each attempt: a request whose body was already read sends nothing the second time.

// callWithRetry builds a fresh request for every attempt, so a POST body is
// never sent empty on a retry. A 429 means the request never reached the API.
func callWithRetry(ctx context.Context, newReq func(context.Context) (*http.Request, error)) (*http.Response, error) {
    for attempt := 0; attempt < 5; attempt++ {
        req, err := newReq(ctx)
        if err != nil {
            return nil, err
        }
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return nil, err
        }
        if resp.StatusCode != http.StatusTooManyRequests {
            return resp, nil
        }
        resp.Body.Close()

        // The 429 has no Retry-After, the window slides over 60 s and
        // rejected requests count too: wait a full window.
        wait := 61 * time.Second
        log.Printf("Rate limited. Waiting %s.", wait)
        select {
        case <-time.After(wait):
        case <-ctx.Done():
            return nil, ctx.Err()
        }
    }
    return nil, errors.New("exceeded max retry attempts")
}

// Usage: the body is rebuilt from the same bytes on every attempt.
func startOrder(ctx context.Context, orderID string) (*http.Response, error) {
    body, err := json.Marshal(map[string]interface{}{
        "bpmnProcessId": "order-processing",
        "variables":     map[string]interface{}{"orderId": orderID},
    })
    if err != nil {
        return nil, err
    }
    return callWithRetry(ctx, func(ctx context.Context) (*http.Request, error) {
        req, err := http.NewRequestWithContext(ctx, http.MethodPost,
            "https://priostack.com/api/v1/process-instances", bytes.NewReader(body))
        if err != nil {
            return nil, err
        }
        req.Header.Set("X-API-Key", os.Getenv("PRIOSTACK_API_KEY"))
        req.Header.Set("Content-Type", "application/json")
        return req, nil
    })
}

Retry Logic in Python

import time
import requests

def call_with_retry(url, headers, payload, max_attempts=5):
    for attempt in range(max_attempts):
        # requests builds a fresh request, body included, on every call.
        response = requests.post(url, headers=headers, json=payload, timeout=15)
        if response.status_code != 429:
            return response

        # The 429 has no Retry-After, the window slides over 60 s and
        # rejected requests count too: wait a full window.
        print("Rate limited. Sleeping 61s...")
        time.sleep(61)

    raise Exception("Max retry attempts exceeded")

Tips for High-Volume Usage

1. Pause Between Empty Polls

POST /api/v1/jobs/activate answers at once, with {"jobs": []} when nothing waits: there is no long polling. Sleep 1 to 5 seconds after an empty or failed poll, and ask for several jobs per call with maxJobsToActivate. A worker that pauses 2 seconds between empty polls sends about 30 requests a minute.

# One poll; pause 2 s before the next one when the answer is empty
curl -X POST "https://priostack.com/api/v1/jobs/activate" \
  -H "X-API-Key: your_key" \
  -H "Content-Type: application/json" \
  -d '{"type": "my-worker", "maxJobsToActivate": 10}'

2. Pace Instance Starts

There is no batch endpoint for starting instances. Start them one call each with POST /api/v1/process-instances: each call costs 1 credit and counts toward the 300 requests per minute, so spread large runs over time.

3. Cache Process Definitions

Avoid calling GET /api/v1/process-definitions on every request. Start instances by bpmnProcessId, which does not change: a changed redeploy creates a new version, and new starts always use the newest one.

4. Monitor Your Usage

No header reports your remaining requests, so count them in your application per outgoing IP, log a warning above 240 a minute (80% of the limit), and throttle before you reach it.

Need higher limits? Contact sales@priostack.com.