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

# Sync API

> Manual GitHub sync trigger and webhook secret provisioning.

# Sync

The sync endpoints let you pull the linked GitHub repo into Hubify on demand, check the last sync state, and provision the per-lab webhook secret.

For automated push-driven sync, configure the [GitHub webhook](#github-webhook).

**Base URL:** `https://www.hubify.com/api/v1`

## Manual sync

Pull the current default-branch HEAD into Hubify. Useful for first-time sync after linking a repo, or for reconciling drift if a webhook event was missed.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://www.hubify.com/api/v1/sync \
    -H "Authorization: Bearer $HUBIFY_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "labId": "<id>",
      "subPath": "papers",
      "ref": "abc123def..."
    }'
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch("https://www.hubify.com/api/v1/sync", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.HUBIFY_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      labId,
      subPath: "papers",   // optional: limit sync to a subtree
      ref: "abc123def...", // optional: sync a specific commit/branch
    }),
  });
  const { ok, pulled, updated, skipped, removed, sha } = await res.json();
  ```
</CodeGroup>

Required scope: `lab:<slug>:rw`.

Response:

```json theme={null}
{
  "ok": true,
  "pulled": 12,
  "updated": 3,
  "skipped": 47,
  "removed": 1,
  "sha": "abc123def..."
}
```

## Sync status

<CodeGroup>
  ```bash curl theme={null}
  curl "https://www.hubify.com/api/v1/sync?labId=<id>" \
    -H "Authorization: Bearer $HUBIFY_TOKEN"
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(
    `https://www.hubify.com/api/v1/sync?labId=${labId}`,
    { headers: { Authorization: `Bearer ${process.env.HUBIFY_TOKEN}` } }
  );
  const status = await res.json();
  ```
</CodeGroup>

Required scope: `lab:<slug>:r`.

```json theme={null}
{
  "repo": "https://github.com/owner/repo",
  "defaultBranch": "main",
  "lastSyncAt": 1714368000000,
  "lastSyncSha": "a1b2c3d...",
  "autoPushOnEdit": true
}
```

## Provision webhook secret

Mint or fetch the per-lab GitHub webhook secret. The secret is returned on first call so you can paste it into GitHub's webhook settings; subsequent calls return the same value (idempotent, we don't rotate by accident).

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://www.hubify.com/api/v1/sync/secret \
    -H "Authorization: Bearer $HUBIFY_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{ "labId": "<id>" }'
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch("https://www.hubify.com/api/v1/sync/secret", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.HUBIFY_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ labId }),
  });
  const { secret, existed, webhookUrl } = await res.json();
  ```
</CodeGroup>

Required scope: `lab:<slug>:admin`.

```json theme={null}
{
  "secret": "...64 hex chars...",
  "existed": false,
  "webhookUrl": "https://www.hubify.com/api/webhooks/github"
}
```

## GitHub webhook

```http theme={null}
POST /api/webhooks/github
X-GitHub-Event: push
X-Hub-Signature-256: sha256=...
```

Hubify verifies the HMAC signature against the lab's `gitWebhookSecret` (provisioned above), then runs `syncFromGithub` for the pushed commit.

* `ping` event: ack with 200 (no sync)
* `push` event on default branch: full sync
* `push` event on non-default branch: ignored
* Other events (star, fork, issues, etc.): ignored

Response on success:

```json theme={null}
{
  "ok": true,
  "delivery": "1234-5678-...",
  "result": { "ok": true, "pulled": 0, "updated": 1, "skipped": 0, "removed": 0, "sha": "..." }
}
```

## Errors

| Status | Meaning                                                                   |
| ------ | ------------------------------------------------------------------------- |
| 400    | Missing `labId` / invalid JSON / missing `repository.full_name` (webhook) |
| 401    | Invalid API key (REST) or signature mismatch (webhook)                    |
| 403    | Key lacks the required scope                                              |
| 404    | Lab not found                                                             |
| 412    | Lab has no GitHub repo linked, or webhook secret not provisioned yet      |
| 429    | Rate limit                                                                |
| 500    | Sync failed, see response body or activity feed                           |

See the [Local Agents guide](/docs/guides/local-agents) for the end-to-end setup walkthrough.
