errors
The API uses ordinary HTTP status codes. FastAPI HTTPException bodies use detail. Some older stats errors still look like a zeroed GitHubStatsResponse. Redis middleware can answer 404 and 429 before a route runs.
Status codes#
| Status | When |
|---|---|
200 |
Success. JSON, HTML, or image/svg+xml for the stats card. |
400 |
Bad heatmap window. Unknown view, or view=year without year. |
404 |
GitHub user missing, or a cached invalid handle. |
429 |
This API’s rate limit (IP or handle), not GitHub’s. |
500 |
Server config or uncaught failure. Missing GITHUB_TOKEN is the common case. |
502 |
GitHub returned an error that is not 404 and not a throttle. |
503 |
GitHub primary or secondary rate limit. Do not treat this as a missing user. |
GitHub 403 with x-ratelimit-remaining: 0 or retry-after is 503, not 404. Mapping throttle to 404 used to blacklist a real login in the invalid-user cache.
Error shapes#
FastAPI detail#
Most handlers raise HTTPException. Body is:
{
"detail": "User tashifkhan not found"
}
Examples: User {username} not found, GitHub API error, GitHub API rate limit exceeded, please retry shortly, Failed to retrieve pull requests, GitHub token not configured.
Stats-shaped error#
GitHubStatsResponse.error() still exists for the combined stats payload:
{
"status": "error",
"message": "User not found or API error",
"topLanguages": [],
"totalCommits": 0,
"longestStreak": 0,
"currentStreak": 0,
"profile_visitors": 0,
"contributions": null
}
Missing token:
{
"status": "error",
"message": "GitHub token not configured",
"topLanguages": [],
"totalCommits": 0,
"longestStreak": 0,
"currentStreak": 0
}
Prefer the HTTP status over message. A 200 with status: success is the happy path for enveloped routes.
Rate limit from this API#
Redis middleware, when cache is enabled:
{
"status": "error",
"message": "Rate limit exceeded",
"retryAfter": 12,
"limitedBy": "ip"
}
Headers: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. limitedBy is ip, handle, invalid-ip, or invalid-handle.
Cached missing user:
{
"status": "error",
"message": "User does not exist"
}
That response includes X-Cache: NEGATIVE-HIT.
This API’s rate limits#
Defaults when Redis is configured. Override with env vars.
| Window | Default |
|---|---|
| Per IP | 60 requests / 60s (RATE_LIMIT_IP_REQUESTS) |
| Per handle | 30 requests / 60s (RATE_LIMIT_HANDLE_REQUESTS) |
| Invalid-user IP | 10 / 600s |
| Invalid-user handle | 5 / 600s |
Successful 200s cache for API_CACHE_TTL_SECONDS (default 3600). SVG uses its own 24h Cache-Control. Invalid users cache for INVALID_USER_CACHE_TTL_SECONDS (default 300). /, /docs, /redoc, /openapi.json, and /favicon.ico skip this cache.
Without Redis, this layer does not run. GitHub’s 5000/hour token budget still applies. Attribution walks stop when remaining GitHub calls drop below ATTRIBUTION_RATE_LIMIT_FLOOR (default 500) so they do not starve every other route.
What to retry#
429from this API: waitRetry-After.503from GitHub throttle: wait and retry. Do not cache that handle as missing.502: GitHub hiccup. Retry once.404: the login is gone, or it was recently 404-cached. Wait out the invalid TTL if you just created the account.400on heatmap: fixview/year.500withGitHub token not configured: operator problem, not the caller.