> Source: https://wexa.ai/docs/context/code-sync-api

# Code sync

The code sync routes keep a repository in the code graph. They need the `fabric:codesync.write` grant and a project-scoped credential, except `names`.

**Read the resolved scope**

**REST API**

```bash
curl -sS -H "Authorization: Bearer $FABRIC_API_KEY" \
  https://fabric.wexa.ai/v1/codesync/names
```

```json
{ "org_id": "org_abc", "dept_id": "dept_abc", "project_id": "proj_abc", "user_id": "user_abc" }
```

## Parameters

| Parameter | Type | Required | Notes |
| --- | --- | --- | --- |
| `repo_url` | string | Required | `POST /v1/codesync/bootstrap`: the clone URL, matched against the webhook payload's `clone_url`. |
| `repo_name` | string | Required | `bootstrap`: the `owner/name` form, matched against the payload's `full_name`. |
| `ttl_seconds` | integer | Optional | `bootstrap`: how long the binding stays valid. |
| `connector_id`, `nodes`, `edges` | body | Optional | `POST /v1/codesync/ingest-event` and `bulk-ingest`: a client-shaped body, forwarded as sent. A body over the size cap is refused, not truncated. |
| `job_id` | string | Required | `GET /v1/codesync/ingest-status/{job_id}`: the job `ingest-async` returned. Poll on an interval; it counts against quota. |
| none | | | `names`, `connector` and `bolt-credentials` take no parameters. |

**Errors**

| Status | Code | Raised when |
| --- | --- | --- |
| 400 | `invalid_request` | The request body cannot be read or is not valid JSON. |
| 403 | `forbidden` | The credential lacks `fabric:codesync.write`, or has no project. |
| 502 | `unconfigured` | No ingestion target is configured, or no connector authority is running. |
| 401 | `invalid_signature` | The GitHub webhook signature is wrong or missing. |
| 404 | `repo_not_bound` | A push arrived for a repository never bootstrapped. |

**More details**

Call `connector` and `bootstrap` before the webhook can resolve a push; the three ingest routes are alternatives, not stages.

Every route but the GitHub push receiver is project-scoped REST behind one grant. The receiver is authenticated by signature rather than by credential.

### One grant gates almost all of it

| Grant | Routes |
|---|---|
| `fabric:codesync.write` | all eight credentialled routes except `GET /v1/codesync/names` |

The same check also requires a project-scoped credential. A token holding the grant but no project
is refused with `"code sync requires a project-scoped token (re-consent with a project selected)"`.

### `GET /v1/codesync/names`

The identity echo. It is the one route on this page that needs no grant, and it exists so a client
can discover which organization, department, project and user its credential resolves to before it
starts sending data anywhere.

```bash
curl -sS -H "Authorization: Bearer $FABRIC_API_KEY" \
  https://fabric.wexa.ai/v1/codesync/names
```

```json
{ "org_id": "org_abc", "dept_id": "dept_abc", "project_id": "proj_abc", "user_id": "user_abc" }
```

Where the platform can resolve them, the response also carries the human-readable names alongside
the ids. The run quoted above could not, so only the ids came back —
treat the name fields as present-when-available rather than guaranteed.

### `POST /v1/codesync/connector`

Finds or provisions the codebase connector for the token's project, then triggers a connector synchronisation. Idempotent.

The body is empty. When the connector authority is unreachable the call fails loudly rather than
half-succeeding:

```json
{ "error": "data_service_unreachable",
  "error_description": "the connector authority refused the connection" }
```

### `POST /v1/codesync/bootstrap`

Binds a repository — by URL and name — to the project's connector, and returns the binding the
GitHub webhook later looks up.

```bash
curl -sS -X POST https://fabric.wexa.ai/v1/codesync/bootstrap \
  -H "Authorization: Bearer $FABRIC_API_KEY" -H "Content-Type: application/json" \
  -d '{"repo_url":"https://github.com/acme/widgets","repo_name":"acme/widgets","ttl_seconds":3600}'
```

| Field | Meaning |
|---|---|
| `repo_url` | the clone URL, matched against the webhook payload's `clone_url` |
| `repo_name` | the `owner/name` form, matched against the payload's `full_name` |
| `ttl_seconds` | how long the binding stays valid |

### `POST /v1/codesync/ingest-event` and `POST /v1/codesync/bulk-ingest`

The two synchronous ingest routes. `ingest-event` carries one change; `bulk-ingest` carries a batch.
Both take a client-shaped body, forward it, and return the ingestion target's JSON verbatim with its
status code.

```bash
curl -sS -X POST https://fabric.wexa.ai/v1/codesync/ingest-event \
  -H "Authorization: Bearer $FABRIC_API_KEY" -H "Content-Type: application/json" \
  -d '{"connector_id":"conn_abc",
       "nodes":[{"id":"f1","kind":"File","path":"src/app.ts"}],
       "edges":[]}'
```

```json
{ "ok": true, "nodes": 3, "edges": 5 }
```

```json
{ "ok": true, "files": 2, "nodes": 18, "edges": 41 }
```

Request bodies are capped; a body over the limit is refused rather than truncated.

If the ingestion target is not configured on the deployment, these routes answer `502` with
`{"error":"unconfigured", ...}` with a description saying the ingestion target is not set rather than pretending
to have accepted the data.

### `POST /v1/codesync/ingest-async` and `GET /v1/codesync/ingest-status/{job_id}`

The asynchronous pair, which is what the editor extension uses for a full extraction: a first
request queues the work and returns immediately, and a second route polls it.

The payload the queue receives carries the token's organization and project, whatever the client sent. Poll on an interval, not in a tight loop: the status route counts against the project and organization quota windows.

```json
{ "job_id": "job_abc_0001", "status": "queued" }
```

```bash
curl -sS -H "Authorization: Bearer $FABRIC_API_KEY" \
  https://fabric.wexa.ai/v1/codesync/ingest-status/job_abc_0001
```

```json
{ "job_id": "job_abc_0001", "status": "completed", "nodes": 12, "edges": 30, "processed": 42 }
```

### `GET /v1/codesync/bolt-credentials`

Returns the connection details for reading the project's graph directly, for clients that query it
rather than going through the gateway.

```json
{ "uri": "bolt://graph.example.com:7687", "username": "proj_abc",
  "password": "…", "database": "proj_abc" }
```

The response is a secret: do not log it, and do not cache it where a second project could read it. The values above are from a stand-in target, not a real deployment.

### `POST /v1/codesync/webhook/github`

The GitHub push receiver. It takes no bearer token — GitHub does not hold one — and is authenticated
by an HMAC-SHA256 signature over the raw request body, sent in `X-Hub-Signature-256`.

Four outcomes, all observed live on a gateway with the shared secret configured:

| Request | Response |
|---|---|
| `X-GitHub-Event: ping`, correct signature | `200` `{"ok":true,"pong":true}` |
| any event, wrong or missing signature | `401` `{"error":"invalid_signature","error_description":"GitHub webhook signature mismatch"}` |
| `X-GitHub-Event: issues` (or any non-push, non-ping event) | `200` `{"ok":true,"ignored":"issues"}` |
| `X-GitHub-Event: push` for a repository never bootstrapped | `404` `{"error":"repo_not_bound","error_description":"call POST /v1/codesync/bootstrap with repo_name first, …"}` |

### Per-route ledger

| Route | Result |
|---|---|
| `GET /v1/codesync/names` | `200` with the four resolved ids |
| `POST /v1/codesync/connector` | `403` without the grant, `502` with it (no connector authority running) |
| `POST /v1/codesync/bootstrap` | `403` without the grant, `502` with it (no connector authority running) |
| `POST /v1/codesync/ingest-event` | `403` without the grant, `200` with it — tenant pinning observed downstream |
| `POST /v1/codesync/bulk-ingest` | `403` without the grant, `200` with it |
| `POST /v1/codesync/ingest-async` | `200`, `job_id` returned |
| `GET /v1/codesync/ingest-status/{job_id}` | `200`, status echoed |
| `GET /v1/codesync/bolt-credentials` | `403` without the grant, `200` with it |
| `POST /v1/codesync/webhook/github` | all four outcomes above, signed and unsigned |

The `nodes`, `edges` and `processed` counts quoted depend on what your own extraction produces.

Every tool call works the same way: [How every tool call works](/docs/sdks-and-apis/how-every-tool-call-works).
