---
title: "Rate Limiting"
url: https://develop.sentry.dev/sdk/foundations/transport/rate-limiting/
---

# Rate Limiting

Rate limits are communicated to SDKs via status codes and response headers. For regular rate limit responses, we emit a [`429`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) status code and specify a [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header.

In addition to `Retry-After`, Sentry (Relay) also includes a special response header `X-Sentry-Rate-Limits` containing a detailed list of all rate limits that apply per data category.

```bash
X-Sentry-Rate-Limits: <quota_limit>, <quota_limit>, ...
```

Each *quota\_limit* has the form `retry_after:categories:scope:reason_code:namespaces:...` with the following parameters:

* `retry_after`: Number of seconds (as an integer or a floating point number) until this rate limit expires.
* `categories`: Semicolon-separated list of [data categories](https://github.com/getsentry/relay/blob/master/relay-base-schema/src/data_category.rs#L91). **If empty, this limit applies to all categories**. While these categories might look similar to the [envelope item types](https://develop.sentry.dev/sdk/foundations/transport/envelope-items.md), they are not identical, and have slight differences.
* `scope`: The scope that this limit applies to. Can be ignored by SDKs.
* `reason_code`: A unique identifier for the quota hinting at the rate limiting reason. Can be ignored by SDKs.
* `namespaces`: Semicolon-separated list of metric namespace identifiers. This will only be present if the rate limit applies to the `metric_bucket` data category. If the namespace is not present, the backoff applies to all metrics.
* More parameters can be added in the future, and can be ignored by SDKs (promise of backward compatibility).

The header may contain spaces which must be ignored. Relay will only emit spaces after `,` to separate *quota\_limits*.

**Example response** (formatted for readability):

```python
HTTP/1.1 429 Too Many Requests
Retry-After: 2700
X-Sentry-Rate-Limits:
  60:transaction:key,
  2700:default;error;security:organization
```

**Example response** (with empty categories list):

```python
HTTP/1.1 429 Too Many Requests
Retry-After: 2700
X-Sentry-Rate-Limits: 60::organization, 2700::organization
```

**Example response** (for metric buckets):

```python
HTTP/1.1 429 Too Many Requests
Retry-After: 2700
X-Sentry-Rate-Limits: 2700:metric_bucket:organization:quota_exceeded:custom
```

The `X-Sentry-Rate-Limits` header may appear in any response, regardless of status code, even `200` responses. This is to proactively inform SDKs that certain payload types are disabled before SDKs even try to send them.

## [Rate Limit Enforcement](https://develop.sentry.dev/sdk/foundations/transport/rate-limiting.md#rate-limit-enforcement)

SDKs are expected to honor 429 responses and rate limits communicated from Sentry by stopping data transmission until the rate limit has expired. Events, transactions, sessions, and/or any other supported payload type, depending on the communicated limits, are to be discarded during the period that the rate limit is in place.

### [Stage 1: Parse Response Headers](https://develop.sentry.dev/sdk/foundations/transport/rate-limiting.md#stage-1-parse-response-headers)

To detect rate limits in a response from Sentry, apply the following steps:

1. On every response, look for `X-Sentry-Rate-Limits`. If present, parse it and immediately go to Stage 2.
2. On `429` responses, check for `Retry-After`. Treat it like `categories=[]` and go to Stage 2. It is allowed but not required to do this on other status codes.
3. On `429` responses without the above headers, assume a *60s* rate limit for all categories and go to Stage 2. See the [Java implementation](https://github.com/getsentry/sentry-java/blob/b182c6e4353dacd145a529298aa6f1ee41928d9f/sentry/src/main/java/io/sentry/transport/RateLimiter.java#L261-L270) for reference.
4. Otherwise, there are no rate limits communicated by Sentry.

### [Stage 2: Determine Rate Limits](https://develop.sentry.dev/sdk/foundations/transport/rate-limiting.md#stage-2-determine-rate-limits)

Guidelines for how SDKs should determine the current rate limits:

* Rate limits must be tracked per each DSN, what corresponds to storing a rate limits map in an instance of the SDK transport/client.
* SDKs that only support one payload type can ignore `X-Sentry-Rate-Limits` and use the `Retry-After` header to determine rate limit expiration.
* SDKs must implement and obey limits for supported categories, for example `error` and `transaction`. For each category, they should maintain a separate limit, and another separate limit for the implicit "all" category.
* SDKs must ignore dimensions they do not support or are irrelevant, for example `scope` and `reason_code`.
* Categories and scopes that are unknown to the client should be ignored. The limit still applies to the known categories. Clients should expect that more will be added in the future.
* More broadly, unknown dimensions must be ignored, that is, all additional colons and text to the right of the last parsed/interpreted dimension.
* Limits where **all** categories are unknown must be ignored since those limits apply to data the SDK cannot send. Do not apply unknown categories to a default category. Note that this is distinct from limits with no category, which implicitly apply to all categories (think of this as a special category).
* Always keep the maximum rate limit if multiple rate limits reference the same category. If a new rate limit is shorter than an already stored rate limit, then keep the longer one.
* `X-Sentry-Rate-Limits` returned in `200 OK` responses should be treated like on 429 responses. Sentry may choose to respond with `200 OK` regardless of a rate limit or may choose to inform a client proactively about a rate limit that is unrelated to the current request. This happens specifically for *reject-all quotas* to prevent clients from sending requests.

## [Definitions](https://develop.sentry.dev/sdk/foundations/transport/rate-limiting.md#definitions)

As stated earlier, SDKs can ignore the `scope` dimension. These definitions are here as a supplement to explain what the `X-Sentry-Rate-Limits` header is made of.

* **Category:** Classifies the type of data that is being counted. Arbitrary categories can be added as long as they can be inferred from the event or data being ingested. While these [data categories](https://github.com/getsentry/relay/blob/master/relay-base-schema/src/data_category.rs#L91) might look similar to the [envelope item types](https://develop.sentry.dev/sdk/foundations/transport/envelope-items.md), they are not identical, and have slight differences.

  * `default`: Events with an event\_type not listed explicitly below.
  * `error`: Error events.
  * `transaction`: Transaction type events.
  * `monitor`: Monitor check-ins.
  * `span`: Span type events.
  * `log_item`: Log events.
  * `security`: Events with event\_type `csp`
  * `attachment`: Attachment bytes stored (unused for rate limiting)
  * `session`: Session update events
  * `profile`: Profiling events
  * `profile_chunk`: Continuous Profiling chunks
  * `replay`: Session Replays
  * `feedback`: User Feedbacks
  * `trace_metric`: Metric type events
  * `internal`: a sentry/system internal event[1](https://develop.sentry.dev/sdk/foundations/transport/rate-limiting.md#user-content-fn-internal)

* **Scope**: The unit / model in Sentry that quotas are enforced for.

  * `organization`
  * `project`
  * `key`

## [Footnotes](https://develop.sentry.dev/sdk/foundations/transport/rate-limiting.md#footnote-label)

1. Special note on the internal data category: this data category exists but is promised not to be emitted as we do not enforce rate limits on internal items. This might appear odd but the purpose of this is to allow envelope implementations in SDKs to assign a data category to internal items such as `client_report`. SDKs can handle `internal` like any other data category but they can rely on the fact that rate limits are not communicated for these explicitly. That said, the special *all* category does apply to `internal` event types as well. [↩](https://develop.sentry.dev/sdk/foundations/transport/rate-limiting.md#user-content-fnref-internal)
