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

# publish-user-connector

[Can be held for approval](/docs/sdks-and-apis/how-every-tool-call-works#lifecycle-and-governance)

Publish a user-defined connector so others can use it, or take it back to a private draft. Needs the `fabric:skill.write` grant.

**Publish a connector to the project**

**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":"publish-user-connector","arguments":{"connector_id":"6b1a3d5e7f9c0b2a4d6e8f02","scope":"project"}}}'
```

```json
{
  "content": [
    {
      "type": "text",
      "text": "{\"lifecycle_id\":\"qlc_000017\",\"result\":{\"connector_id\":\"6b1a3d5e7f9c0b2a4d6e8f02\",\"name\":\"Payroll\",\"category\":\"user_payroll\",\"mode\":\"live\",\"base_url\":\"https://payroll.example.com/v1\",\"lifecycle\":\"published\",\"access_scope\":\"project\",\"has_credentials\":true,\"connected\":true,\"actions\":[{\"key\":\"get_employee\",\"name\":\"Get employee\"}]}}"
    }
  ],
  "isError": false
}
```

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/publish-user-connector \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{"connector_id":"6b1a3d5e7f9c0b2a4d6e8f02","scope":"project"}'
```

```json
{
  "lifecycle_id": "qlc_000017",
  "result": {
    "connector_id": "6b1a3d5e7f9c0b2a4d6e8f02",
    "name": "Payroll",
    "category": "user_payroll",
    "mode": "live",
    "base_url": "https://payroll.example.com/v1",
    "lifecycle": "published",
    "access_scope": "project",
    "has_credentials": true,
    "connected": true,
    "actions": [
      {
        "key": "get_employee",
        "name": "Get employee"
      }
    ]
  }
}
```

**TypeScript SDK**

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

const fabric = new Fabric()
const result = await fabric.publishUserConnector({
  connector_id: '6b1a3d5e7f9c0b2a4d6e8f02',
  scope: 'project'
})
```

```ts
// result is the payload itself, with no `.result` to read
{
  connector_id: '6b1a3d5e7f9c0b2a4d6e8f02',
  name: 'Payroll',
  category: 'user_payroll',
  mode: 'live',
  base_url: 'https://payroll.example.com/v1',
  lifecycle: 'published',
  access_scope: 'project',
  has_credentials: true,
  connected: true,
  actions: [
    {
      key: 'get_employee',
      name: 'Get employee'
    }
  ]
}
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.publish_user_connector(
    connector_id="6b1a3d5e7f9c0b2a4d6e8f02",
    scope="project",
)
```

```python
# out is the result, already unwrapped
{
    "connector_id": "6b1a3d5e7f9c0b2a4d6e8f02",
    "name": "Payroll",
    "category": "user_payroll",
    "mode": "live",
    "base_url": "https://payroll.example.com/v1",
    "lifecycle": "published",
    "access_scope": "project",
    "has_credentials": True,
    "connected": True,
    "actions": [
        {
            "key": "get_employee",
            "name": "Get employee"
        }
    ]
}
```

## Parameters

| Parameter | Type | Required | Notes |
| --- | --- | --- | --- |
| `connector_id` | string | Required | The user-defined connector, from `list-user-connectors`. An unknown id is refused (404). A missing id, or a connector you may not edit, is refused (502); the message names which. |
| `scope` | string | Required | One of `project` (everyone in this project can use it), `organization` (every project of the organization; takes an organization admin; anyone else is refused) or `individual` (back to a draft only its author uses). |

**Show full response**

An example response, after publishing a draft to the project.

**Result of publish-user-connector**

**REST API**

```json
{
  "lifecycle_id": "qlc_000017",
  "result": {
    "connector_id": "6b1a3d5e7f9c0b2a4d6e8f02",
    "name": "Payroll",
    "category": "user_payroll",
    "description": "Read employees from the payroll API.",
    "logo": null,
    "mode": "live",
    "base_url": "https://payroll.example.com/v1",
    "auth": {
      "type": "api_key",
      "name": "X-API-Key",
      "location": "header"
    },
    "headers": {},
    "connected": true,
    "lifecycle": "published",
    "access_scope": "project",
    "has_credentials": true,
    "user_id": "6a0f1e2d3c4b5a6978877665",
    "project_id": "6a0f1e2d3c4b5a6978877601",
    "org_id": "6a0f1e2d3c4b5a6978877600",
    "actions": [
      {
        "key": "get_employee",
        "name": "Get employee",
        "description": "One employee by id.",
        "input_schema": {
          "fields": [
            {
              "name": "employee_id",
              "type": "string",
              "required": true
            }
          ]
        },
        "output_schema": {
          "fields": [
            {
              "name": "id",
              "type": "string"
            },
            {
              "name": "name",
              "type": "string"
            }
          ]
        },
        "fault": null,
        "request": {
          "method": "GET",
          "path": "/employees/{employee_id}",
          "query": [],
          "headers": {},
          "body": "none",
          "result_path": null
        }
      }
    ]
  }
}
```

**Wexa MCP server**

```json
{
  "content": [
    {
      "type": "text",
      "text": "{\"lifecycle_id\":\"qlc_000017\",\"result\":{\"connector_id\":\"6b1a3d5e7f9c0b2a4d6e8f02\",\"name\":\"Payroll\",\"category\":\"user_payroll\",\"description\":\"Read employees from the payroll API.\",\"logo\":null,\"mode\":\"live\",\"base_url\":\"https://payroll.example.com/v1\",\"auth\":{\"type\":\"api_key\",\"name\":\"X-API-Key\",\"location\":\"header\"},\"headers\":{},\"connected\":true,\"lifecycle\":\"published\",\"access_scope\":\"project\",\"has_credentials\":true,\"user_id\":\"6a0f1e2d3c4b5a6978877665\",\"project_id\":\"6a0f1e2d3c4b5a6978877601\",\"org_id\":\"6a0f1e2d3c4b5a6978877600\",\"actions\":[{\"key\":\"get_employee\",\"name\":\"Get employee\",\"description\":\"One employee by id.\",\"input_schema\":{\"fields\":[{\"name\":\"employee_id\",\"type\":\"string\",\"required\":true}]},\"output_schema\":{\"fields\":[{\"name\":\"id\",\"type\":\"string\"},{\"name\":\"name\",\"type\":\"string\"}]},\"fault\":null,\"request\":{\"method\":\"GET\",\"path\":\"/employees/{employee_id}\",\"query\":[],\"headers\":{},\"body\":\"none\",\"result_path\":null}}]}}"
    }
  ],
  "isError": false
}
```

**TypeScript SDK**

```ts
// the object you get back IS the result — there is no `.result` to read
{
  "connector_id": "6b1a3d5e7f9c0b2a4d6e8f02",
  "name": "Payroll",
  "category": "user_payroll",
  "description": "Read employees from the payroll API.",
  "logo": null,
  "mode": "live",
  "base_url": "https://payroll.example.com/v1",
  "auth": {
    "type": "api_key",
    "name": "X-API-Key",
    "location": "header"
  },
  "headers": {},
  "connected": true,
  "lifecycle": "published",
  "access_scope": "project",
  "has_credentials": true,
  "user_id": "6a0f1e2d3c4b5a6978877665",
  "project_id": "6a0f1e2d3c4b5a6978877601",
  "org_id": "6a0f1e2d3c4b5a6978877600",
  "actions": [
    {
      "key": "get_employee",
      "name": "Get employee",
      "description": "One employee by id.",
      "input_schema": {
        "fields": [
          {
            "name": "employee_id",
            "type": "string",
            "required": true
          }
        ]
      },
      "output_schema": {
        "fields": [
          {
            "name": "id",
            "type": "string"
          },
          {
            "name": "name",
            "type": "string"
          }
        ]
      },
      "fault": null,
      "request": {
        "method": "GET",
        "path": "/employees/{employee_id}",
        "query": [],
        "headers": {},
        "body": "none",
        "result_path": null
      }
    }
  ]
}

// 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_000017'
```

**Python SDK**

```python
# a dict subclass; `result` is already unwrapped
{
 "connector_id": "6b1a3d5e7f9c0b2a4d6e8f02",
 "name": "Payroll",
 "category": "user_payroll",
 "description": "Read employees from the payroll API.",
 "logo": None,
 "mode": "live",
 "base_url": "https://payroll.example.com/v1",
 "auth": {
  "type": "api_key",
  "name": "X-API-Key",
  "location": "header"
 },
 "headers": {},
 "connected": True,
 "lifecycle": "published",
 "access_scope": "project",
 "has_credentials": True,
 "user_id": "6a0f1e2d3c4b5a6978877665",
 "project_id": "6a0f1e2d3c4b5a6978877601",
 "org_id": "6a0f1e2d3c4b5a6978877600",
 "actions": [
  {
   "key": "get_employee",
   "name": "Get employee",
   "description": "One employee by id.",
   "input_schema": {
    "fields": [
     {
      "name": "employee_id",
      "type": "string",
      "required": True
     }
    ]
   },
   "output_schema": {
    "fields": [
     {
      "name": "id",
      "type": "string"
     },
     {
      "name": "name",
      "type": "string"
     }
    ]
   },
   "fault": None,
   "request": {
    "method": "GET",
    "path": "/employees/{employee_id}",
    "query": [],
    "headers": {},
    "body": "none",
    "result_path": None
   }
  }
 ]
}

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

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

**More details**

**Response fields**

The connector as it now stands, the same as [`get-user-connector`](/docs/connectors/get-user-connector) returns it:

- `connector_id`: The connector's id. Pass it to the other user-defined connector tools.
- `name`: Its name.
- `category`: Its category. A user-defined connector's category starts with `user_`.
- `description`: Its description; empty when it has none.
- `logo`: Its logo, or null.
- `mode`: `live` (calls the real API) or `simulated`.
- `base_url`: A live connector's base URL; every action's request path is appended to it.
- `auth`: How requests authenticate: `type` is `none`, `api_key`, `bearer` or `basic`; for `api_key`, `name` is the header or query parameter and `location` is `header` or `query`.
- `headers`: Static headers sent on every request.
- `connected`: Whether its actions are skills agents can use. Publishing connects it.
- `lifecycle`: `draft` or `published`.
- `access_scope`: `individual` (a draft, its author's alone), `project` or `organization`.
- `has_credentials`: Whether credentials are saved. The credentials themselves are never returned.
- `user_id`: Its author.
- `project_id`: The project it belongs to.
- `org_id`: The organization it belongs to.
- `actions`: Its actions. Each has a `key`, a `name`, a `description`, an `input_schema`, an `output_schema`, a `fault` and, for a live connector, the `request` it makes (`method`, `path`, `query`, `headers`, `body`, `result_path`).

Publishing generates the connector's skills, so agents, process flows and chat can run its actions.

You may publish a connector you may edit: your own, or, for one already published in this project, any of them if you are a project admin. Publishing to the organization also takes an organization admin (an owner or an org admin). That check runs before policy, so it refuses before anything is held. Whether you may edit the connector is checked when the call is executed, after any approval.

The connector id in the example is illustrative. Swap it for one `list-user-connectors` returned to you.

Marked consequential: it is audited in full, and a policy rule may hold it for a person to approve. The project's write gate, when a project admin has turned it on (it is off by default), also holds it when an API key or SDK session calls it.

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