> For the complete documentation index, see [llms.txt](https://v2.dataos.info/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://v2.dataos.info/references/v1/interfaces/apis/data-product-apis/getting-started/sdks/python-sdk.md).

# Python SDK

Connect once, then pull data, save views, and check on pipeline health using plain Python. No HTTP requests, no manual JSON parsing. You get back real Python objects with autocomplete, not raw dicts you have to guess the shape of.

Built on [uplink](https://uplink.readthedocs.io/) and [Pydantic v2](https://docs.pydantic.dev/).

## Install

{% file src="/files/2J5E0l30lKdlvRGxg3Nu" %}

```bash
pip install vulcan_sdk-0.228.1.28-py3-none-any.whl
```

Requires Python 3.9+. Dependencies (`pydantic`, `requests`, `uplink`) are installed automatically with the SDK.

## Connect

```python
from vulcan_sdk import VulcanClient

client = VulcanClient(
    fqdn="example.dataos.io",       # your DataOS instance
    dp_name="b2b_saas",             # the data product you're connecting to
    access_token="<your-dataos-api-token>",
    tenant="public",
)
```

That's it. Every example below just reuses this `client`. The SDK builds the base URL for you:

```
https://{fqdn}/vulcan/tenants/{tenant}/data-products/{tenant}-{dp_name}/api/v1
```

`Accept: application/json` is set on every request so the server always returns JSON (not Parquet).

***

## See what's inside a data product

Before you query anything, it helps to know what models, columns, and metrics you actually have to work with:

```python
schema = client.metadata.get_semantic()
for model in schema.models:
    print(model["name"], [c["name"] for c in model.get("columns", [])])
```

Need the full catalog instead (product info, lineage graph, gateway config, per-model freshness)? Use `client.metadata.get()`.

If you need a formal, standards-based description of a model instead, pull its data contract:

```python
contract = client.metadata.get_odcs_contract("customer.users")
print(contract.name, contract.version, contract.status)
```

Or the whole data product as one document:

```python
product = client.metadata.get_odps_product()
print(product.name, product.output_ports)
```

If the data product has usage terms attached, read them before you build against it:

```python
agreement_text = client.metadata.get_agreement()  # markdown string, or 404 if none is configured
```

***

## Run a query and get your data

There are two ways to ask for data. Use whichever one feels natural to you.

**Measures and dimensions, if you think in those terms:**

```python
stmt = client.query.submit_semantic_rest(
    query={
        "measures": ["orders.total_revenue"],
        "dimensions": ["orders.ORDERSTATUS"],
        "limit": 10,
    }
)
```

**Or just write SQL:**

```python
stmt = client.query.submit_semantic_sql(
    "SELECT ORDERKEY, ORDERSTATUS FROM orders LIMIT 10"
)
```

Need raw SQL against physical tables instead of the semantic layer? Use `client.query.submit_statement(sql)`.

Either way, you get a response back immediately with a job id. The query itself runs in the background, so you need to wait for it to finish before you can grab the results:

```python
import time

while stmt.status.upper() not in {"SUCCESS", "SUCCEEDED", "FAILED", "CANCELLED"}:
    time.sleep(2)
    stmt = client.query.get_statement(stmt.id)

result = client.query.get_statement_result(stmt.id)
print(result.cols)
for row in result.rows:
    print(row)
```

Tip: if you'd rather work with a DataFrame, this takes one line:

```python
import pandas as pd
df = pd.DataFrame(result.rows, columns=result.cols)
```

If you'd rather write GraphQL, `submit_semantic_rest` takes it directly via `graphql_body`:

```python
stmt = client.query.submit_semantic_rest(
    query={},
    graphql_body="query { table(limit: 10) { orders { ORDERKEY ORDERSTATUS } } }",
)
```

Need the generated warehouse SQL instead of executing the query? See the [transpile-only recipe](https://v2.dataos.info/consume/recipes/transpile-semantic-queries-to-native-warehouse-sql). Need to page through more than 50,000 rows? See the [pagination recipe](https://v2.dataos.info/consume/recipes/retrieve-large-result-sets-with-pagination).

### Querying a named metric

If the data product has metrics already defined, you can just ask for one by name:

```python
data = client.query.get_metric("CUSTOMER_GROWTH", granularity="month", limit=100)
```

If you need filters on top of that, use `post_metric` instead and poll for the result the same way you would for any other query:

```python
stmt = client.query.post_metric(
    "CUSTOMER_GROWTH",
    body={"dimensions": ["ts"], "granularity": "month", "filters": []},
)
```

***

## Save a query so you don't have to rewrite it

Once a query has finished running, you can save it as a **perspective**, basically a named, shareable view that lives at its own slug:

```python
from vulcan_sdk import PerspectiveCreateRequest

new_view = client.perspectives.create(
    "top-customers",
    PerspectiveCreateRequest(name="Top Customers", statement_id=stmt.id, is_public=True),
)
```

`request` must be an actual `PerspectiveCreateRequest` / `PerspectiveUpdateRequest` instance (import both from `vulcan_sdk`); a plain dict raises `AttributeError`.

{% hint style="warning" %}
**Known issue, still present in 0.228.1.28:** `create()` raises `TypeError: got an unexpected keyword argument 'slug'`. The underlying request method only accepts a `body`, but the wrapper also tries to pass `slug` separately, which doesn't exist on that method. Until this is patched, call the lower-level API directly and put `slug` inside the request body instead:

```python
from vulcan_sdk import PerspectiveCreateRequest, PerspectiveDetail

request = PerspectiveCreateRequest(
    name="Top Customers",
    statement_id=stmt.id,
    slug="top-customers",   # slug goes in the body here, not as a separate argument
    is_public=True,
)
data = client.perspectives._api.create_perspective(body=request.model_dump(exclude_none=True))
new_view = PerspectiveDetail(**data)
```

`_api` is a private attribute, so treat this as a temporary workaround.
{% endhint %}

From then on you can just fetch it whenever you need it, no need to resubmit the query:

```python
result = client.perspectives.get_result("top-customers")
```

To see what's already been saved:

```python
listing = client.perspectives.list()
for p in listing.items:
    print(p.slug, p.name, p.owner)
```

```python
from vulcan_sdk import PerspectiveUpdateRequest

client.perspectives.update("top-customers", PerspectiveUpdateRequest(description="Q3 view"))
client.perspectives.delete("top-customers")
```

***

## Connect from Power BI or Tableau

If you'd rather work in a BI tool, you can grab the connection files and open them there instead:

```python
with open("powerbi.zip", "wb") as f:
    f.write(client.metadata.download_powerbi())

with open("connection.tds", "wb") as f:
    f.write(client.metadata.download_tableau())
```

These are only populated for production deployments, so don't expect much from a dev or staging environment.

Or skip the manual download and publish straight to Tableau Cloud or Tableau Server:

```python
result = client.metadata.publish_tableau({
    "server_address": "https://10ax.online.tableau.com",
    "site_id": "mysite",
    "token_name": "vulcan-publisher",
    "personal_access_token": "<token-secret>",
})
```

Credentials are used only for that one request, they're never stored, logged, or cached on the server side.

***

## Know whether you can trust the data

```python
summary = client.dq.get_summary()
print(summary.checks_count, summary.summary)
```

If something looks off, you can dig into the specific rule behind it:

```python
rules = client.dq.list_rules(model="orders")
for r in rules.rules:
    print(r.name, r.dimension, r.column)
```

***

## See what's been running behind the scenes

```python
runs = client.activity.list_runs(limit=10)
for r in runs.runs:
    print(r.run_id, r.success, r.start_ts)

plans = client.activity.list_plans(limit=10)
for p in plans.plans:
    print(p.plan_id, p.git_commit_sha)
```

Need something lighter for a timeline view instead of the full detail? Pass `summary=True` and you get back compact rows with change/error counts instead:

```python
plans = client.activity.list_plans(limit=10, summary=True)
runs = client.activity.list_runs(limit=10, summary=True)
```

You can also filter runs down to one plan, and drill into the actual SQL a run executed:

```python
runs = client.activity.list_runs(plan="142")  # plan_seq or plan_id, either works

statements = client.activity.get_run_sql_statements(runs.runs[0].run_id, limit=50)
```

***

## Stay in the loop

Follow a data product if you want to keep tabs on it:

```python
client.followers.follow()
```

And check what's happened recently:

```python
notifs = client.notifications.list(limit=20)
for n in notifs.notifications:
    print(n.event, n.created_ts)
```

***

## Handling errors

Every failed call raises the same exception, so you really only need one `except` block anywhere you use the SDK:

```python
from vulcan_sdk import VulcanClient, VulcanError

try:
    client.activity.get_plan("does-not-exist")
except VulcanError as e:
    print(e.status_code)   # e.g. 404
    print(e.detail)        # what actually went wrong
```

***

## Full method reference

Everything above covers the common cases. If you need something more specific, here's every method on every client.

### `client.metadata`

| Method                          | What it does                                                                                | Returns            |
| ------------------------------- | ------------------------------------------------------------------------------------------- | ------------------ |
| `get()`                         | Full catalog for the data product: name, version, fingerprint, product info, gateway config | `MetadataResponse` |
| `get_semantic()`                | The semantic schema: models, dimensions, measures                                           | `SemanticSchema`   |
| `get_odcs_contract(model_name)` | One model's data contract, ODCS v3.1.0 format                                               | `OdcsContract`     |
| `get_odps_product()`            | The whole data product as one document, ODPS v1.0.0 format                                  | `OdpsProduct`      |
| `get_agreement()`               | Usage agreement text (404 if none is configured)                                            | `str`              |
| `download_powerbi()`            | Power BI connection file as raw bytes                                                       | `bytes`            |
| `download_tableau()`            | Tableau connector file as raw bytes                                                         | `bytes`            |
| `publish_tableau(body)`         | Publish semantic models straight to Tableau Cloud/Server as a data source                   | `dict`             |

### `client.query`

| Method                                                         | What it does                                                                                | Returns             |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------- |
| `submit_semantic_rest(query, *, graphql_body=None, meta=None)` | Run a query using measures/dimensions, or GraphQL via `graphql_body`                        | `StatementAccepted` |
| `submit_semantic_sql(sql, *, meta=None)`                       | Run a query written as SQL                                                                  | `StatementAccepted` |
| `submit_statement(sql, *, meta=None)`                          | Run raw SQL directly against physical tables, bypassing the semantic layer                  | `StatementAccepted` |
| `get_statement(statement_id)`                                  | Check the status of a submitted query                                                       | `StatementDetail`   |
| `get_statement_result(statement_id)`                           | Get the rows back for a completed query                                                     | `StatementResult`   |
| `get_metric(metric_name, **params)`                            | Quick lookup of a named metric (dimensions, granularity, timezone, limit, offset as kwargs) | `dict`              |
| `post_metric(metric_name, body)`                               | Named metric query with filters/grouping in the body                                        | `StatementAccepted` |
| `get_usage_metrics()`                                          | Query usage stats for this data product                                                     | `UsageMetrics`      |

Query syntax references: [REST query format](/references/v1/interfaces/apis/data-product-apis/getting-started/querying-data-products/rest.md) · [SQL reference](/references/v1/interfaces/apis/data-product-apis/getting-started/querying-data-products/semantic-sql.md) · [GraphQL reference](/references/v1/interfaces/apis/data-product-apis/getting-started/querying-data-products/graphql.md)

### `client.perspectives`

| Method                                                    | What it does                                                                              | Returns                    |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -------------------------- |
| `list(*, limit=20, offset=0, owner=None, is_public=None)` | List saved perspectives                                                                   | `PerspectivesListResponse` |
| `get(slug)`                                               | Fetch one perspective's details                                                           | `PerspectiveDetail`        |
| `get_result(slug, *, force_refresh=None)`                 | Run the perspective's query and get the rows                                              | `StatementResult`          |
| `create(slug, request: PerspectiveCreateRequest)`         | Save a completed query as a new perspective ⚠️ broken in 0.228.1.28, see workaround above | `PerspectiveDetail`        |
| `update(slug, request: PerspectiveUpdateRequest)`         | Change a perspective's name, description, visibility, etc.                                | `PerspectiveDetail`        |
| `delete(slug)`                                            | Delete a perspective                                                                      | `dict`                     |

`request` must be an actual `PerspectiveCreateRequest` / `PerspectiveUpdateRequest` instance (import both from `vulcan_sdk`). A plain dict raises `AttributeError`.

### `client.followers`

| Method                                                           | What it does                                         | Returns                 |
| ---------------------------------------------------------------- | ---------------------------------------------------- | ----------------------- |
| `get_follow_status()`                                            | Whether you're currently following this data product | `FollowStatus`          |
| `follow(*, metadata=None)`                                       | Follow this data product                             | `Follower`              |
| `unfollow()`                                                     | Stop following this data product                     | `Follower`              |
| `get_analytics(*, granularity=None, start_ts=None, end_ts=None)` | Follower counts, optionally broken down over time    | `FollowerAnalytics`     |
| `list(*, limit=50, offset=0)`                                    | List everyone following this data product            | `FollowersListResponse` |

### `client.notifications`

| Method                                                                                            | What it does                                                                    | Returns                     |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------- |
| `list(*, run_id=None, plan_id=None, events=None, start_ts=None, end_ts=None, limit=50, offset=0)` | List notifications, optionally filtered by run, plan, event type, or time range | `NotificationsListResponse` |

### `client.dq`

| Method                                                          | What it does                                                                 | Returns              |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------- |
| `get_summary()`                                                 | Product-level data quality roll-up                                           | `DQSummary`          |
| `list_rules(*, model=None, dimension=None, limit=50, offset=0)` | List configured data quality rules and recent outcomes                       | `RulesResponse`      |
| `get_rule_by_name(model, dimension, name)`                      | Look up a rule by model, dimension, and check name (result is under `.rule`) | `RuleDetailResponse` |
| `get_rule(identity)`                                            | Look up a rule by its identity hash (result is under `.rule`)                | `RuleDetailResponse` |

### `client.activity`

| Method                                                                                                                      | What it does                                                                                              | Returns                                                              |
| --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `list_plans(*, model_names=None, limit=20, offset=0, start_ts=None, end_ts=None, success=None, summary=False, impact=None)` | List deployment plans; `impact` filters by `direct`/`indirect`/`metadata`/`all` when `model_names` is set | `PlansListResponse`, or `PlansListSummaryResponse` if `summary=True` |
| `get_plan(plan_id)`                                                                                                         | Fetch one plan's details                                                                                  | `Plan`                                                               |
| `get_plan_git_diff(plan_id)`                                                                                                | Git diff behind a plan                                                                                    | `PlanGitDiffResponse`                                                |
| `list_runs(*, model_names=None, success=None, limit=20, offset=0, start_ts=None, end_ts=None, plan=None, summary=False)`    | List pipeline runs; `plan` accepts a `plan_seq` or `plan_id`                                              | `RunsListResponse`, or `RunsListSummaryResponse` if `summary=True`   |
| `get_run(run_id)`                                                                                                           | Fetch one run's details                                                                                   | `Run`                                                                |
| `get_run_sql_statements(run_id, *, kind=None, status=None, limit=100, offset=0)`                                            | List the warehouse SQL statements a run executed                                                          | `dict`                                                               |
| `get_model_runs(model_name, *, limit=20, offset=0, success=None, start_ts=None, end_ts=None, plan=None)`                    | Runs that touched a specific model, with per-run timing/row metrics                                       | `ModelRunActivitiesResponse`                                         |
| `list_models(*, limit=20, offset=0)`                                                                                        | Every model tracked in this data product, with its latest run                                             | `ModelsListResponse`                                                 |

***

## Cheat sheet

| I want to...                         | Call this                                                                                                                  |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| Connect                              | `VulcanClient(fqdn=..., dp_name=..., access_token=..., tenant=...)`                                                        |
| See available models and columns     | `client.metadata.get_semantic()`                                                                                           |
| Get a model's data contract          | `client.metadata.get_odcs_contract(model_name)`                                                                            |
| Get the whole product as one doc     | `client.metadata.get_odps_product()`                                                                                       |
| Read the usage agreement             | `client.metadata.get_agreement()`                                                                                          |
| Run a query (measures/dimensions)    | `client.query.submit_semantic_rest(query={...})`                                                                           |
| Run a query (SQL)                    | `client.query.submit_semantic_sql("SELECT ...")`                                                                           |
| Run raw SQL (physical tables)        | `client.query.submit_statement("SELECT ...")`                                                                              |
| Get the generated warehouse SQL only | See the [transpile-only recipe](https://v2.dataos.info/consume/recipes/transpile-semantic-queries-to-native-warehouse-sql) |
| Check if a query is done             | `client.query.get_statement(id)`                                                                                           |
| Get query results                    | `client.query.get_statement_result(id)`                                                                                    |
| Query a named metric                 | `client.query.get_metric(name, **filters)`                                                                                 |
| Query a metric with filters          | `client.query.post_metric(name, body={...})`                                                                               |
| Save a query for reuse               | `client.perspectives.create(slug, PerspectiveCreateRequest(...))` ⚠️ broken in 0.228.1.28, see workaround                  |
| Fetch a saved view                   | `client.perspectives.get_result(slug)`                                                                                     |
| Download Power BI / Tableau files    | `client.metadata.download_powerbi()` / `download_tableau()`                                                                |
| Publish straight to Tableau          | `client.metadata.publish_tableau({...})`                                                                                   |
| Check data quality                   | `client.dq.get_summary()`                                                                                                  |
| See recent pipeline runs             | `client.activity.list_runs()`                                                                                              |
| See the SQL a run executed           | `client.activity.get_run_sql_statements(run_id)`                                                                           |
| Follow a data product                | `client.followers.follow()`                                                                                                |
| See notifications                    | `client.notifications.list()`                                                                                              |

***

## Constructor Options

| Parameter      | Type    | Required | Description                                 |
| -------------- | ------- | -------- | ------------------------------------------- |
| `fqdn`         | `str`   | ✅        | DataOS FQDN, e.g. `"example.dataos.io"`     |
| `dp_name`      | `str`   | ✅        | Data product name, e.g. `"b2b_saas"`        |
| `access_token` | `str`   | ✅        | DataOS API token                            |
| `tenant`       | `str`   | ✅        | Tenant name, e.g. `"public"`                |
| `timeout`      | `float` | No       | Request timeout in seconds (default `30.0`) |
| `verify_ssl`   | `bool`  | No       | Verify TLS certificates (default `True`)    |

## Response Models

All methods return typed Pydantic v2 models with full IDE autocomplete. Extra fields from the API are preserved via `extra="allow"`.

| Client          | Key Models                                                                                                    |
| --------------- | ------------------------------------------------------------------------------------------------------------- |
| `metadata`      | `MetadataResponse`, `SemanticSchema`                                                                          |
| `activity`      | `Plan` (key: `plan_id`), `PlansListResponse`, `Run` (key: `run_id`), `RunsListResponse`, `ModelsListResponse` |
| `dq`            | `DQSummary`, `RulesResponse`, `RuleDetailResponse`                                                            |
| `quality`       | `ChecksListResponse`, `CheckDetailResponse`                                                                   |
| `query`         | `StatementAccepted` (key: `id`), `StatementDetail`, `StatementResult`, `UsageMetrics`                         |
| `perspectives`  | `PerspectiveDetail`, `PerspectivesListResponse` (items via `.items`)                                          |
| `notifications` | `NotificationsListResponse`, `Notification`                                                                   |
| `followers`     | `FollowStatus`, `FollowersListResponse`, `FollowerAnalytics`                                                  |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://v2.dataos.info/references/v1/interfaces/apis/data-product-apis/getting-started/sdks/python-sdk.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
