Rate Limits
The Udacity Public API enforces rate limits to ensure fair usage and protect service stability. Limits are applied per identity (authenticated user or API token) and per IP for unauthenticated requests.
Rate limits apply across all API domains (Catalog, Program Progress, Assessment Progress, Learning Plan Progress, Tokens). A single token’s requests to different domains all count toward the same limit.
Current Limits
100 requests per minute per identity
100 requests per minute per IP address
Rate limits apply across all API domains. A single token’s requests to different domains all count toward the same limit.
Need higher limits? If your use case requires higher rate limits, contact your Udacity account team to discuss options.
Rate Limit Headers
Every response includes headers to help you track your usage:
Maximum requests allowed per window
Requests remaining in the current window
Seconds to wait before retrying (only on 429 responses)
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
Content-Type: application/jsonWhen You Hit the Limit
If you exceed the rate limit, the API returns a 429 Too Many Requests response with the Retry-After header indicating how many seconds to wait.
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
Retry-After: 60
Content-Type: application/json{
"error": {
"message": "Rate limit exceeded. Please try again later.",
"code": "RATE_LIMIT_EXCEEDED"
}
}Retry Strategy
When you receive a 429 response, implement exponential backoff with jitter. This spreads retries over time and prevents thundering-herd effects.
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
if (attempt === maxRetries) {
throw new Error('Rate limit exceeded after max retries');
}
// Exponential backoff with jitter
const baseDelay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
const jitter = Math.random() * 1000;
const delay = baseDelay + jitter;
await new Promise(resolve => setTimeout(resolve, delay));
}
}Best Practices
Monitor the X-RateLimit-Remaining header
Proactively slow down before hitting the limit.
Optimize your queries
- Request only the fields you need to reduce server processing time
- Use pagination with reasonable page sizes (100–500) instead of requesting everything at once
- Cache responses when the data doesn’t change frequently
- Batch related data into a single GraphQL query instead of multiple requests
Use appropriate page sizes
Instead of making many small requests, make fewer, larger requests.
const response = await fetch(url, options);
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
if (remaining < 10) {
// Approaching the limit — add a delay
await new Promise(resolve => setTimeout(resolve, 2000));
}# Bad: 1000 requests for 1000 records (hits rate limit)
query { programProgress(input: { first: 1 }) { edges { node { ... } } } }
# Good: 10 requests for 1000 records
query { programProgress(input: { first: 100 }) { edges { node { ... } } } }