> Source: https://wexa.ai/docs/connectors/sync-connector

# sync-connector

Sync one connector, the same as **Sync this connector** on its card in the console: its actions and ontology are refreshed, then ingestion starts when the connector has data to pull. It answers at once with a `sync_id`. Needs the `fabric:connector.sync` grant.

**Sync a connector**

**Wexa MCP server**

```bash
curl -sS -X POST https://fabric.wexa.ai/mcp/$FABRIC_PROJECT_ID \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -H "content-type: application/json" -H "accept: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"sync-connector","arguments":{"connector_id":"6ab3608bf19beb4f0ed39e77"}}}'
```

```json
{
  "content": [
    {
      "type": "text",
      "text": "{\"lifecycle_id\":\"qlc_000002\",\"result\":{\"sync_id\":\"connector-sync-6aa9beb7ec8121a43b6c791b-6ab3608bf19beb4f0ed39e77-1790280549835063253\",\"connector_id\":\"6ab3608bf19beb4f0ed39e77\",\"joined\":false,\"status\":\"running\"}}"
    }
  ],
  "isError": false
}
```

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/sync-connector \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{"connector_id":"6ab3608bf19beb4f0ed39e77"}'
```

```json
{
  "lifecycle_id": "qlc_000002",
  "result": {
    "sync_id": "connector-sync-6aa9beb7ec8121a43b6c791b-6ab3608bf19beb4f0ed39e77-1790280549835063253",
    "connector_id": "6ab3608bf19beb4f0ed39e77",
    "joined": false,
    "status": "running"
  }
}
```

**TypeScript SDK**

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
const result = await fabric.syncConnector({
  connector_id: '6ab3608bf19beb4f0ed39e77'
})
```

```ts
// result is the payload itself, with no `.result` to read
{
  sync_id: 'connector-sync-6aa9beb7ec8121a43b6c791b-6ab3608bf19beb4f0ed39e77-1790280549835063253',
  connector_id: '6ab3608bf19beb4f0ed39e77',
  joined: false,
  status: 'running'
}
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.sync_connector(connector_id="6ab3608bf19beb4f0ed39e77")
```

```python
# out is the result, already unwrapped
{
    "sync_id": "connector-sync-6aa9beb7ec8121a43b6c791b-6ab3608bf19beb4f0ed39e77-1790280549835063253",
    "connector_id": "6ab3608bf19beb4f0ed39e77",
    "joined": False,
    "status": "running"
}
```

## Parameters

| Parameter | Type | Required | Notes |
| --- | --- | --- | --- |
| `connector_id` | string | Required | The connector, from [`list-connectors`](/docs/connectors/list-connectors). It must be shown to you there and provisioned in your project. Must be a string. One you cannot sync (unknown, in another organization, provisioned in another project, or outside your access scope) is refused (403). A connector missing from the platform's connector list for this project cannot be synced and is refused (422, `S4:validate-input`). |

**Show full response**

- `sync_id`: the sync to pass to [`get-connector-sync`](/docs/connectors/get-connector-sync).
- `connector_id`: the connector you named.
- `joined`: `true` when a running sync was joined, `false` when this call started one.
- `status`: always `running`: the sync has just started, or is still running.

A real response, captured from a live call to `sync-connector` that started a sync of a Linear connector.
Nothing is changed.

**Result of sync-connector**

**REST API**

```json
{
  "lifecycle_id": "qlc_000002",
  "result": {
    "sync_id": "connector-sync-6aa9beb7ec8121a43b6c791b-6ab3608bf19beb4f0ed39e77-1790280549835063253",
    "connector_id": "6ab3608bf19beb4f0ed39e77",
    "joined": false,
    "status": "running"
  }
}
```

**Wexa MCP server**

```json
{
  "content": [
    {
      "type": "text",
      "text": "{\"lifecycle_id\":\"qlc_000002\",\"result\":{\"sync_id\":\"connector-sync-6aa9beb7ec8121a43b6c791b-6ab3608bf19beb4f0ed39e77-1790280549835063253\",\"connector_id\":\"6ab3608bf19beb4f0ed39e77\",\"joined\":false,\"status\":\"running\"}}"
    }
  ],
  "isError": false
}
```

**TypeScript SDK**

```ts
// the object you get back IS the result — there is no `.result` to read
{
  "sync_id": "connector-sync-6aa9beb7ec8121a43b6c791b-6ab3608bf19beb4f0ed39e77-1790280549835063253",
  "connector_id": "6ab3608bf19beb4f0ed39e77",
  "joined": false,
  "status": "running"
}

// The TS SDK names these camelCase — lifecycleId, traceId — where Python uses
// lifecycle_id and trace_id. Both are non-enumerable: readable, but omitted by
// Object.keys() and JSON.stringify(). `result.lifecycleId` here is undefined.
result.lifecycleId // 'qlc_000002'
```

**Python SDK**

```python
# a dict subclass; `result` is already unwrapped
{
 "sync_id": "connector-sync-6aa9beb7ec8121a43b6c791b-6ab3608bf19beb4f0ed39e77-1790280549835063253",
 "connector_id": "6ab3608bf19beb4f0ed39e77",
 "joined": False,
 "status": "running"
}

out.lifecycle_id  # 'qlc_000002' — an attribute, not a key
```

**More details**

A sync can run for minutes, so the call never waits for it. Pass the `sync_id` to `get-connector-sync` to follow it, or let an SDK wait for you. See [ingestion](/docs/context/ingestion) for following the result.

Both SDKs take three options that are not sent to the platform:

- `wait` (Python and TypeScript, default off): poll `get-connector-sync` until the sync is no longer `running`.
- `wait_timeout` in Python, `waitTimeout` in TypeScript (default `600`): the most seconds to wait.
- `poll` (default `5`): seconds between two polls.

With `wait`, both SDKs start or join the sync, then poll until its `status` is no longer `running`, and return that last status with `joined` added. A failed sync comes back as a status with `status: "failed"`; it is not raised. `wait` waits only for the refresh of the connector's actions and ontology. It never waits for ingestion to finish, because a native connector such as Linear or Slack cannot report when its data is all in: read the `ingestion` field of the status for what is known. When the time limit is reached, the SDK stops polling and returns the last status, which still says `status: "running"`. Nothing is raised, and the sync goes on.

```python
sync = fabric.sync_connector(connector_id="6ab3608bf19beb4f0ed39e77", wait=True)
```

In the console, the Connectors page has two buttons for refreshing. **Sync to Context** at the top syncs all of the project's connectors. **Sync this connector**, in the menu on one connector's card, does what this tool does. **Sync frequency** in the same menu sets how often that connector is polled after its first full sync, within the range the dialog shows.

You can sync a connector when `list-connectors` shows it to you, which the access scope chosen at provisioning decides (the same rule that decides which actions you can run), and it was provisioned in your project. A connector provisioned in another project is refused, even when its access scope lets you use its actions.

By access scope, it is listed for you when:

- `organization`: your project is in the connector's organization.
- `department`: your project is in the connector's department.
- `project`: your project is the project the connector was provisioned in.
- `individual`: you are the person who provisioned it.

A connector in another organization is always refused. Every refusal is the same `S8:connector-not-usable` error, so the answer does not tell you whether a connector you cannot use exists.

When a sync of the connector is still running, the call does not start a second one. It answers with the id of the running sync and `joined: true`, so the load on the connected system never doubles. A finished sync is not joined: the next call starts a new one. Two calls in the same instant can both start a sync, because neither sees the other yet. The console's own Sync button always starts a new sync.

Every call writes a `sync-connector.run` audit event, whether it started a sync, joined one or was refused: who called, the connector, its access scope and department, your department, the `sync_id`, and the outcome (`started`, `joined`, `refused` or `failed`). A call refused because the credential lacks the grant is recorded as `sync-connector.rejected`.

Not marked consequential, so it does not wait for approval: a sync only reads from the connected system, and scheduled automation must be able to run it. A sync reads the connected system with the connector's own credential and can pull all of its data into your project's context graph. That is why it needs its own grant, and why a key that may run actions or read data does not gain it. Administrator keys carry it by default. An API key minted before the grant existed does not carry it, and must be reissued to use this tool.

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