# Rate Limits

> How tier-based rate limits, the daily credit cap, and 429 responses work.

Rate limits protect the API and keep usage fair. They are applied per API key and are tier-based: your plan determines the exact per-minute and per-day request limits, the concurrency limit, and the daily credit cap.

Because limits depend on your tier, read your current values from the tier endpoint rather than hardcoding numbers.

## `GET /v1/account/tier`

Return the current rate limits and quotas for your account. Authenticated with your API key — usable from backend services, CI jobs, and agents without a browser session.

**Auth:** API Key (Bearer token)

| Field | Type | Description |
| --- | --- | --- |
| `plan_code` | `string` | Current tier name (e.g. "pro"). |
| `requests_per_minute` | `integer` | Max requests per rolling minute window. |
| `requests_per_day` | `integer` | Max requests per calendar day (UTC). |
| `concurrent_limit` | `integer` | Max in-flight requests at once. |
| `max_credits_per_day` | `integer` | Daily credit spending cap. Resets 00:00 UTC. |
| `allowed_model_types` | `string[]` | Model categories this tier can access. |

```bash
curl https://api.picxstudio.com/v1/account/tier \
  -H "Authorization: Bearer pxsk_your_key"
```

```json
{
  "plan_code": "pro",
  "requests_per_minute": 60,
  "requests_per_day": 10000,
  "concurrent_limit": 10,
  "max_credits_per_day": 13000,
  "allowed_model_types": ["image", "video", "audio", "agent"]
}
```

> [!TIP]
> Call this once at startup and cache it. Tier limits rarely change mid-session.

## `GET /api/tier` (Dashboard)

The same data, authenticated with your browser session (JWT). This is what the dashboard UI calls — if you are building a headless integration, prefer `/v1/account/tier` above.

**Auth:** Dashboard (JWT)

> [!NOTE]
> Tiers are examples and may change — always read /api/tier for your real limits. Typical seeded tiers: Starter 10/min · 500/day, Pro 30/min · 2,000/day, Max 60/min · 5,000/day, Ultra 120/min · 10,000/day.

When you exceed a limit the API returns HTTP 429 with standard rate-limit headers:

## Rate-limit headers (on 429)

```
X-RateLimit-Limit: 30          # your per-minute request cap
X-RateLimit-Remaining: 0       # requests left in the current window
X-RateLimit-Reset: 1718659245  # unix time when the window resets
Retry-After: 12                # seconds to wait before retrying
```

> [!WARNING]
> On HTTP 429 Too Many Requests, read the Retry-After header and wait that many seconds before retrying. Do not retry immediately.

## 429 response

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1718659245

{
  "detail": "Rate limit exceeded. Retry after 12 seconds."
}
```

> [!NOTE]
> Separate from request limits, each account has a daily credit cap. When it is reached the API also returns 429 with a Retry-After header; the cap resets at 00:00 UTC.

## Handling rate limits

1. **Watch for 429s**

   Treat HTTP 429 as a signal to slow down, not an error to surface to users.

2. **Honor Retry-After**

   Wait for the number of seconds in the Retry-After header before the next attempt.

3. **Back off exponentially**

   For repeated 429s, increase the wait each time (for example 1s, 2s, 4s) with a capped maximum and a retry limit.

## Retry with backoff

**python**

```python
import time
import requests

def api_request(method, url, max_retries=3, **kwargs):
    for attempt in range(max_retries):
        resp = requests.request(method, url, **kwargs)
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", "5"))
            print(f"Rate limited; waiting {retry_after}s")
            time.sleep(retry_after)
            continue
        resp.raise_for_status()
        return resp.json()
    raise RuntimeError("Max retries exceeded")
```

**javascript**

```javascript
async function apiRequest(url, options = {}, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const res = await fetch(url, options);
    if (res.status === 429) {
      const retryAfter = parseInt(res.headers.get("Retry-After") || "5", 10);
      console.log(`Rate limited; waiting ${retryAfter}s`);
      await new Promise((r) => setTimeout(r, retryAfter * 1000));
      continue;
    }
    if (!res.ok) throw new Error(`API error: ${res.status}`);
    return res.json();
  }
  throw new Error("Max retries exceeded");
}
```

## FAQ

### How do I find my actual rate limits instead of guessing?

Call `GET /v1/account/tier` with your API key — it returns your real `requests_per_minute`, `requests_per_day`, `concurrent_limit`, and `max_credits_per_day` for your current plan. Limits are tier-based, so don't hardcode numbers.

### What happens when I exceed a rate limit?

The API returns HTTP 429 with `X-RateLimit-*` headers and a `Retry-After` header telling you how many seconds to wait. Read `Retry-After` and wait — don't retry immediately.

### Is the daily credit cap the same thing as the rate limit?

No — they're separate. Request-rate limits govern how many calls you can make per minute/day; the daily credit cap governs total spend per day. Both return 429 when exceeded, and the credit cap resets at 00:00 UTC.

### What's the recommended way to handle 429s in my own retry logic?

Honor `Retry-After` first, then back off exponentially on repeated 429s (e.g. 1s, 2s, 4s) with a capped maximum and a retry limit — see "Retry with backoff" above for working Python and JavaScript examples.

### Should I cache my tier limits, or check them on every request?

Cache them. Call `/v1/account/tier` once at startup — tier limits rarely change mid-session.
