# Talking to the GitHub API with httpx

With local scaffolding done, `scaffoldr new` needed to create a repo on GitHub. That means authenticating with a token, sending HTTP requests to GitHub's REST API, and handling whatever comes back - success or failure.

## Choosing httpx

I used `httpx` for this instead of the standard library's `urllib` or the more common `requests` library. `httpx` has a `requests`\-like API, but supports sync and async out of the box. scaffoldr only needs sync calls, but I wanted the option to move to async later without switching libraries.

## Finding the token

Every request to GitHub needs a personal access token. `_get_token` looks for one in two places:

```python
def _get_token() -> str:
    token = os.environ.get("SCAFFOLDR_GITHUB_TOKEN")
    if token:
        return token
    cfg = Config.load()
    if cfg.github_token:
        return cfg.github_token
    typer.echo(
        "Error no GitHub token found.\n"
        "Set SCAFFOLDR_GITHUB_TOKEN env var or run "
        "`scaffoldr config init` to save a token.",
        err=True,
    )
    raise typer.Exit(code=1)
```

It checks the `SCAFFOLDR_GITHUB_TOKEN` environment variable, then falls back to the saved config file. Checking the environment variable first means anyone who doesn't want a token sitting in a config file on disk has that option, without scaffoldr forcing one approach.

If neither exists, it prints a message telling the user exactly how to fix it, then exits.

## Building an authenticated client

Once there's a token, `_client` builds an `httpx.Client` configured to talk to GitHub:

```python
def _client() -> httpx.Client:
    token = _get_token()
    return httpx.Client(
        base_url=GITHUB_API,
        headers={
            "Authorization": f"Bearer {token}",
            "Accept": "application/vnd.github+json",
            "X-GitHub-Api-Version": "2022-11-28",
        },
        timeout=10.0,
    )
```

Setting `base_url` means requests only need a path - `/user/repos` instead of the full `https://api.github.com/user/repos` every time. The headers identify the request (`Authorization`), set the expected response format (`Accept`), and pin a specific API version (`X-GitHub-Api-Version`) so future GitHub API changes don't silently break scaffoldr. `timeout=10.0` fails a request after 10 seconds instead of hanging forever.

## Creating the repo

`create_repo` sends the actual request:

```python
def create_repo(
    name: str,
    description: str = "",
    private: bool = False,
) -> dict:
    with _client() as client:
        response = client.post(
            "/user/repos",
            json = {
                "name": name,
                "description": description,
                "private": private,
                "auto_init": False,
            },
    )
```

`with _client() as client` uses the client as a context manager, which closes the underlying HTTP connection automatically once the block finishes - no separate `client.close()` call needed.

`auto_init: False` tells GitHub not to create a default README or initial commit. scaffoldr already builds the project locally and pushes its own first commit - if GitHub also created one, the two histories wouldn't match when scaffoldr tries to push.

## Handling specific failures

GitHub's API returns different status codes depending on what went wrong. `422` and `401` are common enough, and different enough from each other, to be worth a specific message:

```python
if response.status_code == 422:
    typer.echo(
        f"Error: repo '{name}' already exists on GitHub.",
        err=True,
    )
    raise typer.Exit(code=1)
if response.status_code == 401:
    typer.echo(
        "Error: invalid or expired GitHub token.", err=True
    )
    raise typer.Exit(code=1)
```

`422` from this endpoint almost always means the repo name is already taken. `401` means the token is missing, wrong, or expired. Both tell the user exactly what happened instead of a generic "request failed."

Everything else falls through:

```python
if not response.is_success:
    typer.echo(
        f"Error: GitHub API returned "
        f"{response.status_code} - "
        f"{response.text}",
        err=True,
    )
    raise typer.Exit(code=1)
```

`response.is_success` is `httpx`'s shorthand for "status code between 200 and 299." This is the fallback for anything that doesn't fit the two cases above - less specific, but it still shows the actual status code and response body, which is enough to debug something unexpected.

## What's next

With the repo created, the next step was pushing the local commit to it and creating the default issues every new project gets. That's the next post.

The code is on [GitHub](https://github.com/scaffoldr)
