Create and query connections
Create a connection to a database or app, query it over the API, and attach it to an agent.
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 with401 {"detail": "Not authenticated"}. - Creating an OAuth connection needs a signed-in user for
auth/initandauth/callback; every other route here takes an API key or a bearer token. credential-fieldstakes anauth_typefrom the connector'scredential_details.auth_strategies[].type; another value returns400 Invalid auth type '<value>'. Must be one of: ….
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
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
| Field | Type | Notes |
|---|---|---|
name | string, required | |
connector_key | string, required | From Supported connectors |
auth_type | string, required | One of the connector's auth_type values |
credentials | object, required | See 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
| 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,DESCRIBEorEXPLAINand contains no write keyword. - When the connection's semantic layer lists tables, only tables with
is_included: truecan be queried. timeout_secondsis optional: default30, maximum300.
Create a connection
-
Pick
connector_keyandauth_typefrom Supported connectors and fillcredentialsfrom Credentials. -
Create it. Central Hub validates the fields, tests the connection and stores the credentials encrypted:
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": "…" } }' -
Keep the
idfrom the201response (abridged). Sensitive fields come back as[REDACTED]:{ "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
databaseconnection with categorydata_sourcegenerates its semantic layer in the background.
Create an OAuth connection
Bearer only: run these steps as the signed-in user who will own the connection.
-
Start the flow with your
redirect_uri. Put connector config incredentialswhen the connector needs it, such as Snowflakeaccount,databaseandwarehouse, or your own OAuth app'sclient_idandclient_secret: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"}' -
Keep both values of the response,
{"auth_url": "…", "state": "…"}, and send the user toauth_url. -
When the provider redirects back, exchange its
codewith thestatefrom step 2, within 15 minutes and as the same user: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>"}' -
Create the connection with
"auth_type": "oauth2"and the returned object ascredentials.
Other members of a space OAuth connection connect their own account.
List connections
-
Filter with any of
category(data_source,app),connector_type(database,file,folder,app) andconnector_key. Withconnector_key,action_keykeeps only connections whose granted OAuth scopes cover that action: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" -
Read the array: your private connections first, then the workspace's, each sorted by name.
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
-
Read the connection's query format and
actions;instructions_promptis guidance for the query, not a request body:curl "$CENTRAL_HUB_URL/api/v1/data-sources/connections/123/instructions/" \ -H "X-API-Key: $ALO_API_KEY" \ -H "X-Space-Slug: $WORKSPACE_SLUG" -
Send the query, with no trailing slash:
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}'{ "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)." } -
For an action, put its key in
operationand its params beside it. Slacklist_channels:{"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
-
Send
POST …/connections/{connection_id}/metadata, with no trailing slash and an optionaltimeout_seconds: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}' -
Read
tables[]: each hasname(schema-qualified for PostgreSQL, e.g.public.tbl_ord),columns[]withname,data_typeandnullable,primary_keyandforeign_keys.unavailablelists configured items your credentials could not read.
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:
{
"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.
-
Start the flow; it needs the Create connections permission (
connection:create):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"}' -
Keep
stateand send the user toauth_url. -
Within 15 minutes and as the same user, send
POST …/connections/42/authenticate/callback/with{"code": "…", "state": "…"}. The response is the connection withuser_authenticated: true.
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:
enabled_connection_ids: [123]Troubleshooting
| Response | Cause | Fix |
|---|---|---|
422 {"detail": [{"field": "password", "message": "Password is required"}]} | A required credential is missing | Add it; see 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 |
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 |
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 |