Rate limiting is implemented natively via Azure API Management (APIM), which sits upstream of the HIBP API itself. An HTTP 429 response means you've exceeded the rate limit and need to either reduce the rate at which you're issuing requests or upgrade to a higher rate limit plan. The HTTP 429 response will include a Retry-After header indicating how many seconds you should wait before making the next request. Constantly exceeding the rate limit may result in a longer Retry-After header being returned from Cloudflare's edge.
Due to network volatility between your client and HIBP's services, you should not try to query services at the rate limit, rather just below it. For example, a 100 RPM rate limit may be reliably queried at 95 requests per minute, but attempting 100 requests in a minute may cause too many requests to be issued within the allowable window.
If you find you're unexpectedly exceeding the limit, here are some things to check:
- No other processes are using the same API key
- You're leaving a small additional buffer between requests
- Try rotating the key if you still can't work out the problem, that will invalidate any other locations it's presently being used in
If you'd like to test the rate limit, try the PowerShell script below:
$apiKey = "Your_API_Key" # replace this with your actual API key
$targetRpm = 100 # The rate the script will attempt to issue requests at
$safetyBufferPercent = 5 # Extra percentage delay per request to ensure the rate limit is not exceeded
$url = "https://haveibeenpwned.com/api/v3/breachedaccount/test@example.com"
$headers = @{
"hibp-api-key" = $apiKey
}
$successfulRequests = 0
$failedRequests = 0
$startTime = [DateTime]::UtcNow
$targetIntervalMs = (60000 / $targetRpm) * (1 + $safetyBufferPercent / 100)
Write-Host "Must be a $([math]::Round($targetIntervalMs)) millisecond delay between requests"
while ($true) {
$requestStart = [DateTime]::UtcNow
try {
$response = Invoke-WebRequest -Uri $url -Headers $headers
Write-Host "Status Code: $($response.StatusCode)"
$successfulRequests++
}
catch {
Write-Host "Error: $($_.Exception.Message)"
$failedRequests++
}
$elapsed = [DateTime]::UtcNow - $startTime
if ($elapsed.TotalMinutes -gt 0) {
$rpm = [math]::Round($successfulRequests / $elapsed.TotalMinutes, 2)
Write-Host "Requests per minute (since start): $rpm (total: $successfulRequests successes)"
}
# Exit after 1 minute
if ($elapsed.TotalMinutes -ge 1) {
break
}
# Calculate how long to sleep to maintain target rate
$elapsedThisRequest = ([DateTime]::UtcNow - $requestStart).TotalMilliseconds
$sleepMs = [math]::Max(0, $targetIntervalMs - $elapsedThisRequest)
Start-Sleep -Milliseconds $sleepMs
}
# Summary report
$finalRpm = [math]::Round($successfulRequests / $elapsed.TotalMinutes, 2)
Write-Host "=== Summary ==="
Write-Host "Successful requests: $successfulRequests"
Write-Host "Failed requests: $failedRequests"
Write-Host "Achieved requests per minute: $finalRpm"
Tested with a 100 RPM key, the results should be similar to the following:
Status Code: 200
Requests per minute (since start): 94.92 (total: 94 successes)
Status Code: 200
Requests per minute (since start): 94.79 (total: 95 successes)
=== Summary ===
Successful requests: 95
Failed requests: 0
Achieved requests per minute: 94.79