Cloud REST API Rate Limiting

Ativo Programs uses rate limiting on its Cloud REST API to ensure service availability and responsiveness among clients.

Implementation

Ativo limits the number of Jira Cloud REST API calls per client. The exact settings are not published, but requests are at risk of being rate limited when doing more than 100 requests over a few minutes.

The rate limits are calculated per client, not per user. E.g., if John and Ann are Jira admins working on the same Jira tenant (same URL), then the API calls of John and Ann both count together against the rate limits.

There is no rate limiting on the Data Center platform. It is at the discretion of the Jira Data Center Admins to review scripts and external integrations.

Rate limit responses

In cases of rate limiting, the HTTP header response contains the status code 429.

Retry with backoff

A response with the status code 429 has not been processed. You can (and probably want to) safely retry it.

The best practice is to retry events with an exponential backoff. We also recommend using Jitter (a random small time added) to avoid the thundering herd problem.

For example:

Retry number
Waiting time (ms)

1

1000 - 1300 ms (randomly chosen in this range)

2

2000 - 2500 ms

3

4000 - 5000 ms

4

8000 - 10000 ms

5 and successive

16000 - 20000 ms

Example rate limiting retry code with Jitter and exponential retry (TypeScript):

/**
 * Wrapper around fetch to include rate limiting handling
 * (c) Ativo Programs 2024
 * Shared for illustrative purposes. Review, test & adopt before use. 
 * The ATIVO EULA applies.
 */
export default class RateLimitedRetryFetch {

    protected readonly TTL_MAX = 5; // how many retries (TTL = Time To Live)
    protected readonly RETRY_WAIT_TIME = 1000; // in ms, increases exponential with back-offs
    protected readonly JITTER_MIN_FACTOR = 1; // don't go below the requested wait time
    protected readonly JITTER_MAX_FACTOR = 1.3; // max increase factor for Jitter
    protected readonly MAX_RETRY_TIME = 16000; // in ms, max retry time before applying Jitter

    public fetchWithRetry(url: string, options: RequestInit = {},): Promise<Response> {
        return this.requestStep(url, options, this.TTL_MAX);
    }

    /**
     * Can be called recursively, until time to live (ttl) is 0
     */
    private async requestStep(url: string, options: RequestInit = {}, ttl: number): Promise<Response> {
        if (ttl <= 0) throw new Error('Rate limit exceeded and retries exhausted');
        const response = await fetch(url, options);
        if (response.status === 429) {
            return this.handleBackOffAndRetry(url, options, ttl);
        } else {
            return response; // If response is not 429, return the response. No retries then.
        }
    }

    private async handleBackOffAndRetry(url: string, options: RequestInit = {}, ttl: number) {
        const waitTimeInMs = this.getWaitTimeInMs(ttl);
        const jitterWaitTimeInMs = this.applyJitter(waitTimeInMs);
        await new Promise(resolve => setTimeout(resolve, jitterWaitTimeInMs)); //sleeps
        return this.requestStep(url, options, ttl - 1);
    }

    private getWaitTimeInMs(ttl: number) {
        const iteration = (this.TTL_MAX - ttl); // between 0 and 4
        const backoffFactor = Math.pow(2, iteration); // between 1 (2^0) and 16 (2^4)
        const waitTime =  backoffFactor * this.RETRY_WAIT_TIME; // between 1000 ms and 16000 ms
        return Math.min(waitTime, this.MAX_RETRY_TIME); //keep wait under 16000 ms, even when increasing number of retries 
    }

    private applyJitter(waitTimeInMs: number): number {
        const jitterFactor = RateLimitedRetryFetch.getRandomBetween(this.JITTER_MIN_FACTOR, this.JITTER_MAX_FACTOR);
        return Math.floor(jitterFactor * waitTimeInMs);
    }

    private static getRandomBetween(min: number, max: number): number {
        return Math.random() * (max - min) + min;
    }

}

Other tips

The Ativo configuration typically changes slowly, allowing external apps and scripts to cache REST API responses for a reasonable duration.

Further reading

The Ativo API rate limiting is implemented in a similar way as the Jira Rate Limiting. Code that works with Jira rate limiting should also work for Ativo API rate limiting (except we don't use the Retry-After and X-RateLimit-Reset headers, but these are also optional in Jira).

Last updated