Direct Connections
Connections to data warehouses, Cube, and dbt semantic layers.
A direct connection stores credentials/config for a data source. Secrets (passwords, keys) are never returned — see What connectionFields contains for what you do get back, which depends on who owns the connection.
Using one to create a metric? See Importing a dbt metric or Creating a metric from a data warehouse or Cube connection.
Full field reference (types, enums, required/read-only flags) is generated from components.schemas.DirectConnection in the API Reference tab, and tabulated field by field — with what each one's read-only status means for a write — in Field Reference. Or fetch the live OpenAPI document directly, see Getting Started for the URL.
Two kinds of connection
The CRUD endpoints cover both kinds, and so does get_connection_fields, but the per-connection metadata sub-endpoints do not — each kind exposes what it can query in a different way:
| Kind | Identified by | Metadata endpoint |
|---|---|---|
| Direct database | dataBaseType is any other value | /tables and /tables/:tableName/fields |
| dbt | dataBaseType is "dbt" | /metrics |
Asking the wrong one for a connection is a 400 naming the endpoint that does serve it, so the two cases stay distinguishable from a connection that does not exist (404).
GET /direct_connections · GET /direct_connections/:connectionId
GET /direct_connections · GET /direct_connections/:connectionId{
"id": "13451dc615244a9cb6c99203a3abbea5",
"name": "Prod Postgres",
"dataBaseType": "postgresql",
"ownerId": "7c2f599e6dfb402996b2194ab9b11164",
"isShared": true,
"connectionFields": { "user": "reporting_ro", "_host": "db.example.com", "_port": 5432, "_database": "analytics", "currentSchema": "public" },
"version": "1.0"
}Field names under connectionFields vary by dataBaseType — each connector defines its own parameter names (e.g. Postgres uses _host/_port/_database; other connectors differ).
What the list returns
GET /direct_connections returns the connections available to you: the ones you own, plus the ones shared across the account. isShared is what distinguishes them — it marks a connection as shared with every user in the account, rather than with any particular user. Another user's unshared connection is not yours to see, and the detail endpoint answers 404 for one.
What connectionFields contains
connectionFields containsconnectionFields is populated only for connections you own. A connection shared with the account comes back with an empty object, even though you can otherwise read and query it:
{
"id": "8944c31f217bed24f295a26c5d39b9d0",
"name": "Klipfolio Analytics",
"dataBaseType": "bigquery",
"ownerId": "71f30bd60e4fb72bf8703743ae1084ec",
"isShared": true,
"connectionFields": {},
"version": "1.0"
}The one exception is the built-in sample connection (dataBaseType: "sample"), which reports its fields to everyone — it holds no private credentials.
Within a connection you own, secret fields (passwords, private keys, OAuth credentials) are omitted rather than masked — so a field you set on create may be absent when you read it back. Everything non-secret is returned as a plain scalar, as in the example above.
A missing secret is not a problem for a subsequent update, though: an update keeps every secret whose key it does not send, so you can GET a connection and PUT it back unchanged without its credentials being lost. See Updating without resending secrets.
GET /direct_connections/get_connection_fields/:dataBaseType
GET /direct_connections/get_connection_fields/:dataBaseTypeWhat to send under connectionFields for a given connector. Read-only, and about the connector type rather than any connection — no connection need exist, and dataBaseType is matched case-insensitively (Snowflake works).
The response is an array of arrays. Each inner array is one complete set of fields that satisfies the connection — send the fields of one set, not a mixture of two.
| Field | Type | Notes |
|---|---|---|
name | string | the key to use under connectionFields |
type | string | the kind of value expected, as the connector reports it — e.g. string, password, port, host, keyFile, certFile, or choice |
required | boolean | whether the field must be present in this set |
sensitive | boolean | whether the value is a secret — see below |
value | string | choice fields only — the value this set fixes the field to; send it verbatim |
There is more than one set whenever the connector offers a choice. Snowflake, for example, authenticates either by password or by key pair, so it publishes two:
[
[
{ "name": "user", "type": "string", "required": true, "sensitive": false },
{ "name": "_authType", "type": "choice", "required": false, "sensitive": false, "value": "USER_PASSWORD" },
{ "name": "password", "type": "password", "required": true, "sensitive": true },
{ "name": "_accountId", "type": "string", "required": true, "sensitive": false },
{ "name": "db", "type": "string", "required": true, "sensitive": false },
{ "name": "schema", "type": "string", "required": true, "sensitive": false },
{ "name": "warehouse", "type": "string", "required": true, "sensitive": false },
{ "name": "role", "type": "string", "required": false, "sensitive": false }
],
[
{ "name": "user", "type": "string", "required": true, "sensitive": false },
{ "name": "_authType", "type": "choice", "required": false, "sensitive": false, "value": "KEY_PAIR" },
{ "name": "privateKey__privateKey", "type": "keyFile", "required": true, "sensitive": true },
{ "name": "_privateKey_passphrase", "type": "password", "required": false, "sensitive": true },
{ "name": "_accountId", "type": "string", "required": true, "sensitive": false },
{ "name": "db", "type": "string", "required": true, "sensitive": false },
{ "name": "schema", "type": "string", "required": true, "sensitive": false },
{ "name": "warehouse", "type": "string", "required": true, "sensitive": false },
{ "name": "role", "type": "string", "required": false, "sensitive": false }
]
]A choice field is a field like any other: pick a set, then send the choice field with the value that set names, alongside the rest of the set.
A connector with no choices publishes a single set — that array of one is still the response shape:
[
[
{ "name": "environmentId", "type": "string", "required": true, "sensitive": false },
{ "name": "token", "type": "password", "required": true, "sensitive": true },
{ "name": "hostName", "type": "host", "required": true, "sensitive": false }
]
]Sensitive fields, create versus update
sensitive: true marks a secret — a password, token, private key or certificate. Secrets are accepted on POST but never returned by any read (see What connectionFields contains), so on create you must supply every one the set requires.
On PUT you do not: see Updating without resending secrets.
An unknown dataBaseType responds 404, naming the types that do exist.
POST /direct_connections
POST /direct_connectionsTwo fields are required on every write:
dataBaseType— the connector type (e.g.postgresql,snowflake,redshift,bigquery,databricks,mariadb,aurora,azuresql,azuresynapse,dbt,cube,sample).isShared— whether the connection is shared with every user in the account. There is no default; sendfalsefor a connection only you can use.
A body missing either is rejected with 400. Returns 201 with the meta/data envelope.
{
"name": "Prod Postgres",
"dataBaseType": "postgresql",
"isShared": true,
"connectionFields": {
"user": "reporting_ro",
"password": "<secret>",
"_host": "db.example.com",
"_port": 5432,
"_database": "analytics",
"currentSchema": "public"
}
}Field names under connectionFields vary by dataBaseType, as above. Rather than guessing them, ask GET /direct_connections/get_connection_fields/:dataBaseType for the sets of fields that connector accepts.
Pass "dbt" to create a dbt semantic-layer connection; it is stored in a different place from the database connectors, which is why it takes no connectionFields of the kind they do.
PUT /direct_connections/:connectionId
PUT /direct_connections/:connectionIdReplace editable fields; returns the updated connection. dataBaseType and isShared are required here too, so resend both.
Updating without resending secrets
Secrets are the exception to "replace editable fields": an update preserves any sensitive field whose key it does not send. Since a read omits those keys entirely (see What connectionFields contains), the spec you get back from GET is exactly what PUT accepts — you can send it straight back and the stored credentials survive.
So on an update, a sensitive field has three behaviours:
| What you send | What happens |
|---|---|
| the key is absent | the stored secret is kept |
| a new value | the stored secret is replaced |
an empty value ("" or null) | the secret is cleared |
Which fields count as sensitive is per connector — ask get_connection_fields and read the sensitive flag. Non-sensitive fields follow the ordinary replace rule, so an editable one you omit is cleared as usual.
DELETE /direct_connections/:connectionId
DELETE /direct_connections/:connectionId204 No Content.
GET /direct_connections/:connectionId/tables
GET /direct_connections/:connectionId/tablesList the tables the connection can query. Read-only.
| Field | Type | Notes |
|---|---|---|
name | string | table identifier, as used in the fields path |
description | string | |
type | enum | CUBE | CUBE_VIEW — always upper-cased by the gateway |
[
{ "name": "orders", "description": "All orders", "type": "CUBE" },
{ "name": "order_summary", "description": "Orders rolled up by month", "type": "CUBE_VIEW" }
]Table metadata is carried only by direct database connections — those whose dataBaseType is anything other than dbt:
| Situation | Response |
|---|---|
| The connection is a dbt connection | 400 — use /metrics instead |
| No connection with that id | 404 |
| Direct connection exposing no table metadata | 404 |
| Direct connection with no tables right now | 200 with [] |
GET /direct_connections/:connectionId/tables/:tableName/fields
GET /direct_connections/:connectionId/tables/:tableName/fieldsList the fields of one of the connection's tables. Read-only. A tableName containing characters that are significant in a URL path must be percent-encoded.
| Field | Type | Notes |
|---|---|---|
name | string | field identifier, as used in a query |
label | string | short display name; falls back to name |
description | string | |
dataType | string | the column's type as the connection reports it, passed through unchanged — e.g. string, double, int, datetime, boolean |
type | string | passed through unchanged — e.g. metric, dimension |
dataType and type are not case-normalized (unlike type on a table), for consistency with GET /query_builder/services/:serviceName/views/:viewName/fields, which describes columns the same way.
[
{ "name": "orders.amount", "label": "Amount", "description": "Order total", "dataType": "double", "type": "metric" },
{ "name": "orders.created_at", "label": "Created At", "dataType": "datetime", "type": "dimension" }
]An unknown tableName responds 404, and a dbt connection responds 400, as above.
GET /direct_connections/:connectionId/metrics
GET /direct_connections/:connectionId/metricsList the metrics a dbt connection's project defines. Read-only.
These are candidate metrics, not metrics that exist yet: each one references a metric defined in dbt and carries no id. They are returned in the same public shape the metrics endpoints use, so an entry can be sent to POST /metrics to create it for the current user. Creating one is not required — the list describes everything available in the dbt project.
sourceType is DBT, sourceId is the connection's id, and fieldId is the metric's name in dbt. Those three are what identify the dbt metric on create.
An entry can be posted back unchanged: POST /metrics ignores the read-only properties.sl*/supportedPeriodicities entries rather than rejecting them, so there is nothing to strip first. They describe the upstream dbt definition, so there is nothing for a create to apply. (The internal slMetricDigest is not returned here at all — see the Metrics guide.)
[
{
"name": "Total Revenue",
"description": "Sum of order revenue",
"sourceType": "DBT",
"sourceId": "conn_dbt_1",
"fieldId": "total_revenue",
"timeRefId": "metric_time",
"properties": {
"numberFormat": "NUMERIC",
"dataShape": "TRANSACTIONAL",
"snapshotPeriod": "none",
"timelessData": true,
"slMetricType": "SIMPLE",
"supportedPeriodicities": "1d,1w,1mo"
},
"dimensions": [
{ "id": "sl_abc123", "name": "Region", "fieldId": "customer__region" }
],
"version": "1.0"
}
]| Situation | Response |
|---|---|
| The connection is a direct database connection | 400 — use /tables instead |
| No connection with that id | 404 |
| dbt connection whose project defines no metrics | 200 with [] |
Updated 6 days ago