ConnectorsConnections

Create and query connections

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

Agent
(message with connection_ids)
metadata
Central Hub
(connector service)
encrypted
PostgreSQL
Snowflake
Slack, ...

Endpoints

MethodURLDoesAuth
GET/api/v1/data-sources/connectors/List connectors; optional categoryKey or bearer
GET/api/v1/data-sources/connectors/{connector_key}/One connector, with credential_details.auth_strategiesKey or bearer
GET/api/v1/data-sources/connectors/{connector_key}/actions/Actions and their paramsKey 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; 204Key or bearer
POST/api/v1/data-sources/connectors/{connector_key}/auth/init/Start OAuth; returns auth_url and stateBearer only
POST/api/v1/data-sources/connectors/{connector_key}/auth/callback/Exchange code and state for credentialsBearer only
POST · GET/api/v1/data-sources/connections/Create · list connectionsKey or bearer
GET · PATCH · DELETE/api/v1/data-sources/connections/{connection_id}/Read · edit · delete oneKey or bearer
POST/api/v1/data-sources/connections/{connection_id}/test/Test the stored credentials; 204Key or bearer
GET/api/v1/data-sources/connections/{connection_id}/auth-status/Whether you have connected your own accountKey or bearer
POST/api/v1/data-sources/connections/{connection_id}/authenticate/init/ · …/callback/Connect your own account to a shared OAuth connectionKey or bearer
GET/api/v1/data-sources/connections/{connection_id}/instructions/Query format and actionsKey or bearer
POST/api/v1/data-sources/connections/{connection_id}/queryRun a queryKey or bearer
POST/api/v1/data-sources/connections/{connection_id}/metadataTables, columns and keysKey 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

connector_keyNamecategoryconnector_typeauth_type
apifyApifyappnulltoken
apolloApolloappnulltoken
attioAttiodata_sourcedatabaseoauth2, token
bigqueryGoogle BigQuerydata_sourcedatabaseoauth2, key-pair
confluenceConfluencedata_sourcefolderoauth2
coupaCoupaappnulltoken
google_calendarGoogle Calendarappappoauth2
google_driveGoogle Drivedata_sourcefolderoauth2, key-pair
google_sheetsGoogle Sheetsdata_sourcefileoauth2, key-pair
hubspotHubSpotdata_sourcedatabaseoauth2
ironcladIroncladappnulltoken
jiraJiraappnulloauth2, token
nebulyNebulydata_sourceapptoken
onetrustOneTrustappnullclient_credentials, token
postgresqlPostgreSQLdata_sourcedatabasebasic
salesforceSalesforcedata_sourcedatabaseoauth2
sharepointSharePointdata_sourcefolderoauth2
slackSlackappnulloauth2
snowflakeSnowflakedata_sourcedatabasebasic, token, key-pair, oauth2
tavilyTavilyappnulltoken

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_keyauth_typecredentials
postgresqlbasichost, port, dbname, user, password
snowflakeevery typeaccount (an account identifier such as xy12345 or xy12345.us-east-1, not a hostname), user, database, warehouse, (schema), (role)
snowflakebasic · token · key-pairplus password · token (programmatic access token) · private_key (PEM) and (private_key_file_pwd)
bigquerykey-pairproject_id, service_account_key (the whole JSON key as a string), (dataset), (location), (subject)
google_drive · google_sheetskey-pairservice_account_key, (subject), (file_ids · spreadsheet_ids)
tavily · apollo · attiotokenapi_key
apifytokenapi_token
jiratokendomain (e.g. mycompany.atlassian.net), email, api_token
nebulytokenapi_key, (base_url)
coupatokenbase_url, client_id, client_secret
ironcladtokenbase_url, client_id, client_secret, actor_email, scopes
onetrustclient_credentials · tokenbase_url, plus client_id and client_secret · access_token
anyoauth2The object auth/callback/ returns, plus any required field it lacks: Snowflake needs user

Create request fields

FieldTypeNotes
namestring, required
connector_keystring, requiredFrom Supported connectors
auth_typestring, requiredOne of the connector's auth_type values
credentialsobject, requiredSee Credentials
categorydata_source or app, default data_sourceStored as sent, and the category list filter reads it: send the connector's category
scope_visibilityprivate or space, default privateprivate: only you. space: every workspace member

Query body and response

ConnectorsqueryResponse
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 lists tables, only tables with is_included: true can be queried.
  • timeout_seconds is optional: default 30, maximum 300.

Create a connection

  1. Pick connector_key and auth_type from Supported connectors and fill credentials from Credentials.

  2. 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": "…"
        }
      }'
  3. Keep the id from the 201 response (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 database connection with category data_source generates 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.

  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:

    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:

    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 with "auth_type": "oauth2" and the returned object as credentials.

Other members of a space OAuth connection connect their own account.

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:

    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

CallBehaviour
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

  1. Read the connection's query format and actions; instructions_prompt is 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"
  2. 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)."
    }
  3. For an action, put its key in operation and its params beside it. Slack list_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

  1. Send POST …/connections/{connection_id}/metadata, with no trailing slash and an optional timeout_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}'
  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

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.

  1. 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"}'
  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

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

ResponseCauseFix
422 {"detail": [{"field": "password", "message": "Password is required"}]}A required credential is missingAdd it; see Credentials
422 {"detail": [{"field": "port", "message": "Port must be text"}]}A credential was sent as a numberSend it as a string
422 Invalid auth type 'oauth2'. Must be one of: basicThe connector does not offer that auth_typeUse a value from Supported connectors
400 Connection test failed with provided credentialsThe host or credentials were rejectedCheck them; nothing was saved
409 Workspace sharing is available after a second member joinsscope_visibility: "space" in a one-member workspaceKeep private or invite a member
403 You do not have permission to access this connection.Someone other than the owner edited or deleted itAsk the owner
422 Query type 'DELETE' is not allowed. Only read-only queries …The SQL writes or changes schemaSend a read-only query
422 Access to table(s) 'PUBLIC.CUSTOMERS' is not allowed. …The table is excluded in the semantic layerSet 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 refusedUse an action without a query param
400 Query execution failed: …The database rejected the queryFix the SQL; the message is the database's
428 PER_USER_AUTH_REQUIREDYou have not connected your account to this shared OAuth connectionConnect your account
400 Invalid or expired OAuth statestate is wrong, already used, or belongs to another userRestart from init
307 on /query or /metadataThe URL has a trailing slashRemove it

On this page