> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paradime.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Discovery API

> Use cases and example queries for the Paradime Discovery API: monitor pipeline performance, data quality, lineage, ownership, and dataset changes with GraphQL.

<Info>
  **Prerequisites:**

  * Your API keys ***must*** have the Discovery API Viewer capability.
  * Discovery metadata is populated from your [Bolt](/products/bolt) runs. An environment must have at least one completed run before the API returns data for it.
</Info>

The Discovery API lets you query the metadata Paradime collects every time Bolt runs your dbt project: the project structure from the manifest, run results, test outcomes, source freshness, and warehouse catalog information. Use it to power data quality monitoring, lineage tooling, cost and performance analysis, data catalogs, and audit workflows.

Every query starts from the `environment` field, identified by the environment `slug` (for example `production`). Each environment exposes two states:

* **`definition`**: the project as declared in code, parsed from the latest manifest.
* **`applied`**: the definition joined with the latest execution state, including run status, timing, test results, source freshness, and catalog metadata.

<Info>
  The examples below authenticate with an **account API key**: pass `Authorization: Bearer <token>` and `X-Paradime-Workspace: <workspace_uid>` (the account key starts with `prdm_cmp_`). Legacy **workspace API keys** are still supported: send `X-API-KEY` and `X-API-SECRET` headers instead. See [API Keys](/developers/api-keys).
</Info>

## Use cases

| Use case                    | Outcome                                                                                             | Example questions                                                                                                |
| --------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| [Performance](#performance) | Identify inefficiencies in pipeline execution to reduce infrastructure costs and improve timeliness | What's the latest status of each model? Do I need to run this model? How long did my models take to run?         |
| [Quality](#quality)         | Monitor data source freshness and test results to resolve issues and drive trust in data            | How fresh are my data sources? Which tests and models failed? What's my project's test coverage?                 |
| [Discovery](#discovery)     | Find and understand relevant datasets with rich context and metadata                                | What do these tables and columns mean? What's the full data lineage at a model level? Which metrics are defined? |
| [Governance](#governance)   | Audit data development and facilitate collaboration within and between teams                        | Who is responsible for this model? How do I contact the model's owner? Who can use this model?                   |
| [Development](#development) | Understand dataset changes and usage and gauge impacts to inform project development                | How is this model used in BI tools? Which nodes depend on this data source? How has a model changed over time?   |

## Running queries

Send every query as a `POST` to your API endpoint. The first example is shown in full; the remaining examples show only the GraphQL document and variables, which you can drop into the same request shape.

<Tabs>
  <Tab title="Python">
    ```python theme={"system"}
    import requests

    # API credentials
    api_endpoint = "<YOUR_API_ENDPOINT>"
    api_token = "<YOUR_API_TOKEN>"          # account API key (starts with prdm_cmp_)
    workspace_uid = "<YOUR_WORKSPACE_UID>"

    graphql_query = """
    query EnvironmentOverview($environmentSlug: String!) {
        environment(slug: $environmentSlug) {
            dbtProjectName
            adapterType
            applied {
                lastUpdatedAt
                resourceCounts
                packages
            }
        }
    }
    """

    variables = {"environmentSlug": "production"}

    response = requests.post(
        api_endpoint,
        json={"query": graphql_query, "variables": variables},
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_token}",
            "X-Paradime-Workspace": workspace_uid,
        },
    )

    print(response.json())
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"system"}
    curl -X POST "<YOUR_API_ENDPOINT>" \
         -H "Content-Type: application/json" \
         -H "Authorization: Bearer <YOUR_API_TOKEN>" \
         -H "X-Paradime-Workspace: <YOUR_WORKSPACE_UID>" \
         -d '{
           "query": "query EnvironmentOverview($environmentSlug: String!) { environment(slug: $environmentSlug) { dbtProjectName adapterType applied { lastUpdatedAt resourceCounts packages } } }",
           "variables": {
             "environmentSlug": "production"
           }
         }'
    ```
  </Tab>
</Tabs>

<Accordion title="Example response">
  ```json theme={"system"}
  {
    "data": {
      "environment": {
        "dbtProjectName": "demo_sales_project",
        "adapterType": "snowflake",
        "applied": {
          "lastUpdatedAt": "2026-08-06T02:14:33+00:00",
          "resourceCounts": "{\"model\": 24, \"source\": 4, \"test\": 38, \"seed\": 2, \"snapshot\": 1, \"exposure\": 3}",
          "packages": ["demo_sales_project", "dbt_utils"]
        }
      }
    }
  }
  ```
</Accordion>

<Note>
  Connection fields (`models`, `sources`, `tests`, and so on) are cursor-paginated. Pass `first` to limit page size and `after` with the previous page's `pageInfo.endCursor` to fetch the next page. Every connection also exposes `totalCount`.
</Note>

## Performance

Identify inefficiencies in pipeline execution to reduce infrastructure costs and improve timeliness. Query the latest applied state across the DAG with `environment { applied { models } }`, or the run history of a single node with `environment { applied { modelHistoricalRuns } }`.

### How long did each model take to run?

Understand how long it takes to build each model. Longer build times result in higher infrastructure costs and fresher data arriving later to stakeholders.

Get a list of executed models and their execution time:

```graphql theme={"system"}
query AppliedModels($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    applied {
      models(first: $first) {
        edges {
          node {
            name
            uniqueId
            materializedType
            executionInfo {
              lastSuccessRunId
              executionTime
              executeStartedAt
            }
          }
        }
      }
    }
  }
}
```

Then drill into the run history of the longest-running model with `modelHistoricalRuns`. You can look it up by `uniqueId` or by `identifier` (the model's alias):

```graphql theme={"system"}
query ModelHistoricalRuns(
  $environmentSlug: String!
  $uniqueId: String
  $lastRunCount: Int
) {
  environment(slug: $environmentSlug) {
    applied {
      modelHistoricalRuns(uniqueId: $uniqueId, lastRunCount: $lastRunCount) {
        name
        runId
        runElapsedTime
        generatedAt
        executionTime
        executeStartedAt
        executeCompletedAt
        status
      }
    }
  }
}
```

```json theme={"system"}
{
  "environmentSlug": "production",
  "uniqueId": "model.demo_sales_project.order_items",
  "lastRunCount": 20
}
```

<Note>
  `runId` values returned by the Discovery API are Paradime Bolt `schedule_run` IDs. You can pass them to the [Bolt API](/developers/graphql-api/api-reference/bolt-api) (for example `boltRunStatus`) to inspect the full run.
</Note>

### What's the latest state of each model?

Retrieve the applied state of your models and how they arrived in that state: the status, timing, and error of the most recent run, plus the most recent successful run.

```graphql theme={"system"}
query LatestModelState($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    applied {
      models(first: $first) {
        edges {
          node {
            uniqueId
            compiledCode
            database
            schema
            alias
            materializedType
            executionInfo {
              lastRunId
              lastRunStatus
              lastRunError
              lastRunGeneratedAt
              lastSuccessRunId
              executeStartedAt
              executeCompletedAt
              executionTime
              runElapsedTime
            }
          }
        }
      }
    }
  }
}
```

<Note>
  Use `lastRunStatus` to determine whether a run failed. `lastRunError` carries the warehouse adapter's response message for the last execution, so it can be populated (for example with `CREATE VIEW (0 processed)`) even when the run succeeded.
</Note>

### What's changed since the last run?

Determine whether a model actually needs to be rebuilt. A view with no code change, or a table whose code and upstream source data are both unchanged, does not need to run again. Unnecessary runs waste warehouse spend.

Compare the `rawCode` in the applied state (what last ran) against the definition state (what is currently in your repository), and collect the model's upstream sources:

```graphql theme={"system"}
query ModelDrift($environmentSlug: String!, $uniqueId: String!) {
  environment(slug: $environmentSlug) {
    applied {
      models(first: 1, filter: { uniqueId: $uniqueId }) {
        edges {
          node {
            rawCode
            materializedType
            executionInfo {
              lastRunGeneratedAt
              executeCompletedAt
            }
            ancestors(types: ["source"]) {
              uniqueId
              name
            }
          }
        }
      }
    }
    definition {
      models(first: 1, filter: { uniqueId: $uniqueId }) {
        edges {
          node {
            rawCode
            materializedType
          }
        }
      }
    }
  }
}
```

If the code matches, check whether the upstream source data has been loaded since the model last ran by passing the ancestor `uniqueId` values into a sources query and comparing `freshness.maxLoadedAt` with the model's `executeCompletedAt`:

```graphql theme={"system"}
query SourceLoadTimes($environmentSlug: String!, $uniqueIds: [String]) {
  environment(slug: $environmentSlug) {
    applied {
      sources(first: 50, filter: { uniqueIds: $uniqueIds }) {
        edges {
          node {
            uniqueId
            freshness {
              maxLoadedAt
            }
          }
        }
      }
    }
  }
}
```

## Quality

Monitor data source freshness and test results to diagnose and resolve issues and drive trust in data. Combined with [webhooks](/developers/webhooks), these queries help you detect, investigate, and alert on incidents.

### Which models and tests failed to run?

Filter on the latest status to get the models that failed to build and the tests that failed during their most recent execution. Helpful when diagnosing issues that result in delayed or incorrect data.

```graphql theme={"system"}
query FailedModelsAndTests($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    applied {
      models(first: $first, filter: { lastRunStatus: "error" }) {
        edges {
          node {
            name
            executionInfo {
              lastRunId
              lastRunError
            }
          }
        }
      }
      tests(first: $first, filter: { status: "fail" }) {
        edges {
          node {
            name
            executionInfo {
              lastRunId
              lastFailures
            }
          }
        }
      }
    }
  }
}
```

To review the historical failure rate of a given model, fetch its recent run history:

```graphql theme={"system"}
query ModelFailureHistory(
  $environmentSlug: String!
  $uniqueId: String!
  $lastRunCount: Int
) {
  environment(slug: $environmentSlug) {
    applied {
      modelHistoricalRuns(uniqueId: $uniqueId, lastRunCount: $lastRunCount) {
        runId
        executeStartedAt
        status
        error
      }
    }
  }
}
```

### When was the data my model uses last refreshed?

Gauge the freshness of everything feeding a given model. First fetch the model's transitive upstream nodes:

```graphql theme={"system"}
query ModelAncestors($environmentSlug: String!, $uniqueId: String!) {
  environment(slug: $environmentSlug) {
    applied {
      models(first: 1, filter: { uniqueId: $uniqueId }) {
        edges {
          node {
            name
            ancestors(types: ["model", "source", "seed", "snapshot"]) {
              uniqueId
              name
              resourceType
            }
          }
        }
      }
    }
  }
}
```

Then fetch the execution or load time for each ancestor type in a single follow-up query, passing the `uniqueId` values from the first result:

```graphql theme={"system"}
query AncestorFreshness(
  $environmentSlug: String!
  $modelIds: [String]
  $sourceIds: [String]
  $seedIds: [String]
  $snapshotIds: [String]
) {
  environment(slug: $environmentSlug) {
    applied {
      models(first: 100, filter: { uniqueIds: $modelIds }) {
        edges {
          node {
            uniqueId
            materializedType
            executionInfo {
              executeCompletedAt
            }
          }
        }
      }
      sources(first: 100, filter: { uniqueIds: $sourceIds }) {
        edges {
          node {
            uniqueId
            sourceName
            freshness {
              maxLoadedAt
            }
          }
        }
      }
      seeds(first: 100, filter: { uniqueIds: $seedIds }) {
        edges {
          node {
            uniqueId
            executionInfo {
              executeCompletedAt
            }
          }
        }
      }
      snapshots(first: 100, filter: { uniqueIds: $snapshotIds }) {
        edges {
          node {
            uniqueId
            executionInfo {
              executeCompletedAt
            }
          }
        }
      }
    }
  }
}
```

### Are my data sources fresh?

Check source freshness to ensure the data loaded into your warehouse complies with expectations. The API returns the latest freshness check result alongside the declared freshness criteria.

```graphql theme={"system"}
query SourceFreshness($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    applied {
      sources(first: $first, filter: { freshnessChecked: true }) {
        edges {
          node {
            sourceName
            name
            identifier
            loader
            freshnessConfig
            freshness {
              freshnessRunId
              freshnessStatus
              maxLoadedAt
              maxLoadedAtTimeAgoInS
              snapshottedAt
              criteria
            }
          }
        }
      }
    }
  }
}
```

<Note>
  You can also filter by outcome with `filter: { freshnessStatus: "error" }` (accepted values: `pass`, `warn`, `error`) to alert only on stale sources.
</Note>

### What's the test coverage and status?

Data tests ensure stakeholders work with high-quality data. The Discovery API returns complete test results for an environment, with each test linked to the nodes it runs against via `parents`.

```graphql theme={"system"}
query TestCoverage($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    applied {
      tests(first: $first) {
        edges {
          node {
            name
            columnName
            testType
            parents {
              name
              resourceType
            }
            executionInfo {
              lastRunStatus
              lastRunError
              executeCompletedAt
              executionTime
              lastFailures
            }
          }
        }
      }
    }
  }
}
```

### How is this model contracted and versioned?

Contracts enforce the shape of a model, and versions track discrete stages in its evolution. Retrieve the contract, constraints, version, and column schema of your public models:

```graphql theme={"system"}
query ContractedModels($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    applied {
      models(first: $first, filter: { access: "public" }) {
        edges {
          node {
            name
            version
            latestVersion
            contractEnforced
            constraints
            catalog {
              columns {
                name
                type
              }
            }
          }
        }
      }
    }
  }
}
```

## Discovery

Find and understand relevant datasets with rich context and metadata. Query the latest applied or definition state, often in the downstream part of the DAG (for example, mart models).

### What does this dataset and its columns mean?

Map a table or view in your warehouse back to the model in your dbt project and retrieve its meaning: the description, tags, and meta from your YAML files, plus per-column metadata from the warehouse catalog.

```graphql theme={"system"}
query DatasetMeaning($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    applied {
      models(
        first: $first
        filter: {
          database: "analytics"
          schema: "prod"
          identifier: "customers"
        }
      ) {
        edges {
          node {
            name
            description
            tags
            meta
            catalog {
              rowCount
              sizeBytes
              lastModified
              columns {
                name
                description
                type
              }
            }
          }
        }
      }
    }
  }
}
```

### What's the full data lineage at a model level?

Retrieve any model's upstream dependencies. Unlike dbt Cloud's Discovery API, `ancestors`, `parents`, and `children` return a single concrete node type, so no inline fragments are needed:

```graphql theme={"system"}
query ModelLineage($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    applied {
      models(first: $first) {
        edges {
          node {
            name
            ancestors(types: ["model", "source", "seed", "snapshot"]) {
              uniqueId
              name
              resourceType
              database
              schema
            }
          }
        }
      }
    }
  }
}
```

To reconstruct the entire DAG in one call, use the flat `lineage` feed instead. It returns every node with its direct parent edges:

```graphql theme={"system"}
query FullLineage($environmentSlug: String!) {
  environment(slug: $environmentSlug) {
    applied {
      lineage {
        uniqueId
        name
        resourceType
        parentIds
        publicParentIds
      }
    }
  }
}
```

### Which metrics are available?

Query the metrics defined in your project for documentation purposes (for example, a data catalog) or to drive downstream tooling.

```graphql theme={"system"}
query DefinedMetrics($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    definition {
      metrics(first: $first) {
        edges {
          node {
            name
            label
            description
            metricType
            typeParams
            filter
            tags
            parents {
              name
              resourceType
            }
          }
        }
      }
    }
  }
}
```

<Note>
  The `definition` state also exposes `semanticModels`, `savedQueries`, `macros`, and `functions` connections with the same query shape.
</Note>

## Governance

Audit data development and facilitate collaboration within and between teams.

### Who is responsible for this model?

Groups associate models with an owner. Fetch a model's `groupName` and `access`, then look up the owner details on the group:

```graphql theme={"system"}
query ModelOwnership($environmentSlug: String!, $uniqueId: String!) {
  environment(slug: $environmentSlug) {
    applied {
      models(first: 1, filter: { uniqueId: $uniqueId }) {
        edges {
          node {
            name
            description
            access
            groupName
          }
        }
      }
    }
    definition {
      groups(first: 100) {
        edges {
          node {
            name
            ownerName
            ownerEmail
          }
        }
      }
    }
  }
}
```

You can also list every model owned by a team by filtering on the group directly:

```graphql theme={"system"}
query ModelsByGroup($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    definition {
      models(first: $first, filter: { group: "finance" }) {
        edges {
          node {
            name
            groupName
          }
        }
      }
    }
  }
}
```

### Who can use this model?

The `access` field specifies the level of access for a given model: `public`, `protected`, or `private`. Public models function like APIs that other teams can build on.

Retrieve the access level of every model:

```graphql theme={"system"}
query ModelAccess($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    definition {
      models(first: $first) {
        edges {
          node {
            name
            access
          }
        }
      }
    }
  }
}
```

Or retrieve only the public models:

```graphql theme={"system"}
query PublicModels($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    definition {
      models(first: $first, filter: { access: "public" }) {
        edges {
          node {
            name
          }
        }
      }
    }
  }
}
```

## Development

Understand dataset changes and usage and gauge impacts to inform project development.

### How is this model used in downstream tools?

Exposures define how models are used in dashboards, notebooks, and other downstream tools. Query an exposure to see which nodes feed it, plus Paradime's computed health rollup across all of its ancestors: worst source freshness, worst run status, and worst test result.

```graphql theme={"system"}
query ExposureHealth($environmentSlug: String!, $first: Int!) {
  environment(slug: $environmentSlug) {
    applied {
      exposures(first: $first) {
        edges {
          node {
            name
            description
            exposureType
            maturity
            ownerName
            ownerEmail
            url
            parents {
              name
              resourceType
            }
            freshnessStatus
            runStatus
            quality
            isHealthy
            healthIssues
          }
        }
      }
    }
  }
}
```

<Note>
  Filter by tool type with `filter: { exposureType: "dashboard" }` (accepted values: `dashboard`, `notebook`, `analysis`, `ml`, `application`).
</Note>

### How has this model changed over time?

View how a model evolved across recent runs, including the compiled SQL and the column schema and table stats captured for each execution. Pass `withCatalog: true` to include catalog data where it was generated for that run.

```graphql theme={"system"}
query ModelEvolution(
  $environmentSlug: String!
  $uniqueId: String!
  $lastRunCount: Int!
  $withCatalog: Boolean!
) {
  environment(slug: $environmentSlug) {
    applied {
      modelHistoricalRuns(
        uniqueId: $uniqueId
        lastRunCount: $lastRunCount
        withCatalog: $withCatalog
      ) {
        runId
        status
        generatedAt
        compiledCode
        catalog {
          rowCount
          sizeBytes
          columns {
            name
            type
          }
        }
      }
    }
  }
}
```

```json theme={"system"}
{
  "environmentSlug": "production",
  "uniqueId": "model.demo_sales_project.order_items",
  "lastRunCount": 10,
  "withCatalog": true
}
```

### Which nodes depend on this data source?

Lineage begins with your data sources. For a given source, `children` returns the nodes that directly depend on it:

```graphql theme={"system"}
query SourceDependents($environmentSlug: String!, $uniqueIds: [String]) {
  environment(slug: $environmentSlug) {
    applied {
      sources(first: 10, filter: { uniqueIds: $uniqueIds }) {
        edges {
          node {
            sourceName
            name
            loader
            children {
              uniqueId
              name
              resourceType
              database
              schema
            }
          }
        }
      }
    }
  }
}
```

```json theme={"system"}
{
  "environmentSlug": "production",
  "uniqueIds": ["source.demo_sales_project.raw.orders"]
}
```

<Note>
  `children` returns direct dependents (one generation). To walk the full downstream tree, either iterate on each child's `children`, or fetch the whole DAG once with the [`lineage` feed](#whats-the-full-data-lineage-at-a-model-level) and traverse `parentIds` in your own code.
</Note>

## Refresh the catalog

Warehouse catalog metadata (row counts, sizes, column types) is collected periodically. Trigger an on-demand refresh with the `refreshCatalog` mutation. This mutation requires the Catalog Admin capability on your API key.

```graphql theme={"system"}
mutation RefreshCatalog {
  refreshCatalog {
    ok
  }
}
```

## Related docs

* [GraphQL API overview](/developers/graphql-api)
* [Authentication](/developers/graphql-api/authentication)
* [Bolt API](/developers/graphql-api/api-reference/bolt-api)
* [Webhooks](/developers/webhooks)


## Related topics

- [Discovery API: query your dbt metadata over GraphQL](/changelog/2026-08-07/discovery-api.md)
- [API Reference](/developers/graphql-api/api-reference/index.md)
- [Changelog](/changelog/overview.md)
- [Amazon Quick](/products/dino-ai/mcp-server/amazon-quick.md)
- [Translating Key Terms](/guides/migrations/dbt-cloud-tm-importer/translation-of-key-terms.md)
