Rate Limits
GovData uses per-minute rate limits and credits-based usage billing.
Credits
Every API request costs credits. GET requests cost 1 credit, POST requests cost 10 credits. All accounts receive 100 free credits per month, which reset on the 1st of each month.
Purchase additional credits from the billing page. Purchased credits never expire.
Credit headers are included in every API response:
| Header | Description |
|---|---|
X-Credits-Remaining |
Total credits remaining (paid + free) |
X-Credits-Cost |
Credits consumed by this request |
Rate limits
Per-minute rate limits apply based on your API key's rate plan (default: 10 requests/min).
Rate limit headers
Every API response includes rate limit headers:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1708050060| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests per minute for your plan |
X-RateLimit-Remaining |
Requests remaining in the current window |
X-RateLimit-Reset |
Unix timestamp (seconds) when the window resets |
Retry-After |
Seconds to wait before retrying (only on 429 responses) |
Handling rate limits
When you exceed the rate limit, the API returns 429 Too Many Requests.
Use the Retry-After header to determine when to retry:
# Check for 429 status and Retry-After header curl -i -H "Authorization: Bearer YOUR_API_KEY" \ https://api.govdata.dev/v1/tax/income/bands # If 429 returned: # HTTP/1.1 429 Too Many Requests # Retry-After: 45
def request_with_retry(uri, api_key, max_retries: 3) retries = 0 loop do req = Net::HTTP::Get.new(uri) req["Authorization"] = "Bearer #{api_key}" res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } return res unless res.code == "429" && retries < max_retries wait = res["Retry-After"].to_i sleep(wait) retries += 1 end end
import requests import time def request_with_retry(url, api_key, max_retries=3): for attempt in range(max_retries + 1): response = requests.get( url, headers={"Authorization": f"Bearer {api_key}"} ) if response.status_code != 429: return response wait = int(response.headers.get("Retry-After", 60)) time.sleep(wait) return response
async function requestWithRetry(url, apiKey, maxRetries = 3) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const response = await fetch(url, { headers: { "Authorization": `Bearer ${apiKey}` } }); if (response.status !== 429) return response; const wait = parseInt(response.headers.get("Retry-After") || "60"); await new Promise(r => setTimeout(r, wait * 1000)); } }
429 response body
A 429 is returned for rate limit violations. A 402 is returned when credits are exhausted:
Per-minute rate limit exceeded
{ "error": { "code": "rate_limit_exceeded", "message": "Rate limit exceeded. Please retry after the period indicated in the Retry-After header.", "documentation_url": "https://docs.govdata.dev/rate-limits" } }
Insufficient credits
{ "error": { "code": "insufficient_credits", "message": "Insufficient credits. Purchase credits at https://console.govdata.dev/billing", "documentation_url": "https://docs.govdata.dev/rate-limits" } }
Best practices
- Cache responses where possible — tax bands and bank holidays change infrequently
- Implement exponential backoff for retries
- Monitor your usage on the dashboard
- Use test keys during development to avoid consuming your live credits