> This page is for Developer Documentation.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.benchling.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.benchling.com/_mcp/server.

# V3 Rate Limits

The V3 REST API limits how much work each caller can ask of Benchling over a period of time. Limits protect your Benchling tenant's performance for all users.

## How limits are applied

Every V3 request draws from more than one budget at once. A request is rejected if any of the budgets it draws from is exhausted.

* **Your own budget.** Each user and each app has its own budget. Requests one user makes don't use up another user's budget, and requests from one app don't use up another app's budget.
* **The tenant budget.** All V3 requests to a tenant also draw from a budget shared by every caller on that tenant.

The tenant budget is much larger than any single caller's budget, so you will rarely reach it. It can happen when many users or apps use the API on the same tenant at the same time.

### Apps acting on behalf of a user

When an app calls the API on behalf of a user, the request draws from both that user's budget and the app's budget. The app's budget is shared by every user of that app on the tenant, so heavy use of the app by some users can lead to rejected requests for others.

### Not all requests cost the same

Some operations consume more of a budget than others. In general, reads of a single object are cheapest, and large list operations and writes to complex objects cost more. To get the most out of your budget, prefer [batch and bulk operations](/docs/v3-api-overview#batch-and-bulk-writes) to many single-object requests, and fetch only the pages of results you need.

## Rate-limited responses

When a request is rejected, the API returns HTTP `429 Too Many Requests`. Rejected requests are not queued; you must send them again.

The response body includes a `violated-policies` field listing the budgets that rejected the request:

| Value         | Budget                                         |
| ------------- | ---------------------------------------------- |
| `user`        | The budget of the user making the request      |
| `integration` | The budget of the app making the request       |
| `tenant`      | The budget shared by all callers on the tenant |

**`429 response body (abridged)`**

```json title="429 response body (abridged)"
{
  "title": "Too Many Requests",
  "status": 429,
  "violated-policies": ["user"]
}
```

Other limits can also return `429`. One example is the throughput limit on how many objects (entities, containers, results, and so on) can be created or updated per hour. It adjusts with system load, but generally allows tens of thousands of objects an hour. Those responses don't include `violated-policies`.

## The `Retry-After` header

When a request is rejected only because your own budget is exhausted (the most common case), the response includes a `Retry-After` header. Its value is the minimum number of seconds to wait before sending the request again:

**`Example response headers`**

```http title="Example response headers"
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 4
```

The value assumes you make no other requests against the same budget while you wait. If your integration keeps sending requests in the meantime, a retry after that interval may be rejected again.

`Retry-After` is omitted when the request was rejected by a budget you share with other callers: the tenant budget, or an app's budget when the app acts on behalf of a user. How long to wait in those cases depends on other callers' traffic, so Benchling can't give a reliable value.

## Handling rate limits

1. If the response includes `Retry-After`, wait at least that many seconds before retrying.
2. If it doesn't, retry with exponential backoff: increase the delay exponentially with each retry, up to a maximum, and add a small random amount each time so that many clients don't retry in lockstep.
3. Pause other requests that draw from the same budget while you wait, rather than retrying each request independently.
4. Set a maximum number of retries, and surface the failure once it's reached.

**`Retry with Retry-After and exponential backoff (pseudocode)`**

```python title="Retry with Retry-After and exponential backoff (pseudocode)"
def send_with_retries(request, max_retries=8, maximum_delay=60):
    for attempt in range(1, max_retries + 1):
        response = send(request)
        if response.status != 429:
            return response

        retry_after = response.headers.get("Retry-After")
        if retry_after is not None:
            delay_time = int(retry_after)
        else:
            delay_time = min(2 ** attempt + random_float_between(0, 1), maximum_delay)

        delay(delay_time)

    raise RateLimitExceeded(response)
```

If your integration regularly receives `429` responses, reduce the number of requests it makes, for example by using batch operations or spreading work over a longer period. If that isn't enough, contact [Benchling support](mailto:support@benchling.com).