On this page

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

GrantRoutes
fabric:codesync.writeall 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.

curl -sS -H "Authorization: Bearer $FABRIC_API_KEY" \
  https://fabric.wexa.ai/v1/codesync/names
{ "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:

{ "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.

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}'
FieldMeaning
repo_urlthe clone URL, matched against the webhook payload's clone_url
repo_namethe owner/name form, matched against the payload's full_name
ttl_secondshow 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.

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":[]}'
{ "ok": true, "nodes": 3, "edges": 5 }
{ "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.

{ "job_id": "job_abc_0001", "status": "queued" }
curl -sS -H "Authorization: Bearer $FABRIC_API_KEY" \
  https://fabric.wexa.ai/v1/codesync/ingest-status/job_abc_0001
{ "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.

{ "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; 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:

RequestResponse
X-GitHub-Event: ping, correct signature200 {"ok":true,"pong":true}
any event, wrong or missing signature401 {"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 bootstrapped404 {"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 haveUse
one file-save event, want it visible immediatelyPOST /v1/codesync/ingest-event
a batch of files from one scanPOST /v1/codesync/bulk-ingest
a whole repository, first extractionPOST /v1/codesync/ingest-async, then poll ingest-status
commits landing on a branch you do not watch locallythe 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

RouteResult
GET /v1/codesync/names200 with the four resolved ids
POST /v1/codesync/connector403 without the grant, 502 with it (no connector authority running)
POST /v1/codesync/bootstrap403 without the grant, 502 with it (no connector authority running)
POST /v1/codesync/ingest-event403 without the grant, 200 with it — tenant pinning observed downstream
POST /v1/codesync/bulk-ingest403 without the grant, 200 with it
POST /v1/codesync/ingest-async200, job_id returned
GET /v1/codesync/ingest-status/{job_id}200, status echoed
GET /v1/codesync/bolt-credentials403 without the grant, 200 with it
POST /v1/codesync/webhook/githuball four outcomes above, signed and unsigned

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