> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streampixel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits & error codes

> Rate limit windows, HTTP status codes, and the standard error response format used across all Streampixel endpoints.

Every Streampixel REST endpoint shares the same authentication model, error format, and rate-limit philosophy. This page is the single reference for what to expect when a request fails — and how to handle it correctly in production clients.

## Rate limits

Streampixel applies per-user rate limits to write-heavy build pipeline endpoints to protect the build manager and downstream streaming infrastructure.

| Endpoint                         | Limit                           | Scope        |
| -------------------------------- | ------------------------------- | ------------ |
| `POST /projects/upload-file`     | **1 request per 2 minutes**     | Per `userId` |
| `POST /projects/distribute-file` | **1 request per 2 minutes**     | Per `userId` |
| All other endpoints              | Unlimited (subject to fair use) | Per `userId` |

<Warning>
  "Unlimited" still means you must behave well. Aggressive polling, parallel fan-out, or repeated retries against any endpoint can be throttled or blocked at the edge. If you need high-frequency reads, contact support so we can scale your account appropriately.
</Warning>

When you exceed a limit, the API returns `429 Too Many Requests` with a JSON body explaining when you can retry.

```json 429 Too Many Requests theme={"dark"}
{
  "message": "Rate limit exceeded. Try again in 2 minutes."
}
```

## Standard error response

Every error response — regardless of status code — uses the same JSON shape:

```json theme={"dark"}
{
  "message": "Human-readable description of what went wrong"
}
```

There is no `code`, `errors[]`, or nested envelope. Always parse `message` from the JSON body and surface it to operators or logs.

<Info>
  Successful responses (`2xx`) vary by endpoint and are documented on each endpoint's page. The error shape above is uniform across the entire API.
</Info>

## HTTP status codes

| Status                       | Meaning                                                  | Typical example                                     |
| ---------------------------- | -------------------------------------------------------- | --------------------------------------------------- |
| `200 OK`                     | Request succeeded with a response body.                  | `GET /projects` returns the list.                   |
| `201 Created`                | Resource was created.                                    | New build accepted by the pipeline.                 |
| `204 No Content`             | Request succeeded; no body returned.                     | Internal admin actions.                             |
| `400 Bad Request`            | Malformed request — usually a missing or invalid field.  | `"Missing required field: fileUrl"`                 |
| `401 Unauthorized`           | `apiKey` is missing or invalid.                          | `"Unauthorized: Invalid API Key"`                   |
| `403 Forbidden`              | The authenticated user does not own the target resource. | `"Forbidden: project does not belong to this user"` |
| `404 Not Found`              | The resource does not exist.                             | `"Project not found"`                               |
| `415 Unsupported Media Type` | `Content-Type` is missing or not `application/json`.     | `"Content-Type must be application/json"`           |
| `429 Too Many Requests`      | You exceeded a rate limit window.                        | `"Rate limit exceeded. Try again in 2 minutes."`    |
| `500 Internal Server Error`  | Streampixel had an unexpected failure.                   | `"Internal server error"`                           |

### Common causes

<Tabs>
  <Tab title="400 Bad Request">
    * Required field omitted from the JSON body (e.g., `fileUrl`, `projectId`).
    * Wrong field type (number passed where a string was expected).
    * Empty string where a non-empty value is required.
    * Invalid `fileUrl` shape (must be a public direct download link).
  </Tab>

  <Tab title="401 Unauthorized">
    * `apiKey` not included in the request body.
    * `apiKey` has been rotated or revoked from the dashboard.
    * Wrong key for the environment (e.g., dev key against prod project).
  </Tab>

  <Tab title="403 Forbidden">
    * `userId` and `apiKey` are valid, but the user doesn't own the `projectId`.
    * Trying to act on a project that was transferred to another account.
    * Subscription expired and write actions are now blocked.
  </Tab>

  <Tab title="404 Not Found">
    * `projectId` no longer exists.
    * `uploadId` not found (deleted or never created).
    * Webhook URL not configured for the project (returned by Test Webhook).
  </Tab>

  <Tab title="429 Too Many Requests">
    * More than one `upload-file` or `distribute-file` call within a 2-minute window.
  </Tab>

  <Tab title="500 Internal Server Error">
    * Transient infrastructure issue — almost always safe to retry once after a short delay.
    * Persistent 500s should be reported to support with the request ID and timestamp.
  </Tab>
</Tabs>

## Retry strategy

The rule of thumb:

* **Retry** on `429` and `5xx`.
* **Do not retry** on any other `4xx` — the request is wrong; retrying won't fix it.
* **Use exponential backoff with jitter** so a fleet of clients doesn't synchronize on the same retry tick.

### Pseudocode: backoff retry loop

```javascript Node.js theme={"dark"}
async function callWithRetry(fn, { maxAttempts = 5, baseDelayMs = 1000 } = {}) {
  let attempt = 0;

  while (true) {
    attempt++;
    try {
      return await fn();
    } catch (err) {
      const status = err.response?.status;

      // Permanent client errors — do not retry.
      if (status && status >= 400 && status < 500 && status !== 429) {
        throw err;
      }

      // Out of attempts.
      if (attempt >= maxAttempts) {
        throw err;
      }

      // Exponential backoff with jitter: 1s, 2s, 4s, 8s, 16s ± 250ms
      const delay = baseDelayMs * 2 ** (attempt - 1);
      const jitter = Math.floor(Math.random() * 500) - 250;
      await new Promise((r) => setTimeout(r, delay + jitter));
    }
  }
}
```

```python Python theme={"dark"}
import random
import time

def call_with_retry(fn, max_attempts=5, base_delay=1.0):
    attempt = 0
    while True:
        attempt += 1
        try:
            return fn()
        except Exception as err:
            status = getattr(err, "status_code", None) or getattr(err.response, "status_code", None)

            # Permanent client errors — do not retry.
            if status and 400 <= status < 500 and status != 429:
                raise

            if attempt >= max_attempts:
                raise

            delay = base_delay * (2 ** (attempt - 1))
            jitter = random.uniform(-0.25, 0.25)
            time.sleep(delay + jitter)
```

<Tip>
  Cap your maximum delay (e.g., 60 seconds) and your maximum attempts (e.g., 5). Unbounded retries make incidents worse, not better.
</Tip>

## Best practices

* **Always read `message`** from the response body — never rely on the status code alone for human-friendly errors.
* **Log the full request and response** (with `apiKey` redacted) when you hit an unexpected error. The body almost always pinpoints the issue.
* **Honor `429`** — if you see one, slow down. Repeatedly hammering a rate-limited endpoint will get the entire user account temporarily blocked.
* **Treat `5xx` as transient** unless they persist beyond 2–3 retries spread over \~30 seconds. After that, alert and investigate.
* **Never retry idempotently destructive calls without confirming success first** — for `upload-file` specifically, if the first call timed out, check the project's build list before retrying.

## Next steps

<CardGroup cols={2}>
  <Card title="API authentication" icon="key" href="/resources/api-reference/api-authentication">
    How `apiKey` and `userId` work and where to obtain them.
  </Card>

  <Card title="Webhooks" icon="bell" href="/resources/api-reference/webhooks">
    Replace polling with push-based event delivery.
  </Card>
</CardGroup>
