> ## Documentation Index
> Fetch the complete documentation index at: https://docs.squarecloud.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK: GitHub deploys

> Deploy from GitHub with client.apps.deploys: set a webhook, link a repository through the Square Cloud GitHub App, and read the deploy history.

`client.apps.deploys` connects an app to GitHub so a push deploys it. There are two ways to do it: a **webhook** that you add to the repository, or the **Square Cloud GitHub App**.

Examples use the `client` from [Creating the client](/en/sdks/py/client#creating-the-client). `app_id` is the id of one of your apps: [`client.account.me()`](/en/sdks/py/client#account) lists them.

## Webhook

`deploys.set_webhook(app_id, access_token)` configures GitHub webhook deploys with a GitHub access token (`ghp_...` or `github_pat_...`) and returns the webhook URL to add to your repository.

```python theme={"system"}
url = client.apps.deploys.set_webhook(app_id, os.environ["GITHUB_TOKEN"])

print(url)  # add it as a webhook in the repository settings
```

Pass `"@"` as the token to remove the webhook. The method then returns `''`.

```python theme={"system"}
client.apps.deploys.set_webhook(app_id, "@")
```

An invalid token is 400 `INVALID_ACCESS_TOKEN`.

## GitHub App

`deploys.link_github_app(app_id, repository, branch)` links a repository installed through the Square Cloud GitHub App. It needs the `apps:deploy` scope.

```python theme={"system"}
repo = client.apps.deploys.link_github_app(app_id, "octocat/hello-world", "main")

print(repo["id"], repo["full_name"], repo["branch"])
```

`deploys.unlink_github_app(app_id)` removes the link. To link another repository or branch, unlink first.

```python theme={"system"}
client.apps.deploys.unlink_github_app(app_id)
```

Link and unlink share a limit of **3 calls per 60 seconds**.

| Status | Code | When |
| - | - | - |
| 400 | `GIT_ALREADY_CONFIGURED` | The app already has a linked repository: unlink it first |
| 400 | `GIT_NOT_CONFIGURED` | `unlink_github_app` with nothing linked |
| 400 | `BRANCH_NOT_FOUND`, `INVALID_BRANCH_LENGTH` | The branch does not exist, or its name is over 256 characters |
| 403 | `GITHUB_NOT_CONNECTED` | Your account has no GitHub App installation |
| 403 | `REPOSITORY_NOT_AVAILABLE` | The GitHub App is not installed on the repository through your GitHub account |
| 403 | `REPOSITORY_PERMISSION_REQUIRED` | Your GitHub account has no write access to the repository |
| 404 | `REPOSITORY_NOT_FOUND` | The repository does not exist or is not visible |
| 409 | `REPOSITORY_BRANCH_ALREADY_CONFIGURED` | Another app, of any account, already links this repository and branch. Its id is in `message` only when that app is yours |
| 502 | `FAILED_TO_FETCH` | GitHub did not confirm the branch: safe to retry |

## Current configuration

`deploys.current(app_id)` returns what is configured: `app` for the GitHub App link, `webhook` for the webhook URL. It returns `{}` when nothing is set.

```python theme={"system"}
current = client.apps.deploys.current(app_id)

if "app" in current:
    print(current["app"]["name"], current["app"]["branch"])  # e.g. "octocat/hello-world" "main"
if "webhook" in current:
    print(current["webhook"])
```

## Deploy history

`deploys.list(app_id)` returns one **timeline per deploy**, each a list of events, oldest event first. Only Git deploys (webhook and GitHub App pushes) are recorded.

```python theme={"system"}
for timeline in client.apps.deploys.list(app_id):
    last = timeline[-1] if timeline else None
    if last is None:
        continue
    print(last["id"], last["state"], last["date"])

    if last["state"] == "error":
        print(last.get("code"), last.get("message"))
```

| Field | Description |
| - | - |
| `id` | The commit SHA |
| `state` | `pending`, `clone`, `commit`, `restarting`, `success` or `error` |
| `date` | When the event happened |
| `source` | Always `"git"` |
| `branch` | On `clone` events |
| `files` | On `commit` events: `{ added, removed, modified }` |
| `code` / `message` | On `error` events: why the deploy failed (for example `CLONE_FAILED`) |

## Next steps

<CardGroup cols={3}>
  <Card title="Network" icon="globe" href="/en/sdks/py/network">
    Analytics, DNS, custom domains and cache.
  </Card>

  <Card title="Deploy API reference" icon="code" href="/en/api-reference/endpoint/apps/deploy/list">
    The REST endpoints behind these methods.
  </Card>

  <Card title="GitHub deploys from the CLI" icon="terminal" href="/en/cli-reference/github-deploys">
    The same actions from the terminal.
  </Card>
</CardGroup>
