- [Home page](https://www.brimit.com/)
- [Blog](https://www.brimit.com/blog)

# Sitecore Send: API Rate Limits

[Tech Talks](https://www.brimit.com/blog?categories=Tech%20Talks) [DXP](https://www.brimit.com/blog?platforms=DXP)
[Igor Zharikov](https://www.brimit.com/blog/author?authors=Igor%20Zharikov) on July 22, 2024

![Sitecore Send](https://www.brimit.com/-/jssmedia/images/blog/sitecore-send-apis/sendlogo.png?h=400&iar=0&w=660&hash=1A34554FB4C322BA3D6ACC239404FCB1)

#### Background

Sitecore Send provides an [API](https://doc.sitecore.com/send/en/developers/api-documentation/introduction-to-the-sitecore-send-api.html) to interact and integrate with your custom system. You can check my articles about various APIs and how to use them: [here](https://www.brimit.com/blog/sitecore-send-apis) and [here](https://www.brimit.com/blog/sitecore-send-3-rest-api).

If the API is used to subscribe and unsubscribe users, you can easily face [API rate limits](https://doc.sitecore.com/send/en/developers/api-documentation/api-rate-limiting.html).

#### API Rate Limits

##### Current Limits

Up-to-date limits can be checked in the [official documentation](https://doc.sitecore.com/send/en/developers/api-documentation/api-rate-limiting.html).

Currently, the limits are the following - **number of requests per 10 seconds**:

| Resource | Method | Endpoint Path | Rate Limit |
| --- | --- | --- | --- |
| [Add subscribers](https://doc.sitecore.com/send/en/developers/api-documentation/add-a-new-subscriber.html) | POST | `/subscribers/{ListID}/subscribe` | 10 |
| [Add multiple subscribers](https://doc.sitecore.com/send/en/developers/api-documentation/add-multiple-subscribers.html) | POST | `/subscribers/{ListID}/subscribe_many` | 2 |
| [Unsubscribe a subscriber from an account](https://doc.sitecore.com/send/en/developers/api-documentation/unsubscribe-a-subscriber-from-an-account.html) | POST | `/subscribers/unsubscribe` | 20 |
| [Unsubscribe a subscriber from an email list](https://doc.sitecore.com/send/en/developers/api-documentation/unsubscribe-a-subscriber-from-a-mailing-list.html) | POST | `/subscribers/{ListID}/unsubscribe` | 20 |
| [Unsubscribe a subscriber from an email list and a campaign](https://doc.sitecore.com/send/en/developers/api-documentation/unsubscribe-a-subscriber-from-a-mailing-list-and-a-campaign.html) | POST | `/subscribers/{ListID}/
            {CmpId}/unsubscribe` | 20 |

*Note*: All resource limits are counted separately, so you can execute *2 x Add multiple subscribers*, *10 x Add subscribers*, and *20 x Unsubscribe a subscriber from an account* simultaneously within a timespan of 10 seconds without being rate limited.

##### Rate Limited Response

If you reach the rate limit, you'll get the following response:

```
{
  "Code": 429,
  "Error": "RATE_LIMITING",
  "Context": null
}
```

Additionally, information about rate limits is included in response headers, e.g.:

```
x-ratelimit-expires:    07/10/2024 14:12:52
x-ratelimit-firstcall:  07/10/2024 14:12:42
```

These headers can be handled to understand when the next call can be executed.

#### Handling Rate Limits in C# with `Polly`

The source for Polly policies can be checked [here](https://github.com/izharikov/SitecoreSend/blob/develop/dotnet/SitecoreSend.SDK.Tests/Limiter/SendRateLimiterPolicy.cs).

##### Rate Limiting Using Polly

This policy will limit number of executions withing specified timespan. If it's executed more often, then `RateLimitRejectedException` is thrown.

```
public static AsyncPolicy<T> ConfigureRateLimit<T>(int numberOfExecutions,
    TimeSpan perTimeSpan)
{
    return Policy
            .RateLimitAsync<T>(numberOfExecutions, perTimeSpan)
        ;
}
```

##### Retry if Polly Rate Limit Failed

If the request is executed more often than configured in the policy above, retry it 5 times with some delay.

```
public static AsyncPolicy<T> RetryRateLimitException<T>(int numberOfExecutions, TimeSpan perTimeSpan)
{
    var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: perTimeSpan.Divide(numberOfExecutions) * 1.5,
        retryCount: 5);
    return Policy<T>.Handle<RateLimitRejectedException>()
        .WaitAndRetryAsync(delay);
}
```

##### Retry if HTTP Response is `RATE_LIMITING`

If the request is executed but the HTTP response contains a `RATE_LIMITING` error, retry it after the `x-ratelimit-expires` header date.

```
public static AsyncPolicy<T> RetryRateLimit<T>() where T : SendResponse?
{
    return Policy
        .HandleResult<T>((response) => response?.RateLimitDetails != null)
        .WaitAndRetryAsync(5, (i, result, _) =>
        {
            if (result.Result?.RateLimitDetails?.Expires != null)
            {
                var utcNow = DateTimeOffset.UtcNow;
                return result.Result.RateLimitDetails.Expires.Value.Subtract(utcNow) +
                        TimeSpan.FromMilliseconds(i * 50);
            }

            return TimeSpan.FromMilliseconds(i * 50);
        }, (_, _, _, _) => Task.CompletedTask);
}
```

*Note*: This code is based on the [NuGet package `SitecoreSend.SDK`](https://www.nuget.org/packages/SitecoreSend.SDK).

##### Result Policy

All previously mentioned policies can be combined together:

```
public class SendRateLimiterPolicy<T> where T : SendResponse?
{
    public AsyncPolicy<T> Instance { get; private set; }

    public SendRateLimiterPolicy(int numberOfExecutions,
        TimeSpan perTimeSpan)
    {
        Instance = Policy.WrapAsync(PoliciesHelper.RetryRateLimit<T>(),
            PoliciesHelper.RetryRateLimitException<T>(numberOfExecutions, perTimeSpan),
            PoliciesHelper.ConfigureRateLimit<T>(numberOfExecutions, perTimeSpan));
    }
}
```

And then configured for each endpoint separately:

```
public static class SendRateLimits
{
    public static readonly AsyncPolicy<SendResponse<IList<Subscriber>>?> AddMultipleSubscribers =
        new SendRateLimiterPolicy<SendResponse<IList<Subscriber>>?>(2, TimeSpan.FromSeconds(10)).Instance;

    public static readonly AsyncPolicy<SendResponse<Subscriber>?> AddSubscriber =
        new SendRateLimiterPolicy<SendResponse<Subscriber>?>(10, TimeSpan.FromSeconds(10)).Instance;

    public static readonly AsyncPolicy<SendResponse?> UnsubscribeFromAllLists =
        new SendRateLimiterPolicy<SendResponse?>(20, TimeSpan.FromSeconds(10)).Instance;

    public static readonly AsyncPolicy<SendResponse?> UnsubscribeFromList =
        new SendRateLimiterPolicy<SendResponse?>(20, TimeSpan.FromSeconds(10)).Instance;

    public static readonly AsyncPolicy<SendResponse?> UnsubscribeFromListAndCampaign =
        new SendRateLimiterPolicy<SendResponse?>(20, TimeSpan.FromSeconds(10)).Instance;
}
```

You can automatically use it together with the [`SitecoreSend.SDK` NuGet package](https://www.nuget.org/packages/SitecoreSend.SDK):

```
var client = new SendClient(
    apiConfiguration,
    httpClientFactory, 
    new RateLimiterConfiguration()
    {
        Subscribers = new SubscribersWrapper()
        {
            AddSubscriber = SendRateLimits.AddSubscriber.ExecuteAsync,
            AddMultipleSubscribers = SendRateLimits.AddMultipleSubscribers.ExecuteAsync,
            UnsubscribeFromAllLists = SendRateLimits.UnsubscribeFromAllLists.ExecuteAsync,
            UnsubscribeFromList = SendRateLimits.UnsubscribeFromList.ExecuteAsync,
            UnsubscribeFromListAndCampaign = SendRateLimits.UnsubscribeFromListAndCampaign.ExecuteAsync,
        },
    }
);
```

For more information, check the [README](https://github.com/izharikov/SitecoreSend/tree/develop/dotnet) of the NuGet package.

#### Suggestions to Minimize Rate Limits Errors

##### 1. Use Bulk Actions

Sitecore Send comes with bulk actions by default:

- Import Members into List
- Archive List Members
- Unsubscribe List Members
- Delete List Members
- Copy Members from Another Email List

Maybe you don't need to use the API? Perhaps you can export subscribers into a CSV/XLSX file and perform a bulk operation in the Admin Panel?

##### 2. Use `Add Subscribers` endpoint only to add NEW, not to update EXISTING

You may know that using the `/v3/subscribers/{MailingListID}/subscribe.{Format}` endpoint, you can not only create a new subscriber but also update information about an existing subscriber. **Don't do it or do it very carefully!**

This may cause API rate limits errors. The better approach is to use two requests:

- `GET /v3/subscribers/{MailingListID}/view.{Format}?apikey={API_KEY}&Email={Email}` - to get a subscriber by email (retrieve `{SubscriberId}` from the response)
- `POST /v3/subscribers/{MailingListID}/update/{SubscriberId}.{Format}?apikey={API_KEY}` - to update a subscriber by ID

##### 3. Handle `RATE_LIMITING` Error Code Response

Always handle `RATE_LIMITING` error code with retries: using Polly as mentioned above or using any other mechanism.

##### 4. Ensure subscribe/unsubscribe endpoints are used correctly and limited

On one of our projects, we had a webhook that sends requests to Sitecore Send with updated subscriber information. However, we did not filter anonymous requests, so many requests were sent incorrectly, leading to numerous `RATE_LIMITING` errors. After excluding anonymous requests, we resolved this issue.

* * *

I hope all the explanations and suggestions will help you avoid issues when using the API.

###### Author

![Igor Zharikov - Senior Sitecore Developer, Sitecore MVP](https://www.brimit.com/-/jssmedia/feature/blogs/authors/igor-zharikov-2.jpg?h=700&iar=0&w=700&hash=292A84E8CB3E5CE57CA743385CFB1464)

Igor Zharikov

Sitecore MVP / Senior Sitecore Developer
[Igor Zharikov](https://www.brimit.com/blog/author?authors=Igor%20Zharikov)

###### More By Categories

[#Events](https://www.brimit.com/blog?categories=#Events) [#How-to](https://www.brimit.com/blog?categories=#How-to) [#News](https://www.brimit.com/blog?categories=#News) [#Guides](https://www.brimit.com/blog?categories=#Guides)

###### More by Platform

[Data and AI](https://www.brimit.com/blog?platforms=Data%20and%20AI) [DXP](https://www.brimit.com/blog?platforms=DXP) [E-commerce](https://www.brimit.com/blog?platforms=E-commerce) [Internet of Things](https://www.brimit.com/blog?platforms=Internet%20of%20Things) [Sales and marketing automation](https://www.brimit.com/blog?platforms=Sales%20and%20marketing%20automation)
