The Semantic Layer has two APIs: GraphQL, for metadata and queries over HTTP, and an Arrow Flight SQL
endpoint, reached through JDBC or ADBC, that returns results as Arrow tables. From Python you rarely need to
talk to either directly: dbt Labs’ dbt-sl-sdk wraps both, picks the right one for each call and returns
pyarrow tables.
Copy and paste
pip install "dbt-sl-sdk[sync]" pandas
export DBT_SL_HOST=semantic-layer.cloud.getdbt.com # EMEA: semantic-layer.emea.dbt.com
export DBT_SL_ENVIRONMENT_ID=123456
export DBT_SL_TOKEN=dbtc_... # service token or personal access token
# sl_query.py
import os
from dbtsl import SemanticLayerClient
client = SemanticLayerClient(
environment_id=int(os.environ["DBT_SL_ENVIRONMENT_ID"]),
auth_token=os.environ["DBT_SL_TOKEN"],
host=os.environ["DBT_SL_HOST"],
)
with client.session(): # every API call goes inside a session
# what exists
for m in client.metrics():
print(m.name, "-", m.description)
print([d.qualified_name for d in client.dimensions(metrics=["revenue"])]) # customer__country, ...
# a query: same names and filters as dbt sl query
table = client.query(
metrics=["revenue", "order_count"],
group_by=["metric_time__month", "customer__country"],
where=["{{ TimeDimension('metric_time', 'day') }} >= '2026-01-01'"],
order_by=["metric_time__month", "-revenue"],
limit=100,
)
df = table.to_pandas()
print(df)
# a saved query, and the SQL a query would run
print(client.query(saved_query="monthly_sales_by_country").to_pandas())
print(client.compile_sql(metrics=["revenue"], group_by=["metric_time__month"]))
What the client does
metrics(),dimensions(metrics=[...]),entities(...),saved_queries()read metadata through GraphQL.query(...)runs through the Arrow Flight SQL endpoint and returns apyarrow.Table; call.to_pandas()or hand it to Polars.compile_sql(...)returns the SQL without running it, like--compileon the command line.session()opens the connections once. Open one session per script or per application, not one per request.
The parameters mirror dbt sl query: group_by uses the same names (customer__country,
metric_time__month), where takes the same Jinja filter strings, order_by takes names with a - for
descending order, and saved_query replaces metrics and group_by. A dimension’s qualified_name
(customer__country) is the form to use in group_by; its name alone (country) is not. The SDK
validates the combination before sending it: an order_by on a column that isn’t in metrics or
group_by fails locally with “Cannot determine if the specified order_by clause … is a metric or a
dimension/entity”.
Async applications
In FastAPI or another asyncio application, install dbt-sl-sdk[async] and use
from dbtsl.asyncio import AsyncSemanticLayerClient with async with client.session() and await on each
call. The sync client would block the event loop.
Without the SDK
- GraphQL is plain HTTP:
POST https://semantic-layer.cloud.getdbt.com/api/graphqlwith the headerAuthorization: Bearer <token>and theenvironmentIdin each query. Queries are asynchronous: acreateQuerymutation returns a query ID, and you pollquery(environmentId, queryId)until the status is successful. Useful from languages without an SDK, and for exploring the schema. - JDBC/ADBC speaks SQL with Jinja calls inside:
select * from {{ semantic_layer.query(metrics=['revenue'], group_by=['metric_time__month']) }}. The URL isjdbc:arrow-flight-sql://semantic-layer.cloud.getdbt.com:443?environmentId=...&token=..., and it works with SQL clients such as DataGrip through the Arrow Flight SQL JDBC driver, version 12.0.0 or later.
Tokens
A service token needs the Semantic Layer Only and Metadata Only permissions; a personal access token runs queries with the user’s own credentials. Keep tokens in environment variables or a secrets manager, never in notebooks you share.
The parameters were validated with dbt-sl-sdk 0.13.4; the endpoints and the API flow come from the dbt documentation (October 2026).