Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Tip
New to throttling? Learn what throttling is and how to handle it.
At a glance
Goal: Test how your app handles GitHub REST API rate limits
Time: 15 minutes
Plugins: RateLimitingPlugin, GenericRandomErrorPlugin, RetryAfterPlugin
Prerequisites: Set up Dev Proxy
Your app calls the GitHub API. It works on your machine, then a CI job, a large organization, or a busy day pushes it over the rate limit, and it starts failing. To test it against the real API, you'd need to use up your rate limit, and then wait up to an hour before you can try again. Dev Proxy simulates GitHub rate limits locally, with a limit and a time window that you choose.
Know what GitHub returns
GitHub has 2 kinds of rate limits for the REST API.
Primary rate limits cap how many requests you make per hour. For example, 60 for unauthenticated requests and 5,000 for requests with a personal access token. Every response includes headers that show where you are:
| Header | Meaning |
|---|---|
x-ratelimit-limit |
The maximum number of requests per hour |
x-ratelimit-remaining |
The number of requests left in the current window |
x-ratelimit-reset |
The time when the window resets, in UTC epoch seconds |
When you exceed the primary limit, GitHub returns 403 or 429 with x-ratelimit-remaining set to 0. Don't retry until the time in x-ratelimit-reset.
Secondary rate limits protect GitHub from bursts, like too many concurrent requests or creating too much content too fast. When you exceed one, GitHub returns 403 or 429 with a message about a secondary rate limit. If the response has a retry-after header, wait that many seconds. Otherwise, wait at least a minute, and increase the wait time if the request keeps failing.
GitHub can ban integrations that keep sending requests while they're rate limited. For more information, see Rate limits for the REST API in the GitHub documentation.
Simulate the primary rate limit
Use the RateLimitingPlugin to count requests and return GitHub's rate limit headers. To test without waiting an hour, use a small limit and a short window.
File: devproxyrc.json
{
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
"plugins": [
{
"name": "RetryAfterPlugin",
"enabled": true,
"pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll"
},
{
"name": "RateLimitingPlugin",
"enabled": true,
"pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
"configSection": "githubRateLimit"
}
],
"urlsToWatch": [
"https://api.github.com/*"
],
"githubRateLimit": {
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/ratelimitingplugin.schema.json",
"headerLimit": "x-ratelimit-limit",
"headerRemaining": "x-ratelimit-remaining",
"headerReset": "x-ratelimit-reset",
"resetFormat": "UtcEpochSeconds",
"costPerRequest": 1,
"rateLimit": 5,
"resetTimeWindowSeconds": 60,
"warningThresholdPercent": 0,
"whenLimitExceeded": "Custom",
"customResponseFile": "github-rate-limit-exceeded.json"
}
}
Caution
Add the RetryAfterPlugin before the RateLimitingPlugin in your configuration file. If you add it after, the RateLimitingPlugin handles the request before the RetryAfterPlugin can check it.
In the custom response file, define the response that GitHub returns when you exceed the primary rate limit.
File: github-rate-limit-exceeded.json
{
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/ratelimitingplugin.customresponsefile.schema.json",
"statusCode": 429,
"headers": [
{
"name": "content-type",
"value": "application/json; charset=utf-8"
}
],
"body": {
"message": "API rate limit exceeded for user ID 1.",
"documentation_url": "https://docs.github.com/rest/overview/rate-limits-for-the-rest-api"
}
}
Start Dev Proxy and run your app.
devproxy --config-file devproxyrc.json
Dev Proxy forwards the first 5 requests in each minute to GitHub and sets the x-ratelimit-* headers on the responses. From the 6th request on, Dev Proxy returns the rate limit response with x-ratelimit-remaining set to 0 and x-ratelimit-reset set to the end of the window. If your app calls the API again before the window resets, the RetryAfterPlugin reports it and throttles the request.
Check that your app:
- Reads
x-ratelimit-remainingand slows down before it reaches0. - Stops calling the API after a rate limit response and waits until
x-ratelimit-reset. - Tells the user what's happening, for example "GitHub rate limit reached, retrying at 14:05", instead of failing silently.
Note
GitHub returns either 403 or 429 when you exceed a rate limit. To test that your app handles 403 too, change statusCode to 403. The RetryAfterPlugin only tracks 429 responses, so it doesn't report early retries after a 403.
Tip
Dev Proxy forwards requests to GitHub until the simulated limit is reached. These requests count against your real GitHub rate limit as well.
Simulate secondary rate limits
Secondary rate limits come in bursts and include a retry-after header. Use the GenericRandomErrorPlugin to return them at random.
File: devproxyrc.json
{
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
"plugins": [
{
"name": "RetryAfterPlugin",
"enabled": true,
"pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll"
},
{
"name": "GenericRandomErrorPlugin",
"enabled": true,
"pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
"configSection": "githubSecondaryRateLimit"
}
],
"urlsToWatch": [
"https://api.github.com/*"
],
"githubSecondaryRateLimit": {
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.schema.json",
"errorsFile": "github-secondary-rate-limit.json",
"rate": 50,
"retryAfterInSeconds": 60
}
}
File: github-secondary-rate-limit.json
{
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.errorsfile.schema.json",
"errors": [
{
"request": {
"url": "https://api.github.com/*"
},
"responses": [
{
"statusCode": 429,
"headers": [
{
"name": "content-type",
"value": "application/json; charset=utf-8"
},
{
"name": "retry-after",
"value": "@dynamic"
}
],
"body": {
"message": "You have exceeded a secondary rate limit. Please wait a few minutes before you try again.",
"documentation_url": "https://docs.github.com/rest/overview/rate-limits-for-the-rest-api#about-secondary-rate-limits"
}
}
]
}
]
}
Start Dev Proxy and run your app. Check that your app waits for the number of seconds in the retry-after header before it calls the API again. If it doesn't, the RetryAfterPlugin reports it.
If you use Octokit with the throttling plugin, check that your onRateLimit and onSecondaryRateLimit handlers run and that they return the result you expect.
Next step
Learn more about the RateLimitingPlugin.
See also
- Simulate Rate-Limit API responses - Rate limits on any API
- Test that my application handles throttling properly - Throttling on any API
- RetryAfterPlugin - Verify retry behavior
- Use Dev Proxy in CI/CD - Automate resilience testing in your pipeline