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

# Code sync

Code sync keeps a graph of your codebase current: files, symbols and the edges between them, kept in
step with what is on disk. Two clients drive it — the `fabric-connect` launcher running beside a
checkout, and the editor extension — and both speak the nine routes on this page.

Eight of the nine are project-scoped REST behind one grant. The ninth is an inbound GitHub push
receiver, 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 so the codebase ontology exists before the first ingest. Call it once, at the start
of a session; it is 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": "Post \"https://<data-plane>/connectors/auto-provision\": connection refused" }
```

## `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 |

Bootstrap is a prerequisite for the GitHub receiver, not an optional extra: a push for a repository
that was never bootstrapped is refused, and the refusal says so.

## `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","error_description":"CONTEXT_SERVICE_URL 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.

```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 }
```

The same tenant pinning applies to `ingest-async`: the payload the queue receives carries the
token's organization and project, whatever the client sent. Poll on an interval rather than a tight
loop — the status route counts against the project and organization quota windows like any other
call.

## `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://cognodb.internal:7687", "username": "proj_abc",
  "password": "…", "database": "proj_abc" }
```

These are credentials. Treat the response as a secret: do not log it, and do not cache it anywhere
a second project could read. 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`. It is covered
alongside the other inbound callbacks on [Webhooks](/docs/api/webhooks); the behaviour that belongs
here is what it does with a push.

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, …"}` |

For a bound repository the receiver collects the added, modified and removed paths across the
commits in the payload, stamps the repository node with the branch and the head commit, and deletes
the removed paths so the graph never drifts worse than stale.

## Choosing between the three ingest paths

| You have | Use |
|---|---|
| one file-save event, want it visible immediately | `POST /v1/codesync/ingest-event` |
| a batch of files from one scan | `POST /v1/codesync/bulk-ingest` |
| a whole repository, first extraction | `POST /v1/codesync/ingest-async`, then poll `ingest-status` |
| commits landing on a branch you do not watch locally | the GitHub receiver, after `bootstrap` |

The first three are alternatives, not stages: nothing requires you to call one before another. The
only ordering that is real is `connector` and `bootstrap` before the webhook can resolve a push.

## 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.
