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

# sync-connector

Sync one connector, the same as **Sync this connector** on its card in the console. The connector's actions and
ontology are refreshed, and then ingestion starts when the connector has data to pull. Get the connector's id
from [`list-connectors`](/docs/tools/list-connectors) first.

The call answers at once with a `sync_id`. A sync can run for minutes, so the call never waits for it. Pass the
`sync_id` to [`get-connector-sync`](/docs/tools/get-connector-sync) to follow it, or let an SDK wait for you
([below](#waiting-for-the-sync-to-end)).

## Which connectors you can sync

You can sync a connector when both of these are true:

- `list-connectors` shows it to you. That is decided by the access scope chosen when the connector was
  provisioned, the same rule that decides which actions you can run.
- It was provisioned in your project. These are the connectors whose card the console shows in your project.
  A connector provisioned in another project is refused, even when its access scope lets you use its actions.

| Access scope | 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.

## A sync that is already running is joined

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.

## What it is called on each surface

The wire name is `sync-connector` everywhere: it is what the Wexa MCP server advertises, the REST path is
`POST /v1/sync-connector`, and each SDK exposes it under its own language's naming convention.

| Surface | Name |
| --- | --- |
| Wexa MCP server | `sync-connector` |
| REST API | `POST /v1/sync-connector` |
| TypeScript SDK | `fabric.syncConnector()` |
| Python SDK | `fabric.sync_connector()` |

## Arguments

| Argument | Type | Required | Notes |
| --- | --- | --- | --- |
| `connector_id` | string | Required | The connector, from list-connectors. |

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

| Python | TypeScript | Default | What it does |
| --- | --- | --- | --- |
| `wait` | `wait` | off | Poll `get-connector-sync` until the sync is no longer `running`. |
| `wait_timeout` | `waitTimeout` | `600` | The most seconds to wait. |
| `poll` | `poll` | `5` | Seconds between two polls. |

`project_id`, `projectID`, `organization_id` and `executed_by` are not arguments you pass: the
platform binds all four from the credential you authenticated with, and both SDKs refuse them
before the request leaves your process — see
[server-bound arguments](/docs/get-started/scope-and-server-bound-arguments).

## What it returns

| Field | What it is |
| --- | --- |
| `sync_id` | The sync to pass to `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. |

The three surfaces wrap this differently, and code written against one will not read another
correctly — see [return shape](/docs/tools/overview#return-shape).

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

Keeping the `lifecycle_id` is what lets you ask later why a call was allowed, refused or held.

## Waiting for the sync to end

With `wait`, both SDKs start or join the sync, then poll `get-connector-sync` 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. Poll `get-connector-sync` with its `sync_id` to
follow it further.

## Calling it

The same call on all four surfaces. Pick a tab once and every code block in the documentation follows it.

The ids in this example are real, and resolve against the project the documentation is written
against. Swap them for your own. A value in angle brackets is one only you can supply, and the
platform refuses a literal `<...>`.

**Call sync-connector**

**Wexa MCP server**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "sync-connector",
    "arguments": {
      "connector_id": "6ab3608bf19beb4f0ed39e77"
    }
  }
}
```

**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"}'
```

**TypeScript SDK**

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

const fabric = new Fabric()
const sync = await fabric.syncConnector({
  connector_id: "6ab3608bf19beb4f0ed39e77",
  wait: true
})
```

**Python SDK**

```python
from wexa import Fabric

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

## Errors

Both SDKs raise the same typed errors, chosen from the gateway's own error string first and its
HTTP status second. `sync-connector` checks the `fabric:connector.sync` grant, and reaches the seven classes every
tool reaches — listed under [the common set](/docs/tools/errors#the-common-set). These are the ones specific to it:

| Error | Status | Raised when |
| --- | --- | --- |
| `ForbiddenError` | 403 | The credential does not carry `fabric:connector.sync`. |
| `ForbiddenError` | 403 | The connector is not one you can sync (`S8:connector-not-usable`): it does not exist, it is in another organization, it was provisioned in another project, or its access scope does not include you. |
| `ValidationError` | 422 | `connector_id` is missing or is not a string. |

## Governance

Every call to `sync-connector` runs through the same ten-stage lifecycle as every other Wexa tool: the
credential is resolved to a scope, the `fabric:connector.sync` grant is checked, quota is drawn down, the arguments are
validated, policy rules are evaluated, the call is executed, the result is shaped and redacted, and an audit
record is written. The `lifecycle_id` in the response is the handle to that record.

Every call also writes a `sync-connector.run` audit event, whether it started a sync, joined one or was
refused. The event records 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`.

`sync-connector` is 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, `fabric:connector.sync`, 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.

## See also

[`get-connector-sync`](/docs/tools/get-connector-sync), [`list-connectors`](/docs/tools/list-connectors),
[the tool index](/docs/tools/overview), [errors](/docs/tools/errors), and
[which identifier goes where](/docs/tools/identifiers).
