> Documentation index: [/llms.txt](/llms.txt). Every page as one file: [/llms-full.txt](/llms-full.txt).

# Create and query connections

Create and query connections [#create-and-query-connections]

Create a connection to a database or app, query it over the API, and attach it to an agent.

<ConnectorArchitecture />

Endpoints [#endpoints]

| Method                     | URL                                                                                   | Does                                                     | Auth          |
| -------------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------- |
| `GET`                      | `/api/v1/data-sources/connectors/`                                                    | List connectors; optional `category`                     | Key or bearer |
| `GET`                      | `/api/v1/data-sources/connectors/{connector_key}/`                                    | One connector, with `credential_details.auth_strategies` | Key or bearer |
| `GET`                      | `/api/v1/data-sources/connectors/{connector_key}/actions/`                            | Actions and their `params`                               | Key or bearer |
| `GET`                      | `/api/v1/data-sources/connectors/{connector_key}/credential-fields/?auth_type=`       | Credential fields for one `auth_type` (required)         | Key or bearer |
| `POST`                     | `/api/v1/data-sources/connectors/{connector_key}/test/`                               | Test `{auth_type, credentials}` without saving; `204`    | Key or bearer |
| `POST`                     | `/api/v1/data-sources/connectors/{connector_key}/auth/init/`                          | Start OAuth; returns `auth_url` and `state`              | Bearer only   |
| `POST`                     | `/api/v1/data-sources/connectors/{connector_key}/auth/callback/`                      | Exchange `code` and `state` for credentials              | Bearer only   |
| `POST` · `GET`             | `/api/v1/data-sources/connections/`                                                   | Create · list connections                                | Key or bearer |
| `GET` · `PATCH` · `DELETE` | `/api/v1/data-sources/connections/{connection_id}/`                                   | Read · edit · delete one                                 | Key or bearer |
| `POST`                     | `/api/v1/data-sources/connections/{connection_id}/test/`                              | Test the stored credentials; `204`                       | Key or bearer |
| `GET`                      | `/api/v1/data-sources/connections/{connection_id}/auth-status/`                       | Whether you have connected your own account              | Key or bearer |
| `POST`                     | `/api/v1/data-sources/connections/{connection_id}/authenticate/init/` · `…/callback/` | Connect your own account to a shared OAuth connection    | Key or bearer |
| `GET`                      | `/api/v1/data-sources/connections/{connection_id}/instructions/`                      | Query format and `actions`                               | Key or bearer |
| `POST`                     | `/api/v1/data-sources/connections/{connection_id}/query`                              | Run a query                                              | Key or bearer |
| `POST`                     | `/api/v1/data-sources/connections/{connection_id}/metadata`                           | Tables, columns and keys                                 | Key or bearer |

* Every request sends `X-Space-Slug`. A bearer-only route answers an API key with `401 {"detail": "Not authenticated"}`.
* Creating an OAuth connection needs a signed-in user for `auth/init` and `auth/callback`; every other route here takes an API key or a bearer token.
* `credential-fields` takes an `auth_type` from the connector's `credential_details.auth_strategies[].type`; another value returns `400 Invalid auth type '<value>'. Must be one of: …`.

Supported connectors [#supported-connectors]

| `connector_key`   | Name            | `category`    | `connector_type` | `auth_type`                            |
| ----------------- | --------------- | ------------- | ---------------- | -------------------------------------- |
| `apify`           | Apify           | `app`         | `null`           | `token`                                |
| `apollo`          | Apollo          | `app`         | `null`           | `token`                                |
| `attio`           | Attio           | `data_source` | `database`       | `oauth2`, `token`                      |
| `bigquery`        | Google BigQuery | `data_source` | `database`       | `oauth2`, `key-pair`                   |
| `confluence`      | Confluence      | `data_source` | `folder`         | `oauth2`                               |
| `coupa`           | Coupa           | `app`         | `null`           | `token`                                |
| `google_calendar` | Google Calendar | `app`         | `app`            | `oauth2`                               |
| `google_drive`    | Google Drive    | `data_source` | `folder`         | `oauth2`, `key-pair`                   |
| `google_sheets`   | Google Sheets   | `data_source` | `file`           | `oauth2`, `key-pair`                   |
| `hubspot`         | HubSpot         | `data_source` | `database`       | `oauth2`                               |
| `ironclad`        | Ironclad        | `app`         | `null`           | `token`                                |
| `jira`            | Jira            | `app`         | `null`           | `oauth2`, `token`                      |
| `nebuly`          | Nebuly          | `data_source` | `app`            | `token`                                |
| `onetrust`        | OneTrust        | `app`         | `null`           | `client_credentials`, `token`          |
| `postgresql`      | PostgreSQL      | `data_source` | `database`       | `basic`                                |
| `salesforce`      | Salesforce      | `data_source` | `database`       | `oauth2`                               |
| `sharepoint`      | SharePoint      | `data_source` | `folder`         | `oauth2`                               |
| `slack`           | Slack           | `app`         | `null`           | `oauth2`                               |
| `snowflake`       | Snowflake       | `data_source` | `database`       | `basic`, `token`, `key-pair`, `oauth2` |
| `tavily`          | Tavily          | `app`         | `null`           | `token`                                |

The connector list returns `category`; a connection gets `connector_type` from its connector.

Credentials [#credentials]

Send every field as a string. Optional fields are in parentheses.

| `connector_key`                  | `auth_type`                    | `credentials`                                                                                                                                     |
| -------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `postgresql`                     | `basic`                        | `host`, `port`, `dbname`, `user`, `password`                                                                                                      |
| `snowflake`                      | every type                     | `account` (an account identifier such as `xy12345` or `xy12345.us-east-1`, not a hostname), `user`, `database`, `warehouse`, (`schema`), (`role`) |
| `snowflake`                      | `basic` · `token` · `key-pair` | plus `password` · `token` (programmatic access token) · `private_key` (PEM) and (`private_key_file_pwd`)                                          |
| `bigquery`                       | `key-pair`                     | `project_id`, `service_account_key` (the whole JSON key as a string), (`dataset`), (`location`), (`subject`)                                      |
| `google_drive` · `google_sheets` | `key-pair`                     | `service_account_key`, (`subject`), (`file_ids` · `spreadsheet_ids`)                                                                              |
| `tavily` · `apollo` · `attio`    | `token`                        | `api_key`                                                                                                                                         |
| `apify`                          | `token`                        | `api_token`                                                                                                                                       |
| `jira`                           | `token`                        | `domain` (e.g. `mycompany.atlassian.net`), `email`, `api_token`                                                                                   |
| `nebuly`                         | `token`                        | `api_key`, (`base_url`)                                                                                                                           |
| `coupa`                          | `token`                        | `base_url`, `client_id`, `client_secret`                                                                                                          |
| `ironclad`                       | `token`                        | `base_url`, `client_id`, `client_secret`, `actor_email`, `scopes`                                                                                 |
| `onetrust`                       | `client_credentials` · `token` | `base_url`, plus `client_id` and `client_secret` · `access_token`                                                                                 |
| any                              | `oauth2`                       | The object `auth/callback/` returns, plus any required field it lacks: Snowflake needs `user`                                                     |

Create request fields [#create-request-fields]

| Field              | Type                                          | Notes                                                                                    |
| ------------------ | --------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `name`             | string, required                              |                                                                                          |
| `connector_key`    | string, required                              | From [Supported connectors](#supported-connectors)                                       |
| `auth_type`        | string, required                              | One of the connector's `auth_type` values                                                |
| `credentials`      | object, required                              | See [Credentials](#credentials)                                                          |
| `category`         | `data_source` or `app`, default `data_source` | Stored as sent, and the `category` list filter reads it: send the connector's `category` |
| `scope_visibility` | `private` or `space`, default `private`       | `private`: only you. `space`: every workspace member                                     |

Query body and response [#query-body-and-response]

| Connectors                           | `query`                                                 | Response                                                                     |
| ------------------------------------ | ------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `postgresql`                         | `{"sql": "SELECT …"}`, optional `params`                | `{columns, rows, row_count, summary}`; each row is an object keyed by column |
| `snowflake`                          | `{"sql": "SELECT …"}`, optional `params`                | `{success, data, columns}`; each row in `data` is an array                   |
| `bigquery`                           | `{"sql": "SELECT …"}`, optional `params` and `max_rows` | `{success, columns, data, row_count, total_rows, truncated}`                 |
| Every other connector with `actions` | `{"operation": "<action key>", …params}`                | `{success, data}`; `data` holds the action's output                          |

* SQL must be read-only: it starts with `SELECT`, `WITH`, `SHOW`, `DESCRIBE` or `EXPLAIN` and contains no write keyword.
* When the connection's [semantic layer](../semantic-layer/overview) lists tables, only tables with `is_included: true` can be queried.
* `timeout_seconds` is optional: default `30`, maximum `300`.

Create a connection [#create-a-connection]

1. Pick `connector_key` and `auth_type` from [Supported connectors](#supported-connectors) and fill `credentials` from [Credentials](#credentials).

2. Create it. Central Hub validates the fields, tests the connection and stores the credentials encrypted:

   ```bash
   curl -X POST "$CENTRAL_HUB_URL/api/v1/data-sources/connections/" \
     -H "X-API-Key: $ALO_API_KEY" \
     -H "X-Space-Slug: $WORKSPACE_SLUG" \
     -H "Content-Type: application/json" \
     -d '{
       "name": "Analytics DB",
       "connector_key": "postgresql",
       "auth_type": "basic",
       "credentials": {
         "host": "db.example.com",
         "port": "5432",
         "dbname": "analytics",
         "user": "readonly_user",
         "password": "…"
       }
     }'
   ```

3. Keep the `id` from the `201` response (abridged). Sensitive fields come back as `[REDACTED]`:

   ```json
   {
     "id": 123,
     "name": "Analytics DB",
     "connector_key": "postgresql",
     "auth_type": "basic",
     "credentials": {"host": "db.example.com", "port": "5432", "dbname": "analytics", "user": "readonly_user", "password": "[REDACTED]"},
     "category": "data_source",
     "connector_type": "database",
     "scope_visibility": "private",
     "is_owner": true,
     "semantic_layer": {"tables": [], "relationships": [], "definitions": [], "kpis": [], "status": "generating", "generated_at": "2026-10-13T19:34:51.683117+00:00"}
   }
   ```

   A `database` connection with category `data_source` generates its [semantic layer](../semantic-layer/overview) in the background.

Create an OAuth connection [#create-an-oauth-connection]

Bearer only: run these steps as the signed-in user who will own the connection.

1. Start the flow with your `redirect_uri`. Put connector config in `credentials` when the connector needs it, such as Snowflake `account`, `database` and `warehouse`, or your own OAuth app's `client_id` and `client_secret`:

   ```bash
   curl -X POST "$CENTRAL_HUB_URL/api/v1/data-sources/connectors/google_drive/auth/init/" \
     -H "Authorization: Bearer $ALO_TOKEN" \
     -H "X-Space-Slug: $WORKSPACE_SLUG" \
     -H "Content-Type: application/json" \
     -d '{"redirect_uri": "https://your-app.example.com/oauth/callback"}'
   ```

2. Keep both values of the response, `{"auth_url": "…", "state": "…"}`, and send the user to `auth_url`.

3. When the provider redirects back, exchange its `code` with the `state` from step 2, within 15 minutes and as the same user:

   ```bash
   curl -X POST "$CENTRAL_HUB_URL/api/v1/data-sources/connectors/google_drive/auth/callback/" \
     -H "Authorization: Bearer $ALO_TOKEN" \
     -H "X-Space-Slug: $WORKSPACE_SLUG" \
     -H "Content-Type: application/json" \
     -d '{"code": "<code from the redirect>", "state": "<state from init>"}'
   ```

4. [Create the connection](#create-a-connection) with `"auth_type": "oauth2"` and the returned object as `credentials`.

Other members of a `space` OAuth connection [connect their own account](#connect-your-account-to-a-shared-oauth-connection).

List connections [#list-connections]

1. Filter with any of `category` (`data_source`, `app`), `connector_type` (`database`, `file`, `folder`, `app`) and `connector_key`. With `connector_key`, `action_key` keeps only connections whose granted OAuth scopes cover that action:

   ```bash
   curl "$CENTRAL_HUB_URL/api/v1/data-sources/connections/?category=data_source&connector_type=database" \
     -H "X-API-Key: $ALO_API_KEY" \
     -H "X-Space-Slug: $WORKSPACE_SLUG"
   ```

2. Read the array: your private connections first, then the workspace's, each sorted by name.

Read, edit or delete a connection [#read-edit-or-delete-a-connection]

| Call                                    | Behaviour                                                                                                                                         |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET …/connections/{connection_id}/`    | Sensitive fields are `[REDACTED]`; add `?mask_credentials=false` for decrypted values. A platform key always gets decrypted values                |
| `PATCH …/connections/{connection_id}/`  | Send only `name`, `auth_type`, `credentials` or `scope_visibility`; `credentials` merge into the stored ones, then are validated and tested again |
| `DELETE …/connections/{connection_id}/` | `204`; also removes the ID from `enabled_connection_ids` of the workspace's agents                                                                |

* Only the connection's owner can edit or delete it.
* `scope_visibility: "space"` needs a second workspace member.

Run a query [#run-a-query]

1. Read the connection's query format and `actions`; `instructions_prompt` is guidance for the query, not a request body:

   ```bash
   curl "$CENTRAL_HUB_URL/api/v1/data-sources/connections/123/instructions/" \
     -H "X-API-Key: $ALO_API_KEY" \
     -H "X-Space-Slug: $WORKSPACE_SLUG"
   ```

2. Send the query, with no trailing slash:

   ```bash
   curl -X POST "$CENTRAL_HUB_URL/api/v1/data-sources/connections/123/query" \
     -H "X-API-Key: $ALO_API_KEY" \
     -H "X-Space-Slug: $WORKSPACE_SLUG" \
     -H "Content-Type: application/json" \
     -d '{"query": {"sql": "SELECT id, cust_id, amount FROM tbl_ord ORDER BY id LIMIT 2"}, "timeout_seconds": 30}'
   ```

   ```json
   {
     "columns": ["id", "cust_id", "amount"],
     "rows": [
       {"id": 1, "cust_id": 1, "amount": "120.50"},
       {"id": 2, "cust_id": 2, "amount": "80.00"}
     ],
     "row_count": 2,
     "summary": "Query returned 2 row(s)."
   }
   ```

3. For an action, put its key in `operation` and its params beside it. Slack `list_channels`:

   ```json
   {"query": {"operation": "list_channels", "limit": 20}}
   ```

   The response is `{"success": true, "data": {"channels": [{"id": "…", "name": "…", "is_private": false, "num_members": 12, "topic": "…", "purpose": "…"}]}}`.

Retrieve metadata [#retrieve-metadata]

1. Send `POST …/connections/{connection_id}/metadata`, with no trailing slash and an optional `timeout_seconds`:

   ```bash
   curl -X POST "$CENTRAL_HUB_URL/api/v1/data-sources/connections/123/metadata" \
     -H "X-API-Key: $ALO_API_KEY" \
     -H "X-Space-Slug: $WORKSPACE_SLUG" \
     -H "Content-Type: application/json" \
     -d '{"timeout_seconds": 30}'
   ```

2. Read `tables[]`: each has `name` (schema-qualified for PostgreSQL, e.g. `public.tbl_ord`), `columns[]` with `name`, `data_type` and `nullable`, `primary_key` and `foreign_keys`. `unavailable` lists configured items your credentials could not read.

Connect your account to a shared OAuth connection [#connect-your-account-to-a-shared-oauth-connection]

A `space` OAuth connection uses each member's own tokens. Until you connect, `/query` and `/metadata` return `428`:

```json
{
  "detail": "Per-user authentication required for connection 42 (snowflake): not_connected",
  "code": "PER_USER_AUTH_REQUIRED",
  "reason": "not_connected",
  "connector_key": "snowflake",
  "connection_id": 42
}
```

`reason` is `not_connected` or `needs_reauth` (your token expired). `GET …/connections/{connection_id}/auth-status/` reports it as `authenticated: false`.

1. Start the flow; it needs the Create connections permission (`connection:create`):

   ```bash
   curl -X POST "$CENTRAL_HUB_URL/api/v1/data-sources/connections/42/authenticate/init/" \
     -H "X-API-Key: $ALO_API_KEY" \
     -H "X-Space-Slug: $WORKSPACE_SLUG" \
     -H "Content-Type: application/json" \
     -d '{"redirect_uri": "https://your-app.example.com/oauth/callback"}'
   ```

2. Keep `state` and send the user to `auth_url`.

3. Within 15 minutes and as the same user, send `POST …/connections/42/authenticate/callback/` with `{"code": "…", "state": "…"}`. The response is the connection with `user_authenticated: true`.

Attach a connection to an agent [#attach-a-connection-to-an-agent]

Add the connection ID to the agent's `enabled_connection_ids`: send `PATCH /api/v1/agents-v2/agents/{agent_id}/` with `{"enabled_connection_ids": [123]}`, or set it in the bundle's `agent.yaml` and upload the bundle, as in [Create and configure an agent](../../configurable-agents/create-and-configure#publish-an-agent-bundle):

```yaml
enabled_connection_ids: [123]
```

Troubleshooting [#troubleshooting]

| Response                                                                       | Cause                                                                                                                                                                                    | Fix                                                                        |
| ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `422` `{"detail": [{"field": "password", "message": "Password is required"}]}` | A required credential is missing                                                                                                                                                         | Add it; see [Credentials](#credentials)                                    |
| `422` `{"detail": [{"field": "port", "message": "Port must be text"}]}`        | A credential was sent as a number                                                                                                                                                        | Send it as a string                                                        |
| `422` `Invalid auth type 'oauth2'. Must be one of: basic`                      | The connector does not offer that `auth_type`                                                                                                                                            | Use a value from [Supported connectors](#supported-connectors)             |
| `400` `Connection test failed with provided credentials`                       | The host or credentials were rejected                                                                                                                                                    | Check them; nothing was saved                                              |
| `409` `Workspace sharing is available after a second member joins`             | `scope_visibility: "space"` in a one-member workspace                                                                                                                                    | Keep `private` or invite a member                                          |
| `403` `You do not have permission to access this connection.`                  | Someone other than the owner edited or deleted it                                                                                                                                        | Ask the owner                                                              |
| `422` `Query type 'DELETE' is not allowed. Only read-only queries …`           | The SQL writes or changes schema                                                                                                                                                         | Send a read-only query                                                     |
| `422` `Access to table(s) 'PUBLIC.CUSTOMERS' is not allowed. …`                | The table is excluded in the semantic layer                                                                                                                                              | Set `is_included: true` on it                                              |
| `422` `Query type 'RENEWAL' is not recognized as a safe read-only operation.`  | `/query` checks a string in `query.sql`, `query.query`, `query.code` or `query.statement` as SQL, so actions with a `query` param (Slack `search_messages`, Tavily `search`) are refused | Use an action without a `query` param                                      |
| `400` `Query execution failed: …`                                              | The database rejected the query                                                                                                                                                          | Fix the SQL; the message is the database's                                 |
| `428` `PER_USER_AUTH_REQUIRED`                                                 | You have not connected your account to this shared OAuth connection                                                                                                                      | [Connect your account](#connect-your-account-to-a-shared-oauth-connection) |
| `400` `Invalid or expired OAuth state`                                         | `state` is wrong, already used, or belongs to another user                                                                                                                               | Restart from `init`                                                        |
| `307` on `/query` or `/metadata`                                               | The URL has a trailing slash                                                                                                                                                             | Remove it                                                                  |
