GETTING STARTED

Rate limits

AlgaPSA enforces a token-bucket rate limit on every external /api/v1/* call. Each request consumes a token from a bucket scoped to your API key. When the bucket is empty AlgaPSA returns 429 Too Many Requests with Retry-After guidance. Successful responses also carry the current limit and remaining tokens so you can self-throttle.

Defaults

Burst120 requests
The bucket holds up to 120 tokens. A short flurry — a parallel Promise.all, a batch sync — can spend the full burst before the steady-state limit kicks in.
Sustained60 / min
Tokens refill at one per second. After a burst the practical sustained ceiling is one request per second per API key.
Bucket scopetenant + API key
Every API key in your tenant has its own bucket. One runaway integration cannot starve another. If you need to keep environments separate, give them separate keys.

Defaults are configurable per tenant and per key — if you have a documented need for higher headroom, contact support with the call pattern you expect.

Response headers on every request

Both 2xx and 429 responses include rate-limit headers. Track these in your client to slow down before you hit the limit instead of waiting for a 429.

X-RateLimit-Limitinteger
The bucket size for this key — the burst capacity, e.g. 120.
X-RateLimit-Remaininginteger
Tokens left after this request was counted. -1 means AlgaPSA could not reach Redis to count the request — the request was allowed through (see fail-open).
X-RateLimit-ResetISO-8601
Timestamp when the bucket is expected to be full again. Useful for scheduling resumes.

The 429 response

Throttled requests return 429 Too Many Requests with a JSON envelope and an additional Retry-After header in seconds.

429 response
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 4
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2026-05-07T15:24:15.482Z

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests",
    "details": {
      "retry_after_ms": 3812,
      "remaining": 0
    }
  }
}

Backing off cleanly

The simplest correct strategy is: read Retry-After on a 429 and wait that long, with jitter, before retrying. For sustained pressure, drop your request rate proactively when X-RateLimit-Remaining approaches zero.

client.ts
async function callAlga(path: string, init?: RequestInit): Promise<Response> {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch(`https://algapsa.com${path}`, {
      ...init,
      headers: { "X-API-Key": process.env.ALGA_API_KEY!, ...(init?.headers ?? {}) },
    });

    if (res.status !== 429) return res;

    const retryAfter = Number(res.headers.get("Retry-After") ?? "1");
    const jitter = Math.random() * 250;
    await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitter));
  }
  throw new Error("rate limited after 5 attempts");
}

Fail-open behavior

If AlgaPSA cannot reach the rate-limit store (Redis outage, network partition), the request is allowed through and X-RateLimit-Remaining is set to -1. This means AlgaPSA stays available during infrastructure incidents at the cost of brief unmetered traffic. Treat -1 as "limit unknown" in your client and keep your normal pacing.

Bypassed paths

The following routes are exempt from API rate limiting and never return 429:

  • /api/health, /api/healthz, /api/readyz — liveness probes.
  • /api/v1/meta/health — API health check.
  • /api/v1/mobile/auth/* — mobile auth uses a separate, narrower limiter.
  • Internal extension-runtime routes under /api/internal/ext-*.

Webhook outbound deliveries

Outbound webhook deliveries from AlgaPSA to your URL run through a separate per-webhook bucket (default 100 per minute, configurable on each webhook). The API rate limit on this page only governs your calls into AlgaPSA. See the Webhooks reference for delivery semantics, retry schedule, and idempotency.

If you keep hitting the limit

  • Polling tickets? Replace it with a webhook subscription. The migration guide walks through the cutover.
  • Use distinct keys per integration. Each API key has its own bucket, so isolating environments (production / staging / a specific data sync job) prevents one job from throttling another.
  • Cache stable lookups. Boards, statuses, priorities, and contracts rarely change — fetch them once at startup and reuse the result, rather than re-fetching on every operation.
  • Need higher limits? Contact support with your tenant id, the API key name, and your expected call pattern. Limits are configurable per tenant and per key.