# How to Manage GitHub API Rate Limiting and Handle Errors in Spec-Kit

> Effectively manage GitHub API rate limiting and handle errors with Spec-Kit. Learn how to parse headers, raise exceptions, and use authentication tokens to avoid API access issues.

- Repository: [GitHub/spec-kit](https://github.com/github/spec-kit)
- Tags: how-to-guide
- Published: 2026-03-05

---

**Spec-Kit handles GitHub API rate limiting by parsing standard HTTP headers to extract limit details and reset times, then raises descriptive RuntimeError exceptions that guide users toward authentication tokens when quotas are exhausted.**

The Spec-Kit CLI interacts with the GitHub REST API to download release assets for AI assistant templates, making robust **GitHub API rate limiting and error handling** essential for reliable operation. When unauthenticated requests hit the 60-requests-per-hour ceiling or authenticated users approach the 5,000-requests-per-hour limit, the tool degrades gracefully with actionable feedback rather than opaque HTTP errors. The implementation in [`src/specify_cli/__init__.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/__init__.py) provides a transparent strategy for detecting throttling and guiding remediation.

## Parsing GitHub API Rate Limit Headers

The foundation of Spec-Kit's rate limit management lies in `_parse_rate_limit_headers`, a private helper defined at lines 69‑86 in [`src/specify_cli/__init__.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/__init__.py). This function extracts the standard `X-RateLimit-*` headers—**`Limit`**, **`Remaining`**, and **`Reset`**—along with the optional **`Retry-After`** header.

The parser converts the epoch timestamp from `X-RateLimit-Reset` into a UTC `datetime` object and derives a local-time version for easier user display. It also handles integer conversion for `Retry-After` values when present, returning a structured dictionary that downstream functions consume for error formatting.

```python
def _parse_rate_limit_headers(headers: httpx.Headers) -> dict:
    info = {}
    if "X-RateLimit-Limit" in headers:
        info["limit"] = headers.get("X-RateLimit-Limit")
    if "X-RateLimit-Remaining" in headers:
        info["remaining"] = headers.get("X-RateLimit-Remaining")
    if "X-RateLimit-Reset" in headers:
        reset_epoch = int(headers.get("X-RateLimit-Reset", "0"))
        if reset_epoch:
            reset = datetime.fromtimestamp(reset_epoch, tz=timezone.utc)
            info["reset_epoch"] = reset_epoch
            info["reset_time"] = reset
            info["reset_local"] = reset.astimezone()
    if "Retry-After" in headers:
        try:
            info["retry_after_seconds"] = int(headers.get("Retry-After"))
        except ValueError:
            info["retry_after"] = headers.get("Retry-After")
    return info

```

## Formatting User-Friendly Error Messages

Once headers are parsed, `_format_rate_limit_error` (lines 97‑124 in [`src/specify_cli/__init__.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/__init__.py)) assembles a multi-line message that surfaces critical details to developers. The function receives the HTTP status code, raw headers, and the URL that triggered the failure, then calls the parser to extract structured data.

The formatted output includes the **rate limit ceiling**, **remaining requests**, **exact reset time in local timezone**, and **retry-after duration** when available. It concludes with **troubleshooting tips** that explicitly recommend using a personal access token via `--github-token`, `GH_TOKEN`, or `GITHUB_TOKEN` environment variables to upgrade from the 60-requests-per-hour anonymous quota to the 5,000-requests-per-hour authenticated tier.

```python
def _format_rate_limit_error(status_code: int, headers: httpx.Headers, url: str) -> str:
    rate_info = _parse_rate_limit_headers(headers)
    lines = [f"GitHub API returned status {status_code} for {url}", ""]
    if rate_info:
        lines.append("[bold]Rate Limit Information:[/bold]")
        if "limit" in rate_info:
            lines.append(f"  • Rate Limit: {rate_info['limit']} requests/hour")
        if "remaining" in rate_info:
            lines.append(f"  • Remaining: {rate_info['remaining']}")
        if "reset_local" in rate_info:
            lines.append(
                f"  • Resets at: {rate_info['reset_local'].strftime('%Y-%m-%d %H:%M:%S %Z')}"
            )
        if "retry_after_seconds" in rate_info:
            lines.append(f"  • Retry after: {rate_info['retry_after_seconds']} seconds")
        lines.append("")
    lines.extend(
        [
            "[bold]Troubleshooting Tips:[/bold]",
            "  • If you're on a shared CI or corporate environment, you may be rate‑limited.",
            "  • Consider using a GitHub token via --github-token or the GH_TOKEN/GITHUB_TOKEN environment variable to increase rate limits.",
            "  • Authenticated requests have a limit of 5,000 /hour vs 60 /hour for unauthenticated.",
        ]
    )
    return "\n".join(lines)

```

## Implementing Rate Limit Handling in Download Workflows

The `download_template_from_github` function (lines 721‑724 and 771‑774) applies this error handling logic during both metadata retrieval and asset streaming. When fetching release information from `https://api.github.com/repos/github/spec-kit/releases/latest`, the CLI checks for a `200 OK` response. Any other status triggers a `RuntimeError` containing the formatted rate limit message.

The same validation occurs when initiating the download stream for the actual template asset, ensuring consistent **GitHub API error handling** across both the lightweight JSON API call and the potentially large binary transfer. Top-level command handlers catch these exceptions, render them inside a Rich `Panel` for visual clarity, and exit with `typer.Exit(1)` to signal failure to shell environments and CI pipelines.

```python
def download_template_from_github(ai_assistant: str, download_dir: Path, *,
                                 script_type: str = "sh", verbose: bool = True,
                                 client: httpx.Client = None,
                                 github_token: str = None) -> Tuple[Path, dict]:
    api_url = "https://api.github.com/repos/github/spec-kit/releases/latest"
    response = client.get(api_url,
                          headers=_github_auth_headers(github_token),
                          timeout=30,
                          follow_redirects=True)

    if response.status_code != 200:
        # Rate‑limit aware error handling

        raise RuntimeError(_format_rate_limit_error(response.status_code,
                                                    response.headers,
                                                    api_url))

    # ... locate matching asset ...

    with client.stream("GET", download_url,
                       headers=_github_auth_headers(github_token)) as dl:
        if dl.status_code != 200:
            raise RuntimeError(_format_rate_limit_error(dl.status_code,
                                                        dl.headers,
                                                        download_url))
        # continue streaming download...

```

When the anonymous quota is exhausted, users see structured output like this:

```text
GitHub API returned status 403 for https://api.github.com/repos/github/spec-kit/releases/latest

Rate Limit Information:
  • Rate Limit: 60 requests/hour
  • Remaining: 0
  • Resets at: 2026-03-06 03:15:42 UTC
  • Retry after: 3600 seconds

Troubleshooting Tips:
  • If you're on a shared CI or corporate environment, you may be rate-limited.
  • Consider using a GitHub token via --github-token or the GH_TOKEN/GITHUB_TOKEN environment variable to increase rate limits.
  • Authenticated requests have a limit of 5,000/hour vs 60/hour for unauthenticated.

```

## Summary

- **Header Inspection**: The `_parse_rate_limit_headers` function in [`src/specify_cli/__init__.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/__init__.py) extracts `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers to determine exactly when quotas reset.
- **Actionable Errors**: `_format_rate_limit_error` converts raw HTTP data into human-readable messages that specify reset times and authentication remedies.
- **Workflow Integration**: The `download_template_from_github` function applies these helpers to both API metadata requests and asset download streams, raising `RuntimeError` exceptions that bubble up to Rich-rendered CLI output.
- **Authentication Guidance**: The error messages explicitly guide users to supply tokens via `--github-token`, `GH_TOKEN`, or `GITHUB_TOKEN` to upgrade from 60 requests/hour to 5,000 requests/hour.

## Frequently Asked Questions

### What are the GitHub API rate limits when using Spec-Kit?

Unauthenticated requests are limited to **60 requests per hour**, while authenticated requests using a personal access token allow **5,000 requests per hour**. The Spec-Kit source code references these limits in error messages to encourage token usage when the anonymous quota is exhausted.

### How does Spec-Kit detect when rate limiting occurs?

The tool inspects HTTP response headers via `_parse_rate_limit_headers` in [`src/specify_cli/__init__.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/__init__.py) (lines 69‑86). It looks for `X-RateLimit-Remaining` reaching zero or receiving a `403 Forbidden` status, then parses the `X-RateLimit-Reset` epoch timestamp to calculate when the quota refreshes.

### What should I do when I see a rate limit error in Spec-Kit?

Supply a **GitHub personal access token** using the `--github-token` CLI option or by setting the `GH_TOKEN` or `GITHUB_TOKEN` environment variable. The error output explicitly states when your current quota resets and reminds you that authenticated access provides significantly higher limits than anonymous usage.

### Where is the rate limiting logic implemented in the Spec-Kit codebase?

The core logic resides in [`src/specify_cli/__init__.py`](https://github.com/github/spec-kit/blob/main/src/specify_cli/__init__.py). The helper functions `_parse_rate_limit_headers` and `_format_rate_limit_error` live at lines 69‑86 and 97‑124 respectively, while the `download_template_from_github` function (lines 721‑724 and 771‑774) consumes these helpers during release asset retrieval.