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.
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}'
| 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.
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:
| 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.