Rate Limiting
API rate limits, headers, and best practices for staying within bounds.
All Drok API endpoints are rate-limited to ensure fair usage and platform stability. Rate limits are per-user for authenticated requests and per-IP for unauthenticated requests.
Rate Limits
| Category | Limit | Window |
|---|---|---|
| Authenticated API | 5,000 requests | Per hour |
| Unauthenticated API | 60 requests | Per hour |
| Authentication endpoints | 10 requests | Per minute |
| Search API | 30 requests | Per minute |
| Git operations (clone/push/pull) | 1,000 operations | Per hour |
| GraphQL API | 5,000 points | Per hour |
| Package registry | 1,000 requests | Per hour |
| Webhook delivery | 10,000 deliveries | Per hour per endpoint |
Enterprise Limits
Enterprise organizations can request custom rate limits based on their usage patterns. Contact your account representative or email enterprise@drok.us.
Rate Limit Headers
Every API response includes rate limit headers:
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4997
X-RateLimit-Reset: 1710523200
X-RateLimit-Resource: core| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
X-RateLimit-Resource | The rate limit category (core, search, graphql) |
Exceeding the Limit
When you exceed the rate limit, the API returns 429 Too Many Requests:
{
"error": "rate_limit_exceeded",
"message": "API rate limit exceeded. Try again at 2024-03-15T15:00:00Z.",
"reset_at": "2024-03-15T15:00:00Z",
"documentation_url": "https://docs.drok.us/api-reference/rate-limiting"
}The response includes a Retry-After header indicating how many seconds to wait:
Retry-After: 120GraphQL Rate Limiting
GraphQL requests are rate-limited by query complexity, not request count. Each field in a GraphQL query costs points:
| Field Type | Cost |
|---|---|
| Scalar field | 1 point |
| Object field | 2 points |
| Connection (list) | 2 points + (first/last * node cost) |
| Mutation | 10 points base |
The query cost is returned in the response extensions:
{
"data": { ... },
"extensions": {
"rateLimit": {
"limit": 5000,
"remaining": 4950,
"cost": 50,
"resetAt": "2024-03-15T15:00:00Z"
}
}
}Best Practices
Use Conditional Requests
Use If-None-Match with ETags to avoid consuming rate limit for unchanged resources:
# First request — returns ETag
curl -I https://drok.us/api/v1/repos/my-org/my-repo \
-H "Authorization: Bearer $Drok_TOKEN"
# Response: ETag: "abc123"
# Subsequent request — returns 304 if unchanged (does not count against rate limit)
curl https://drok.us/api/v1/repos/my-org/my-repo \
-H "Authorization: Bearer $Drok_TOKEN" \
-H "If-None-Match: \"abc123\""Use Webhooks Instead of Polling
Instead of polling for changes, configure webhooks to receive push notifications:
drok webhook create my-org/my-repo \
--url https://your-service.com/webhook \
--events push,merge_requestUse GraphQL for Complex Queries
A single GraphQL query can retrieve data that would require multiple REST requests:
query {
repository(owner: "my-org", name: "my-repo") {
mergeRequests(state: OPEN, first: 10) {
nodes {
title
author { username }
reviews { nodes { state } }
pipeline { status }
}
}
}
}Implement Exponential Backoff
When rate-limited, implement exponential backoff:
import time
import requests
def api_request(url, headers, max_retries=5):
for attempt in range(max_retries):
response = requests.get(url, headers=headers)
if response.status_code != 429:
return response
retry_after = int(response.headers.get("Retry-After", 60))
wait_time = min(retry_after, 2 ** attempt * 10)
time.sleep(wait_time)
raise Exception("Rate limit exceeded after max retries")