---
title: Runtime API
description: Runtime API reference of the dltHub Runtime
keywords: [dlthub, runtime, rest api, openapi, runtime-api]
---

<!-- generated from the dltHub Runtime API OpenAPI spec, do not edit -->

REST API of the dltHub Runtime control plane: organizations, workspaces, deployments, configurations, jobs and job runs. The `dlthub` CLI, the Python SDK and the [dltHub web app](https://app.dlthub.com) are built on it.

## Base URL

`https://api.dlthub.com`

## Authentication

Send an API key as a bearer token:

```text
Authorization: Bearer <api key>
```

Create keys in the [dltHub web app](https://app.dlthub.com). The plaintext key is shown once,
and deleting a key revokes it immediately. Keys expire after 90 days unless set otherwise when
created (at most 365).

- **Personal keys** (`dlt_u_…`), under account settings, act as you, with your roles in every
  organization and workspace you belong to.
- **Workspace keys** (`dlt_sa_…`), under workspace settings, act for one workspace with a fixed
  role: DEVELOPER (deploy and run jobs) or VIEWER (read, and run jobs that use the read-only
  `access` profile, such as interactive jobs). Use them for CI and other automation. Workspace
  keys cannot call `GET /api/v1/me`.

## Data plane

Workspace variables, deployment and configuration files, run logs and telemetry are kept on the data plane of the workspace's region and served by the Dataplane, Logs and Telemetry APIs. `GET /api/v1/workspaces/{workspace_id}` returns its address as `dataplane_url`, and `POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token` the token to call it with.

## Versioning

The API version is part of the path. Endpoints under `/v1` are stable: breaking changes ship
only under a new version prefix. New endpoints, optional parameters and response fields can
be added to `/v1` at any time, so ignore fields and enum values you do not know.

## Configurations

### List configurations {#list_configurations}

```text
GET /api/v1/workspaces/{workspace_id}/configurations
```

Lists the configurations of a workspace as a paginated list, newest version first unless `sort`
is given.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

**Query parameters**

| Name | Type | Description |
| --- | --- | --- |
| `version` | integer or null | Exact match on `version`. |
| `sort` | array of string or null | Keys to sort by, applied in the order given. Pairs positionally with `order`, which must have the same number of entries. One of: `version`, `created_at`. |

**Pagination:** `limit`, `offset`, `order`, `cursor`, see [Lists and pagination](overview.md#lists-and-pagination)

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/configurations" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/configurations",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "items": [
    {
      "content_hash": "<content_hash>",
      "created_at": "2026-01-01T00:00:00Z",
      "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "file_count": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "profiles": ["<profiles>"],
      "size_bytes": 0,
      "updated_at": "2026-01-01T00:00:00Z",
      "version": 0,
      "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    }
  ],
  "limit": 0,
  "next_cursor": "<next_cursor>",
  "offset": 0,
  "total": 0
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `items` | array of object | The rows of this page, in sort order. |
| `items[].content_hash` | string | Hash of the package contents: base64 SHA3-256 over each file's path and digest, as dlt computes when building the package; identical file sets give identical hashes. |
| `items[].created_at` | string (date-time) | When it was created. |
| `items[].created_by` | string (uuid) | The `organization_membership_id` of the member who uploaded it. |
| `items[].file_count` | integer | Number of files in the package. |
| `items[].id` | string (uuid) | Unique ID (a UUID). |
| `items[].profiles` | array of string | Profile names found in `<profile>.config.toml` / `<profile>.secrets.toml` file names, sorted. |
| `items[].size_bytes` | integer | Size of the uploaded package in bytes. |
| `items[].updated_at` | string (date-time) | When it was last changed. |
| `items[].version` | integer | Version number within the workspace, starting at 1 and incremented by each upload. |
| `items[].workspace_id` | string (uuid) | The ID of the workspace it belongs to. |
| `limit` | integer | The page size this page was fetched with. |
| `next_cursor` | string or null | Cursor for the page after this one; null on the last. |
| `offset` | integer | How many rows were skipped before this page. |
| `total` | integer | Rows matching the filters, reported up to 10001. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Issue upload configuration {#issue_upload_configuration}

```text
POST /api/v1/workspaces/{workspace_id}/configurations/-issue-upload
```

Prepares the upload of a new configuration. Send a multipart POST with `file` (the package) to
`upload_url`, with `Authorization: Bearer <upload_token>`, within 10 minutes. The configuration
exists, under `configuration_id`, only after the upload succeeds.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/configurations/-issue-upload" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/configurations/-issue-upload",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "configuration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "upload_token": "<upload_token>",
  "upload_url": "<upload_url>"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `configuration_id` | string (uuid) | ID the uploaded configuration will be created under |
| `upload_token` | string | Token for the upload; send it as `Authorization: Bearer <upload_token>`. Valid for 10 minutes. |
| `upload_url` | string | Data plane URL to send the multipart upload POST to. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Get latest configuration {#get_configuration_latest}

```text
GET /api/v1/workspaces/{workspace_id}/configurations/latest
```

Gets the latest and therefore active configuration for a workspace.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/configurations/latest" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/configurations/latest",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "content_hash": "<content_hash>",
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "file_count": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "profiles": ["<profiles>"],
  "size_bytes": 0,
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `content_hash` | string | Hash of the package contents: base64 SHA3-256 over each file's path and digest, as dlt computes when building the package; identical file sets give identical hashes. |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who uploaded it. |
| `file_count` | integer | Number of files in the package. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `profiles` | array of string | Profile names found in `<profile>.config.toml` / `<profile>.secrets.toml` file names, sorted. |
| `size_bytes` | integer | Size of the uploaded package in bytes. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number within the workspace, starting at 1 and incremented by each upload. |
| `workspace_id` | string (uuid) | The ID of the workspace it belongs to. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Get configuration {#get_configuration}

```text
GET /api/v1/workspaces/{workspace_id}/configurations/{configuration_id}
```

Gets a configuration for a workspace by its ID. Find one by version number with `list_configurations`.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `configuration_id` | string (uuid) | ID of the configuration |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/configurations/$CONFIGURATION_ID" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/configurations/<configuration_id>",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "content_hash": "<content_hash>",
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "file_count": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "profiles": ["<profiles>"],
  "size_bytes": 0,
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `content_hash` | string | Hash of the package contents: base64 SHA3-256 over each file's path and digest, as dlt computes when building the package; identical file sets give identical hashes. |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who uploaded it. |
| `file_count` | integer | Number of files in the package. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `profiles` | array of string | Profile names found in `<profile>.config.toml` / `<profile>.secrets.toml` file names, sorted. |
| `size_bytes` | integer | Size of the uploaded package in bytes. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number within the workspace, starting at 1 and incremented by each upload. |
| `workspace_id` | string (uuid) | The ID of the workspace it belongs to. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

## Dataplanes

### List dataplanes {#list_dataplanes}

```text
GET /api/v1/dataplanes
```

Lists the regions (data planes) an organization can choose with `set_region_organization`.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/dataplanes" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/dataplanes",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "items": [
    {
      "description": "<description>",
      "id": "<id>",
      "name": "<name>",
      "region": "<region>"
    }
  ]
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `items` | array of object | The available data planes. |
| `items[].description` | string or null | Optional note about the data plane; `null` when there is none. |
| `items[].id` | string | Data plane id, e.g. `eu1`; also the subdomain of its URL. |
| `items[].name` | string | Human-readable data plane name. |
| `items[].region` | string | Cloud region the data plane runs in, e.g. `europe-west4`. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

## Deployments

### List deployments {#list_deployments}

```text
GET /api/v1/workspaces/{workspace_id}/deployments
```

Lists the deployments of a workspace as a paginated list, newest version first unless `sort`
is given.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

**Query parameters**

| Name | Type | Description |
| --- | --- | --- |
| `version` | integer or null | Exact match on `version`. |
| `sort` | array of string or null | Keys to sort by, applied in the order given. Pairs positionally with `order`, which must have the same number of entries. One of: `version`, `created_at`. |

**Pagination:** `limit`, `offset`, `order`, `cursor`, see [Lists and pagination](overview.md#lists-and-pagination)

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/deployments" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/deployments",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "items": [
    {
      "content_hash": "<content_hash>",
      "created_at": "2026-01-01T00:00:00Z",
      "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "file_count": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "requirements_size_bytes": 0,
      "size_bytes": 0,
      "updated_at": "2026-01-01T00:00:00Z",
      "version": 0,
      "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    }
  ],
  "limit": 0,
  "next_cursor": "<next_cursor>",
  "offset": 0,
  "total": 0
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `items` | array of object | The rows of this page, in sort order. |
| `items[].content_hash` | string | Hash of the package contents: base64 SHA3-256 over each file's path and digest, as dlt computes when building the package; identical file sets give identical hashes. |
| `items[].created_at` | string (date-time) | When it was created. |
| `items[].created_by` | string (uuid) | The `organization_membership_id` of the member who uploaded it. |
| `items[].file_count` | integer | Number of files in the package. |
| `items[].id` | string (uuid) | Unique ID (a UUID). |
| `items[].requirements_size_bytes` | integer | Size of the uploaded requirements file in bytes. |
| `items[].size_bytes` | integer | Size of the uploaded package in bytes. |
| `items[].updated_at` | string (date-time) | When it was last changed. |
| `items[].version` | integer | Version number within the workspace, starting at 1 and incremented by each upload. |
| `items[].workspace_id` | string (uuid) | The ID of the workspace it belongs to. |
| `limit` | integer | The page size this page was fetched with. |
| `next_cursor` | string or null | Cursor for the page after this one; null on the last. |
| `offset` | integer | How many rows were skipped before this page. |
| `total` | integer | Rows matching the filters, reported up to 10001. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Issue upload deployment {#issue_upload_deployment}

```text
POST /api/v1/workspaces/{workspace_id}/deployments/-issue-upload
```

Prepares the upload of a new deployment. Send a multipart POST with `file` (the package) and `requirements_file` to
`upload_url`, with `Authorization: Bearer <upload_token>`, within 10 minutes. The deployment
exists, under `deployment_id`, only after the upload succeeds.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/deployments/-issue-upload" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/deployments/-issue-upload",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "deployment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "upload_token": "<upload_token>",
  "upload_url": "<upload_url>"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `deployment_id` | string (uuid) | ID the uploaded deployment will be created under |
| `upload_token` | string | Token for the upload; send it as `Authorization: Bearer <upload_token>`. Valid for 10 minutes. |
| `upload_url` | string | Data plane URL to send the multipart upload POST to. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Get latest deployment {#get_deployment_latest}

```text
GET /api/v1/workspaces/{workspace_id}/deployments/latest
```

Gets the latest and therefore active deployment for a workspace.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/deployments/latest" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/deployments/latest",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "content_hash": "<content_hash>",
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "file_count": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "requirements_size_bytes": 0,
  "size_bytes": 0,
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `content_hash` | string | Hash of the package contents: base64 SHA3-256 over each file's path and digest, as dlt computes when building the package; identical file sets give identical hashes. |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who uploaded it. |
| `file_count` | integer | Number of files in the package. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `requirements_size_bytes` | integer | Size of the uploaded requirements file in bytes. |
| `size_bytes` | integer | Size of the uploaded package in bytes. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number within the workspace, starting at 1 and incremented by each upload. |
| `workspace_id` | string (uuid) | The ID of the workspace it belongs to. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Get deployment {#get_deployment}

```text
GET /api/v1/workspaces/{workspace_id}/deployments/{deployment_id}
```

Gets a deployment for a workspace by its ID. Find one by version number with `list_deployments`.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `deployment_id` | string (uuid) | ID of the deployment |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/deployments/$DEPLOYMENT_ID" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/deployments/<deployment_id>",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "content_hash": "<content_hash>",
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "file_count": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "requirements_size_bytes": 0,
  "size_bytes": 0,
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `content_hash` | string | Hash of the package contents: base64 SHA3-256 over each file's path and digest, as dlt computes when building the package; identical file sets give identical hashes. |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who uploaded it. |
| `file_count` | integer | Number of files in the package. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `requirements_size_bytes` | integer | Size of the uploaded requirements file in bytes. |
| `size_bytes` | integer | Size of the uploaded package in bytes. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number within the workspace, starting at 1 and incremented by each upload. |
| `workspace_id` | string (uuid) | The ID of the workspace it belongs to. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

## Job runs

### List job runs {#list_job_runs}

```text
GET /api/v1/workspaces/{workspace_id}/job-runs
```

Gets the job runs of a workspace as a paginated list, newest first.

Search with `q` over the job name and the names of the pipelines a run reported; a number
also matches the run number. Filter by status, attention reason, run number, job, job type,
job category, trigger, trigger kind, who started the run (`triggered_by`), profile,
duration, upstream run (`previous_job_run_id`, giving the runs one run triggered), or a
`created_at` window; sort with paired `sort` and `order` lists.

`trigger_kind` matches the kind of trigger a run fired with, which for a run started through
the API may be the job's default trigger, e.g. `schedule:`. Use `triggered_by` to find runs a
person started.

System runs, such as the dashboard job, are excluded unless `job_id` or
`previous_job_run_id` names one, or `include_system_job_runs` is set. Naming a
category does not lift the exclusion.

Set `downstream_job_runs_limit` to attach, to each run, the runs its outcome
triggered.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

**Query parameters**

| Name | Type | Description |
| --- | --- | --- |
| `q` | string or null | Case-insensitive substring match on the job name and the pipeline names the run produced. A numeric value also matches the exact run number. |
| `status` | array of string or null | Exact match on `status`. Repeat the parameter to match any of several values. One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `attention_reason` | array of string or null | Match runs an operator has to look at, for any of the given reasons: `failed`, `empty_run` (completed but loaded no rows), `long_running` (completed after more than 5 hours). A run lists every reason that applies in `attention_reasons`. One of: `failed`, `empty_run`, `long_running`. |
| `number` | integer or null | Exact match on `number`. |
| `job_id` | string (uuid) or null | Exact match on `job_id`. |
| `previous_job_run_id` | string (uuid) or null | Exact match on `previous_job_run_id`. |
| `job_type` | array of string or null | Exact match on `job_type`. Repeat the parameter to match any of several values. One of: `batch`, `interactive`, `stream`. |
| `category` | array of string or null | Match runs of jobs declaring any of the given `expose.category` values. Most jobs declare none and match nothing here. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |
| `exclude_category` | array of string or null | Drop runs of jobs declaring any of the given `expose.category` values. A run of a job declaring none is kept. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |
| `trigger` | array of string or null | Exact match on `trigger`. Repeat the parameter to match any of several values. |
| `trigger_kind` | array of string or null | Match runs by the kind of trigger that fired them, whatever its expression. A run started through the API carries the trigger it was started with, which may be the job's default trigger; use `triggered_by` to find runs a person started. One of: `schedule`, `every`, `once`, `webhook`, `http`, `job.success`, `job.fail`, `manual`. |
| `triggered_by` | array of string (uuid) or null | Exact match on the identity that started the run. Repeat the parameter to match any of several values. |
| `profile` | array of string or null | Exact match on `profile`. Repeat the parameter to match any of several values. |
| `min_duration_seconds` | number or null | Inclusive range over `duration_seconds`, given as `min_duration_seconds` and `max_duration_seconds`. Either bound may stand alone. |
| `max_duration_seconds` | number or null | Inclusive range over `duration_seconds`, given as `min_duration_seconds` and `max_duration_seconds`. Either bound may stand alone. |
| `start` | string (date-time) or null | Lower bound (inclusive) on `created_at`, as a naive local time read in `timezone`, e.g. `2026-03-01T00:00:00`. |
| `end` | string (date-time) or null | Upper bound (exclusive) on `created_at`, as a naive local time read in `timezone`, e.g. `2026-03-11T00:00:00`. |
| `timezone` | string | IANA timezone in which `start` and `end` are read, e.g. `Europe/Berlin`. Default UTC. |
| `sort` | array of string or null | Keys to sort by, applied in the order given. Pairs positionally with `order`, which must have the same number of entries. One of: `created_at`, `number`, `duration_seconds`, `started_at`, `finished_at`. |
| `include_system_job_runs` | boolean | Include runs of system jobs such as the dashboard. |
| `downstream_job_runs_limit` | integer or null | Attach this many of the runs each run triggered, newest first. Only runs started by the run's outcome are linked. |
| `downstream_job_runs_category` | string or null | Attach only downstream runs whose job declares this category, so the newest of that kind is found however many other runs came after it. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |

**Pagination:** `limit`, `offset`, `order`, `cursor`, see [Lists and pagination](overview.md#lists-and-pagination)

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/job-runs" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/job-runs",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "items": [
    {
      "attention_reasons": ["failed"],
      "configuration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "created_at": "2026-01-01T00:00:00Z",
      "deployment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "downstream_job_runs": [
        {
          "category": "pipeline",
          "duration_seconds": 0,
          "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "job_ref": "<job_ref>",
          "name": "<name>",
          "number": 0,
          "started_at": "2026-01-01T00:00:00Z",
          "status": "pending",
          "trigger": "<trigger>"
        }
      ],
      "duration_seconds": 0,
      "finished_at": "2026-01-01T00:00:00Z",
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "interval_end": "2026-01-01T00:00:00Z",
      "interval_start": "2026-01-01T00:00:00Z",
      "job": {
        "created_at": "2026-01-01T00:00:00Z",
        "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "default_trigger": "<default_trigger>",
        "deployment_module": "<deployment_module>",
        "description": "<description>",
        "freshness": ["<freshness>"],
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "interval_end": "2026-01-01T00:00:00Z",
        "interval_start": "2026-01-01T00:00:00Z",
        "is_archived": false,
        "is_paused": false,
        "job_definition": {},
        "job_definition_engine_version": 0,
        "job_definition_hash": "<job_definition_hash>",
        "job_ref": "<job_ref>",
        "job_type": "batch",
        "job_url": "<job_url>",
        "name": "<name>",
        "next_scheduled_run_at": "2026-01-01T00:00:00Z",
        "pipeline_name": "<pipeline_name>",
        "profile": "<profile>",
        "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "public_url": "<public_url>",
        "triggers": ["<triggers>"],
        "updated_at": "2026-01-01T00:00:00Z",
        "version": 0,
        "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      },
      "job_version": {
        "created_at": "2026-01-01T00:00:00Z",
        "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "default_trigger": "<default_trigger>",
        "deployment_module": "<deployment_module>",
        "description": "<description>",
        "freshness": ["<freshness>"],
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "instance_size": "small",
        "interval_end": "2026-01-01T00:00:00Z",
        "interval_start": "2026-01-01T00:00:00Z",
        "job_definition": {},
        "job_definition_engine_version": 0,
        "job_definition_hash": "<job_definition_hash>",
        "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "job_ref": "<job_ref>",
        "job_type": "batch",
        "max_run_time_seconds": 0,
        "memory_mib": 0,
        "name": "<name>",
        "pipeline_name": "<pipeline_name>",
        "profile": "<profile>",
        "provider": "tower",
        "triggers": ["<triggers>"],
        "updated_at": "2026-01-01T00:00:00Z",
        "vcpu": 0,
        "version": 0
      },
      "job_version_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "logs_url": "<logs_url>",
      "number": 0,
      "pipeline_run_summaries": [
        {
          "dataset_name": "<dataset_name>",
          "destination_name": "<destination_name>",
          "duration_ms": 0,
          "finished_at": "2026-01-01T00:00:00Z",
          "pipeline_name": "<pipeline_name>",
          "pipeline_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "started_at": "2026-01-01T00:00:00Z",
          "status": "succeeded",
          "total_rows": 0,
          "transaction_id": "<transaction_id>"
        }
      ],
      "previous_job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "profile": "<profile>",
      "started_at": "2026-01-01T00:00:00Z",
      "status": "pending",
      "trigger": "<trigger>",
      "triggered_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "updated_at": "2026-01-01T00:00:00Z",
      "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    }
  ],
  "limit": 0,
  "next_cursor": "<next_cursor>",
  "offset": 0,
  "total": 0
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `items` | array of object | The rows of this page, in sort order. |
| `items[].attention_reasons` | array of string | Why the run needs an operator's look, every reason that applies in a fixed order: it failed, it completed but loaded no rows, it completed after more than 5 hours. A run still in progress is never flagged. Empty when nothing applies. One of: `failed`, `empty_run`, `long_running`. |
| `items[].configuration_id` | string (uuid) | The ID of the configuration the run uses. |
| `items[].created_at` | string (date-time) | When it was created. |
| `items[].deployment_id` | string (uuid) | The ID of the deployment the run uses. |
| `items[].downstream_job_runs` | array of object or null | Runs this run triggered, newest first. Included only when `downstream_job_runs_limit` is requested; null otherwise. Only runs started by this run's outcome are linked, so a manually started one is absent. |
| `items[].downstream_job_runs[].category` | string or null | The category the job declares in `expose.category`, null when it declares none. Callers that want one kind of downstream run, such as agents, filter on this. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |
| `items[].downstream_job_runs[].duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `items[].downstream_job_runs[].id` | string (uuid) | The run ID |
| `items[].downstream_job_runs[].job_id` | string (uuid) | The ID of the job that ran |
| `items[].downstream_job_runs[].job_ref` | string | The job's canonical reference |
| `items[].downstream_job_runs[].name` | string | The job's display name |
| `items[].downstream_job_runs[].number` | integer | The run number within the job |
| `items[].downstream_job_runs[].started_at` | string (date-time) or null | When the run started, null if not yet started |
| `items[].downstream_job_runs[].status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `items[].downstream_job_runs[].trigger` | string | The trigger that started the downstream run |
| `items[].duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `items[].finished_at` | string (date-time) or null | When the run finished; `null` until it has. |
| `items[].id` | string (uuid) | Unique ID (a UUID). |
| `items[].interval_end` | string (date-time) or null | End of the interval the run processes; `null` for a job without intervals. |
| `items[].interval_start` | string (date-time) or null | Start of the interval the run processes; `null` for a job without intervals. |
| `items[].job` | object | The run's job as it is now; it may have been redeployed since the run. |
| `items[].job.created_at` | string (date-time) | When it was created. |
| `items[].job.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `items[].job.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `items[].job.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `items[].job.description` | string or null | The description of the job |
| `items[].job.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `items[].job.id` | string (uuid) | Unique ID (a UUID). |
| `items[].job.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `items[].job.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `items[].job.is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `items[].job.is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `items[].job.job_definition` | object | Job definition from the dlt deployment manifest. |
| `items[].job.job_definition_engine_version` | integer | Version of the job definition format. |
| `items[].job.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `items[].job.job_ref` | string | Canonical job reference, unique per workspace |
| `items[].job.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `items[].job.job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `items[].job.name` | string or null | Display name for the job |
| `items[].job.next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `items[].job.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `items[].job.profile` | string | The profile the job runs under |
| `items[].job.public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `items[].job.public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `items[].job.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `items[].job.updated_at` | string (date-time) | When it was last changed. |
| `items[].job.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `items[].job.workspace_id` | string (uuid) | The ID of the workspace the job belongs to |
| `items[].job_version` | object | Job version that this run was dispatched with — carries the exact job_definition, provider and max_run_time_seconds used. |
| `items[].job_version.created_at` | string (date-time) | When it was created. |
| `items[].job_version.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `items[].job_version.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `items[].job_version.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `items[].job_version.description` | string or null | The description of the job |
| `items[].job_version.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `items[].job_version.id` | string (uuid) | Unique ID (a UUID). |
| `items[].job_version.instance_size` | string or null | Instance size this version runs on; `null` on a provider without instance sizing. One of: `small`, `medium`, `large`, `xlarge`. |
| `items[].job_version.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `items[].job_version.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `items[].job_version.job_definition` | object | Job definition from the dlt deployment manifest. |
| `items[].job_version.job_definition_engine_version` | integer | Version of the job definition format. |
| `items[].job_version.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `items[].job_version.job_id` | string (uuid) | The ID of the job the job version belongs to |
| `items[].job_version.job_ref` | string | Canonical job reference, unique per workspace |
| `items[].job_version.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `items[].job_version.max_run_time_seconds` | integer | Maximum allowed run duration in seconds for this job version |
| `items[].job_version.memory_mib` | integer or null | Resolved memory in MiB; `null` when the version has no sized hardware. |
| `items[].job_version.name` | string or null | Display name for the job |
| `items[].job_version.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `items[].job_version.profile` | string | The profile the job runs under |
| `items[].job_version.provider` | string | Run provider for runs using this job version (e.g. 'tower', 'modal', 'local') One of: `tower`, `modal`, `local`. |
| `items[].job_version.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `items[].job_version.updated_at` | string (date-time) | When it was last changed. |
| `items[].job_version.vcpu` | number or null | Resolved virtual CPU cores; `null` when the version has no sized hardware. |
| `items[].job_version.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `items[].job_version_id` | string (uuid) | The ID of the job version the run uses. |
| `items[].logs_url` | string or null | A link to the logs of the run |
| `items[].number` | integer | Number of the run within its job, counting up from 1; numbers repeat across jobs. |
| `items[].pipeline_run_summaries` | array of object | One entry per dlt pipeline name the run reported; a pipeline that ran more than once shows its latest report. In no particular order, and empty until telemetry has received the reports. |
| `items[].pipeline_run_summaries[].dataset_name` | string or null | Name of the dataset the pipeline wrote to |
| `items[].pipeline_run_summaries[].destination_name` | string or null | Name of the destination the pipeline wrote to |
| `items[].pipeline_run_summaries[].duration_ms` | integer or null | Duration of the pipeline run in milliseconds |
| `items[].pipeline_run_summaries[].finished_at` | string (date-time) or null | When the pipeline run finished |
| `items[].pipeline_run_summaries[].pipeline_name` | string | Name of the dlt pipeline |
| `items[].pipeline_run_summaries[].pipeline_run_id` | string (uuid) or null | Telemetry ID of the pipeline run |
| `items[].pipeline_run_summaries[].started_at` | string (date-time) or null | When the pipeline run started |
| `items[].pipeline_run_summaries[].status` | string or null | Status of the pipeline run One of: `succeeded`, `failed`. |
| `items[].pipeline_run_summaries[].total_rows` | integer or null | Total number of rows loaded by the pipeline |
| `items[].pipeline_run_summaries[].transaction_id` | string or null | Transaction ID of the pipeline run |
| `items[].previous_job_run_id` | string (uuid) or null | The upstream run whose completion started this one, through a `job.success:` / `job.fail:` trigger or a freshness dependency; `null` for a run started manually, by its own schedule, or by a trigger request. |
| `items[].profile` | string | The name of the profile that was used for the run |
| `items[].started_at` | string (date-time) or null | When the run started; `null` until it has. |
| `items[].status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `items[].trigger` | string | The trigger the run fired with, e.g. `schedule:0 8 * * *`, `manual:jobs.my_job`, `job.success:jobs.upstream`. |
| `items[].triggered_by` | string (uuid) or null | The `organization_membership_id` of the member who started the run; set for any run started through the API, `null` for runs the scheduler, another job or a public URL started. |
| `items[].updated_at` | string (date-time) | When it was last changed. |
| `items[].workspace_id` | string (uuid) | The ID of the workspace the run belongs to |
| `limit` | integer | The page size this page was fetched with. |
| `next_cursor` | string or null | Cursor for the page after this one; null on the last. |
| `offset` | integer | How many rows were skipped before this page. |
| `total` | integer | Rows matching the filters, reported up to 10001. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Create job run {#create_job_run}

```text
POST /api/v1/workspaces/{workspace_id}/job-runs
```

Starts a run of the job's latest version with the workspace's latest deployment and
configuration, under `profile` or the job's own profile. A paused job still runs; pausing
only stops scheduled runs.

`mode` controls run creation:
- `always` (default): always creates a new run (201).
- `when_not_running`: returns the job's pending, starting or running run (200) if it has one,
  otherwise creates a new one (201).

Answers 400 for an archived job, and 404 when the job has no version or the workspace has no
deployment or configuration yet. A run the platform declines to start answers 409 or 403 with
a `code` naming why and the reasons in `extra`: `upstream_not_fresh`, `outside_job_interval`,
`job_concurrency_limit`, `organization_concurrency_limit` (409), `trial_expired`,
`compute_minutes_exhausted` (403), or `run_not_started` for any other reason (409).

Requires the viewer role (or higher) in the workspace. Viewers may only run jobs that use the
read-only `access` profile (by default interactive and agent jobs); any other profile needs
the developer role and answers 403.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="body" label="Body">

| Field | Type | Description |
| --- | --- | --- |
| `job_id` | string (uuid) | **Required.** The ID of the job to run. |
| `mode` | string | How to create a run: `always` creates a new run; `when_not_running` returns the job's pending, starting or running run if it has one, otherwise creates a new one. One of: `always`, `when_not_running`. Default: `"always"`. |
| `profile` | string or null | The profile to run under; defaults to the job's own profile. |
| `refresh` | boolean | Start the run as a refresh: the job's progress goes back to its interval start and the run receives `refresh: true` in its run context, leaving what to reload to the job's code. The progress of the jobs downstream of it is reset too. Ignored when the job's `refresh_propagation` is `block`. Default: `false`. |
| `skip_freshness` | boolean | Start the run even if its upstream jobs are not fresh. When false and an upstream is stale, no run is created and the call answers 409 `upstream_not_fresh`. Default: `false`. |
| `trigger` | string | **Required.** The trigger recorded on the run, e.g. `manual:jobs.my_job`, `schedule:0 8 * * *`. Must be a valid trigger string. |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/job-runs" \
  -H "Authorization: Bearer $DLTHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"job_id": "<job_id>", "trigger": "<trigger>"}'
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/job-runs",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
    json={"job_id": "<job_id>", "trigger": "<trigger>"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code>201</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "attention_reasons": ["failed"],
  "configuration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-01-01T00:00:00Z",
  "deployment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "downstream_job_runs": [
    {
      "category": "pipeline",
      "duration_seconds": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_ref": "<job_ref>",
      "name": "<name>",
      "number": 0,
      "started_at": "2026-01-01T00:00:00Z",
      "status": "pending",
      "trigger": "<trigger>"
    }
  ],
  "duration_seconds": 0,
  "finished_at": "2026-01-01T00:00:00Z",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "interval_end": "2026-01-01T00:00:00Z",
  "interval_start": "2026-01-01T00:00:00Z",
  "job": {
    "created_at": "2026-01-01T00:00:00Z",
    "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "default_trigger": "<default_trigger>",
    "deployment_module": "<deployment_module>",
    "description": "<description>",
    "freshness": ["<freshness>"],
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "interval_end": "2026-01-01T00:00:00Z",
    "interval_start": "2026-01-01T00:00:00Z",
    "is_archived": false,
    "is_paused": false,
    "job_definition": {},
    "job_definition_engine_version": 0,
    "job_definition_hash": "<job_definition_hash>",
    "job_ref": "<job_ref>",
    "job_type": "batch",
    "job_url": "<job_url>",
    "name": "<name>",
    "next_scheduled_run_at": "2026-01-01T00:00:00Z",
    "pipeline_name": "<pipeline_name>",
    "profile": "<profile>",
    "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "public_url": "<public_url>",
    "triggers": ["<triggers>"],
    "updated_at": "2026-01-01T00:00:00Z",
    "version": 0,
    "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  },
  "job_version": {
    "created_at": "2026-01-01T00:00:00Z",
    "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "default_trigger": "<default_trigger>",
    "deployment_module": "<deployment_module>",
    "description": "<description>",
    "freshness": ["<freshness>"],
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "instance_size": "small",
    "interval_end": "2026-01-01T00:00:00Z",
    "interval_start": "2026-01-01T00:00:00Z",
    "job_definition": {},
    "job_definition_engine_version": 0,
    "job_definition_hash": "<job_definition_hash>",
    "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "job_ref": "<job_ref>",
    "job_type": "batch",
    "max_run_time_seconds": 0,
    "memory_mib": 0,
    "name": "<name>",
    "pipeline_name": "<pipeline_name>",
    "profile": "<profile>",
    "provider": "tower",
    "triggers": ["<triggers>"],
    "updated_at": "2026-01-01T00:00:00Z",
    "vcpu": 0,
    "version": 0
  },
  "job_version_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "logs_url": "<logs_url>",
  "number": 0,
  "pipeline_run_summaries": [
    {
      "dataset_name": "<dataset_name>",
      "destination_name": "<destination_name>",
      "duration_ms": 0,
      "finished_at": "2026-01-01T00:00:00Z",
      "pipeline_name": "<pipeline_name>",
      "pipeline_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "started_at": "2026-01-01T00:00:00Z",
      "status": "succeeded",
      "total_rows": 0,
      "transaction_id": "<transaction_id>"
    }
  ],
  "previous_job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "profile": "<profile>",
  "started_at": "2026-01-01T00:00:00Z",
  "status": "pending",
  "trigger": "<trigger>",
  "triggered_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "updated_at": "2026-01-01T00:00:00Z",
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `attention_reasons` | array of string | Why the run needs an operator's look, every reason that applies in a fixed order: it failed, it completed but loaded no rows, it completed after more than 5 hours. A run still in progress is never flagged. Empty when nothing applies. One of: `failed`, `empty_run`, `long_running`. |
| `configuration_id` | string (uuid) | The ID of the configuration the run uses. |
| `created_at` | string (date-time) | When it was created. |
| `deployment_id` | string (uuid) | The ID of the deployment the run uses. |
| `downstream_job_runs` | array of object or null | Runs this run triggered, newest first. Included only when `downstream_job_runs_limit` is requested; null otherwise. Only runs started by this run's outcome are linked, so a manually started one is absent. |
| `downstream_job_runs[].category` | string or null | The category the job declares in `expose.category`, null when it declares none. Callers that want one kind of downstream run, such as agents, filter on this. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |
| `downstream_job_runs[].duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `downstream_job_runs[].id` | string (uuid) | The run ID |
| `downstream_job_runs[].job_id` | string (uuid) | The ID of the job that ran |
| `downstream_job_runs[].job_ref` | string | The job's canonical reference |
| `downstream_job_runs[].name` | string | The job's display name |
| `downstream_job_runs[].number` | integer | The run number within the job |
| `downstream_job_runs[].started_at` | string (date-time) or null | When the run started, null if not yet started |
| `downstream_job_runs[].status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `downstream_job_runs[].trigger` | string | The trigger that started the downstream run |
| `duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `finished_at` | string (date-time) or null | When the run finished; `null` until it has. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `interval_end` | string (date-time) or null | End of the interval the run processes; `null` for a job without intervals. |
| `interval_start` | string (date-time) or null | Start of the interval the run processes; `null` for a job without intervals. |
| `job` | object | The run's job as it is now; it may have been redeployed since the run. |
| `job.created_at` | string (date-time) | When it was created. |
| `job.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `job.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `job.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `job.description` | string or null | The description of the job |
| `job.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `job.id` | string (uuid) | Unique ID (a UUID). |
| `job.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `job.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `job.is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `job.is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job.job_definition` | object | Job definition from the dlt deployment manifest. |
| `job.job_definition_engine_version` | integer | Version of the job definition format. |
| `job.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job.job_ref` | string | Canonical job reference, unique per workspace |
| `job.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job.job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `job.name` | string or null | Display name for the job |
| `job.next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `job.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `job.profile` | string | The profile the job runs under |
| `job.public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `job.public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `job.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `job.updated_at` | string (date-time) | When it was last changed. |
| `job.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `job.workspace_id` | string (uuid) | The ID of the workspace the job belongs to |
| `job_version` | object | Job version that this run was dispatched with — carries the exact job_definition, provider and max_run_time_seconds used. |
| `job_version.created_at` | string (date-time) | When it was created. |
| `job_version.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `job_version.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `job_version.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `job_version.description` | string or null | The description of the job |
| `job_version.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `job_version.id` | string (uuid) | Unique ID (a UUID). |
| `job_version.instance_size` | string or null | Instance size this version runs on; `null` on a provider without instance sizing. One of: `small`, `medium`, `large`, `xlarge`. |
| `job_version.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `job_version.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `job_version.job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_version.job_definition_engine_version` | integer | Version of the job definition format. |
| `job_version.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_version.job_id` | string (uuid) | The ID of the job the job version belongs to |
| `job_version.job_ref` | string | Canonical job reference, unique per workspace |
| `job_version.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_version.max_run_time_seconds` | integer | Maximum allowed run duration in seconds for this job version |
| `job_version.memory_mib` | integer or null | Resolved memory in MiB; `null` when the version has no sized hardware. |
| `job_version.name` | string or null | Display name for the job |
| `job_version.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `job_version.profile` | string | The profile the job runs under |
| `job_version.provider` | string | Run provider for runs using this job version (e.g. 'tower', 'modal', 'local') One of: `tower`, `modal`, `local`. |
| `job_version.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `job_version.updated_at` | string (date-time) | When it was last changed. |
| `job_version.vcpu` | number or null | Resolved virtual CPU cores; `null` when the version has no sized hardware. |
| `job_version.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `job_version_id` | string (uuid) | The ID of the job version the run uses. |
| `logs_url` | string or null | A link to the logs of the run |
| `number` | integer | Number of the run within its job, counting up from 1; numbers repeat across jobs. |
| `pipeline_run_summaries` | array of object | One entry per dlt pipeline name the run reported; a pipeline that ran more than once shows its latest report. In no particular order, and empty until telemetry has received the reports. |
| `pipeline_run_summaries[].dataset_name` | string or null | Name of the dataset the pipeline wrote to |
| `pipeline_run_summaries[].destination_name` | string or null | Name of the destination the pipeline wrote to |
| `pipeline_run_summaries[].duration_ms` | integer or null | Duration of the pipeline run in milliseconds |
| `pipeline_run_summaries[].finished_at` | string (date-time) or null | When the pipeline run finished |
| `pipeline_run_summaries[].pipeline_name` | string | Name of the dlt pipeline |
| `pipeline_run_summaries[].pipeline_run_id` | string (uuid) or null | Telemetry ID of the pipeline run |
| `pipeline_run_summaries[].started_at` | string (date-time) or null | When the pipeline run started |
| `pipeline_run_summaries[].status` | string or null | Status of the pipeline run One of: `succeeded`, `failed`. |
| `pipeline_run_summaries[].total_rows` | integer or null | Total number of rows loaded by the pipeline |
| `pipeline_run_summaries[].transaction_id` | string or null | Transaction ID of the pipeline run |
| `previous_job_run_id` | string (uuid) or null | The upstream run whose completion started this one, through a `job.success:` / `job.fail:` trigger or a freshness dependency; `null` for a run started manually, by its own schedule, or by a trigger request. |
| `profile` | string | The name of the profile that was used for the run |
| `started_at` | string (date-time) or null | When the run started; `null` until it has. |
| `status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `trigger` | string | The trigger the run fired with, e.g. `schedule:0 8 * * *`, `manual:jobs.my_job`, `job.success:jobs.upstream`. |
| `triggered_by` | string (uuid) or null | The `organization_membership_id` of the member who started the run; set for any run started through the API, `null` for runs the scheduler, another job or a public URL started. |
| `updated_at` | string (date-time) | When it was last changed. |
| `workspace_id` | string (uuid) | The ID of the workspace the run belongs to |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Cancel job runs {#cancel_job_runs}

```text
POST /api/v1/workspaces/{workspace_id}/job-runs/-cancel
```

Requests cancellation of the given runs in a single transaction. Each run moves to
`cancelling` and then to `cancelled` once stopped.

Answers a receipt: `succeeded` runs were moved to `cancelling`, `skipped` runs were already
finished or already being cancelled, and `failed` lists ids that name no run in the
workspace. Use `dry_run=true` to preview without cancelling.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="body" label="Body">

| Field | Type | Description |
| --- | --- | --- |
| `dry_run` | boolean | Preview which runs would be cancelled, without cancelling them. Default: `false`. |
| `job_run_ids` | array of string (uuid) | **Required.** Runs to cancel; a run that is already finished or being cancelled is skipped. |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/job-runs/-cancel" \
  -H "Authorization: Bearer $DLTHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"job_run_ids": ["<job_run_ids>"]}'
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/job-runs/-cancel",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
    json={"job_run_ids": ["<job_run_ids>"]},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "failed": [
    {
      "detail": "<detail>",
      "ref": "<ref>"
    }
  ],
  "skipped": [
    {
      "job_ref": "<job_ref>",
      "job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_run_number": 0,
      "previous_status": "pending"
    }
  ],
  "succeeded": [
    {
      "job_ref": "<job_ref>",
      "job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_run_number": 0,
      "previous_status": "pending"
    }
  ]
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `failed` | array of object | Requested runs that do not exist in the workspace. |
| `failed[].detail` | string | Why it failed. |
| `failed[].ref` | string | The item that failed, by its id or natural key. |
| `skipped` | array of object | Runs already finished or already being cancelled. |
| `skipped[].job_ref` | string | Job reference of the cancelled run. |
| `skipped[].job_run_id` | string (uuid) | ID of the cancelled run. |
| `skipped[].job_run_number` | integer | Number of the run within its job; numbers repeat across jobs. |
| `skipped[].previous_status` | string | Status of the run when the cancel request arrived. One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `succeeded` | array of object | Runs moved to `cancelling` (or that would be, in dry-run). |
| `succeeded[].job_ref` | string | Job reference of the cancelled run. |
| `succeeded[].job_run_id` | string (uuid) | ID of the cancelled run. |
| `succeeded[].job_run_number` | integer | Number of the run within its job; numbers repeat across jobs. |
| `succeeded[].previous_status` | string | Status of the run when the cancel request arrived. One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Get job run {#get_job_run}

```text
GET /api/v1/workspaces/{workspace_id}/job-runs/{job_run_id}
```

Gets a run for a workspace by ID.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `job_run_id` | string (uuid) | ID of the job run |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/job-runs/$JOB_RUN_ID" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/job-runs/<job_run_id>",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "attention_reasons": ["failed"],
  "configuration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-01-01T00:00:00Z",
  "deployment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "downstream_job_runs": [
    {
      "category": "pipeline",
      "duration_seconds": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_ref": "<job_ref>",
      "name": "<name>",
      "number": 0,
      "started_at": "2026-01-01T00:00:00Z",
      "status": "pending",
      "trigger": "<trigger>"
    }
  ],
  "duration_seconds": 0,
  "finished_at": "2026-01-01T00:00:00Z",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "interval_end": "2026-01-01T00:00:00Z",
  "interval_start": "2026-01-01T00:00:00Z",
  "job": {
    "created_at": "2026-01-01T00:00:00Z",
    "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "default_trigger": "<default_trigger>",
    "deployment_module": "<deployment_module>",
    "description": "<description>",
    "freshness": ["<freshness>"],
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "interval_end": "2026-01-01T00:00:00Z",
    "interval_start": "2026-01-01T00:00:00Z",
    "is_archived": false,
    "is_paused": false,
    "job_definition": {},
    "job_definition_engine_version": 0,
    "job_definition_hash": "<job_definition_hash>",
    "job_ref": "<job_ref>",
    "job_type": "batch",
    "job_url": "<job_url>",
    "name": "<name>",
    "next_scheduled_run_at": "2026-01-01T00:00:00Z",
    "pipeline_name": "<pipeline_name>",
    "profile": "<profile>",
    "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "public_url": "<public_url>",
    "triggers": ["<triggers>"],
    "updated_at": "2026-01-01T00:00:00Z",
    "version": 0,
    "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  },
  "job_version": {
    "created_at": "2026-01-01T00:00:00Z",
    "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "default_trigger": "<default_trigger>",
    "deployment_module": "<deployment_module>",
    "description": "<description>",
    "freshness": ["<freshness>"],
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "instance_size": "small",
    "interval_end": "2026-01-01T00:00:00Z",
    "interval_start": "2026-01-01T00:00:00Z",
    "job_definition": {},
    "job_definition_engine_version": 0,
    "job_definition_hash": "<job_definition_hash>",
    "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "job_ref": "<job_ref>",
    "job_type": "batch",
    "max_run_time_seconds": 0,
    "memory_mib": 0,
    "name": "<name>",
    "pipeline_name": "<pipeline_name>",
    "profile": "<profile>",
    "provider": "tower",
    "triggers": ["<triggers>"],
    "updated_at": "2026-01-01T00:00:00Z",
    "vcpu": 0,
    "version": 0
  },
  "job_version_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "logs_url": "<logs_url>",
  "number": 0,
  "pipeline_run_summaries": [
    {
      "dataset_name": "<dataset_name>",
      "destination_name": "<destination_name>",
      "duration_ms": 0,
      "finished_at": "2026-01-01T00:00:00Z",
      "pipeline_name": "<pipeline_name>",
      "pipeline_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "started_at": "2026-01-01T00:00:00Z",
      "status": "succeeded",
      "total_rows": 0,
      "transaction_id": "<transaction_id>"
    }
  ],
  "previous_job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "profile": "<profile>",
  "started_at": "2026-01-01T00:00:00Z",
  "status": "pending",
  "trigger": "<trigger>",
  "triggered_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "updated_at": "2026-01-01T00:00:00Z",
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `attention_reasons` | array of string | Why the run needs an operator's look, every reason that applies in a fixed order: it failed, it completed but loaded no rows, it completed after more than 5 hours. A run still in progress is never flagged. Empty when nothing applies. One of: `failed`, `empty_run`, `long_running`. |
| `configuration_id` | string (uuid) | The ID of the configuration the run uses. |
| `created_at` | string (date-time) | When it was created. |
| `deployment_id` | string (uuid) | The ID of the deployment the run uses. |
| `downstream_job_runs` | array of object or null | Runs this run triggered, newest first. Included only when `downstream_job_runs_limit` is requested; null otherwise. Only runs started by this run's outcome are linked, so a manually started one is absent. |
| `downstream_job_runs[].category` | string or null | The category the job declares in `expose.category`, null when it declares none. Callers that want one kind of downstream run, such as agents, filter on this. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |
| `downstream_job_runs[].duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `downstream_job_runs[].id` | string (uuid) | The run ID |
| `downstream_job_runs[].job_id` | string (uuid) | The ID of the job that ran |
| `downstream_job_runs[].job_ref` | string | The job's canonical reference |
| `downstream_job_runs[].name` | string | The job's display name |
| `downstream_job_runs[].number` | integer | The run number within the job |
| `downstream_job_runs[].started_at` | string (date-time) or null | When the run started, null if not yet started |
| `downstream_job_runs[].status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `downstream_job_runs[].trigger` | string | The trigger that started the downstream run |
| `duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `finished_at` | string (date-time) or null | When the run finished; `null` until it has. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `interval_end` | string (date-time) or null | End of the interval the run processes; `null` for a job without intervals. |
| `interval_start` | string (date-time) or null | Start of the interval the run processes; `null` for a job without intervals. |
| `job` | object | The run's job as it is now; it may have been redeployed since the run. |
| `job.created_at` | string (date-time) | When it was created. |
| `job.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `job.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `job.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `job.description` | string or null | The description of the job |
| `job.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `job.id` | string (uuid) | Unique ID (a UUID). |
| `job.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `job.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `job.is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `job.is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job.job_definition` | object | Job definition from the dlt deployment manifest. |
| `job.job_definition_engine_version` | integer | Version of the job definition format. |
| `job.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job.job_ref` | string | Canonical job reference, unique per workspace |
| `job.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job.job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `job.name` | string or null | Display name for the job |
| `job.next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `job.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `job.profile` | string | The profile the job runs under |
| `job.public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `job.public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `job.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `job.updated_at` | string (date-time) | When it was last changed. |
| `job.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `job.workspace_id` | string (uuid) | The ID of the workspace the job belongs to |
| `job_version` | object | Job version that this run was dispatched with — carries the exact job_definition, provider and max_run_time_seconds used. |
| `job_version.created_at` | string (date-time) | When it was created. |
| `job_version.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `job_version.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `job_version.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `job_version.description` | string or null | The description of the job |
| `job_version.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `job_version.id` | string (uuid) | Unique ID (a UUID). |
| `job_version.instance_size` | string or null | Instance size this version runs on; `null` on a provider without instance sizing. One of: `small`, `medium`, `large`, `xlarge`. |
| `job_version.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `job_version.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `job_version.job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_version.job_definition_engine_version` | integer | Version of the job definition format. |
| `job_version.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_version.job_id` | string (uuid) | The ID of the job the job version belongs to |
| `job_version.job_ref` | string | Canonical job reference, unique per workspace |
| `job_version.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_version.max_run_time_seconds` | integer | Maximum allowed run duration in seconds for this job version |
| `job_version.memory_mib` | integer or null | Resolved memory in MiB; `null` when the version has no sized hardware. |
| `job_version.name` | string or null | Display name for the job |
| `job_version.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `job_version.profile` | string | The profile the job runs under |
| `job_version.provider` | string | Run provider for runs using this job version (e.g. 'tower', 'modal', 'local') One of: `tower`, `modal`, `local`. |
| `job_version.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `job_version.updated_at` | string (date-time) | When it was last changed. |
| `job_version.vcpu` | number or null | Resolved virtual CPU cores; `null` when the version has no sized hardware. |
| `job_version.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `job_version_id` | string (uuid) | The ID of the job version the run uses. |
| `logs_url` | string or null | A link to the logs of the run |
| `number` | integer | Number of the run within its job, counting up from 1; numbers repeat across jobs. |
| `pipeline_run_summaries` | array of object | One entry per dlt pipeline name the run reported; a pipeline that ran more than once shows its latest report. In no particular order, and empty until telemetry has received the reports. |
| `pipeline_run_summaries[].dataset_name` | string or null | Name of the dataset the pipeline wrote to |
| `pipeline_run_summaries[].destination_name` | string or null | Name of the destination the pipeline wrote to |
| `pipeline_run_summaries[].duration_ms` | integer or null | Duration of the pipeline run in milliseconds |
| `pipeline_run_summaries[].finished_at` | string (date-time) or null | When the pipeline run finished |
| `pipeline_run_summaries[].pipeline_name` | string | Name of the dlt pipeline |
| `pipeline_run_summaries[].pipeline_run_id` | string (uuid) or null | Telemetry ID of the pipeline run |
| `pipeline_run_summaries[].started_at` | string (date-time) or null | When the pipeline run started |
| `pipeline_run_summaries[].status` | string or null | Status of the pipeline run One of: `succeeded`, `failed`. |
| `pipeline_run_summaries[].total_rows` | integer or null | Total number of rows loaded by the pipeline |
| `pipeline_run_summaries[].transaction_id` | string or null | Transaction ID of the pipeline run |
| `previous_job_run_id` | string (uuid) or null | The upstream run whose completion started this one, through a `job.success:` / `job.fail:` trigger or a freshness dependency; `null` for a run started manually, by its own schedule, or by a trigger request. |
| `profile` | string | The name of the profile that was used for the run |
| `started_at` | string (date-time) or null | When the run started; `null` until it has. |
| `status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `trigger` | string | The trigger the run fired with, e.g. `schedule:0 8 * * *`, `manual:jobs.my_job`, `job.success:jobs.upstream`. |
| `triggered_by` | string (uuid) or null | The `organization_membership_id` of the member who started the run; set for any run started through the API, `null` for runs the scheduler, another job or a public URL started. |
| `updated_at` | string (date-time) | When it was last changed. |
| `workspace_id` | string (uuid) | The ID of the workspace the run belongs to |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Cancel job run {#cancel_job_run}

```text
POST /api/v1/workspaces/{workspace_id}/job-runs/{job_run_id}/-cancel
```

Requests cancellation of a run. The run moves to `cancelling` and then to `cancelled` once
stopped. A run that is already finished or cancelling answers 400.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `job_run_id` | string (uuid) | ID of the job run |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/job-runs/$JOB_RUN_ID/-cancel" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/job-runs/<job_run_id>/-cancel",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "attention_reasons": ["failed"],
  "configuration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-01-01T00:00:00Z",
  "deployment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "downstream_job_runs": [
    {
      "category": "pipeline",
      "duration_seconds": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_ref": "<job_ref>",
      "name": "<name>",
      "number": 0,
      "started_at": "2026-01-01T00:00:00Z",
      "status": "pending",
      "trigger": "<trigger>"
    }
  ],
  "duration_seconds": 0,
  "finished_at": "2026-01-01T00:00:00Z",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "interval_end": "2026-01-01T00:00:00Z",
  "interval_start": "2026-01-01T00:00:00Z",
  "job": {
    "created_at": "2026-01-01T00:00:00Z",
    "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "default_trigger": "<default_trigger>",
    "deployment_module": "<deployment_module>",
    "description": "<description>",
    "freshness": ["<freshness>"],
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "interval_end": "2026-01-01T00:00:00Z",
    "interval_start": "2026-01-01T00:00:00Z",
    "is_archived": false,
    "is_paused": false,
    "job_definition": {},
    "job_definition_engine_version": 0,
    "job_definition_hash": "<job_definition_hash>",
    "job_ref": "<job_ref>",
    "job_type": "batch",
    "job_url": "<job_url>",
    "name": "<name>",
    "next_scheduled_run_at": "2026-01-01T00:00:00Z",
    "pipeline_name": "<pipeline_name>",
    "profile": "<profile>",
    "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "public_url": "<public_url>",
    "triggers": ["<triggers>"],
    "updated_at": "2026-01-01T00:00:00Z",
    "version": 0,
    "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  },
  "job_version": {
    "created_at": "2026-01-01T00:00:00Z",
    "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "default_trigger": "<default_trigger>",
    "deployment_module": "<deployment_module>",
    "description": "<description>",
    "freshness": ["<freshness>"],
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "instance_size": "small",
    "interval_end": "2026-01-01T00:00:00Z",
    "interval_start": "2026-01-01T00:00:00Z",
    "job_definition": {},
    "job_definition_engine_version": 0,
    "job_definition_hash": "<job_definition_hash>",
    "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "job_ref": "<job_ref>",
    "job_type": "batch",
    "max_run_time_seconds": 0,
    "memory_mib": 0,
    "name": "<name>",
    "pipeline_name": "<pipeline_name>",
    "profile": "<profile>",
    "provider": "tower",
    "triggers": ["<triggers>"],
    "updated_at": "2026-01-01T00:00:00Z",
    "vcpu": 0,
    "version": 0
  },
  "job_version_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "logs_url": "<logs_url>",
  "number": 0,
  "pipeline_run_summaries": [
    {
      "dataset_name": "<dataset_name>",
      "destination_name": "<destination_name>",
      "duration_ms": 0,
      "finished_at": "2026-01-01T00:00:00Z",
      "pipeline_name": "<pipeline_name>",
      "pipeline_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "started_at": "2026-01-01T00:00:00Z",
      "status": "succeeded",
      "total_rows": 0,
      "transaction_id": "<transaction_id>"
    }
  ],
  "previous_job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "profile": "<profile>",
  "started_at": "2026-01-01T00:00:00Z",
  "status": "pending",
  "trigger": "<trigger>",
  "triggered_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "updated_at": "2026-01-01T00:00:00Z",
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `attention_reasons` | array of string | Why the run needs an operator's look, every reason that applies in a fixed order: it failed, it completed but loaded no rows, it completed after more than 5 hours. A run still in progress is never flagged. Empty when nothing applies. One of: `failed`, `empty_run`, `long_running`. |
| `configuration_id` | string (uuid) | The ID of the configuration the run uses. |
| `created_at` | string (date-time) | When it was created. |
| `deployment_id` | string (uuid) | The ID of the deployment the run uses. |
| `downstream_job_runs` | array of object or null | Runs this run triggered, newest first. Included only when `downstream_job_runs_limit` is requested; null otherwise. Only runs started by this run's outcome are linked, so a manually started one is absent. |
| `downstream_job_runs[].category` | string or null | The category the job declares in `expose.category`, null when it declares none. Callers that want one kind of downstream run, such as agents, filter on this. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |
| `downstream_job_runs[].duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `downstream_job_runs[].id` | string (uuid) | The run ID |
| `downstream_job_runs[].job_id` | string (uuid) | The ID of the job that ran |
| `downstream_job_runs[].job_ref` | string | The job's canonical reference |
| `downstream_job_runs[].name` | string | The job's display name |
| `downstream_job_runs[].number` | integer | The run number within the job |
| `downstream_job_runs[].started_at` | string (date-time) or null | When the run started, null if not yet started |
| `downstream_job_runs[].status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `downstream_job_runs[].trigger` | string | The trigger that started the downstream run |
| `duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `finished_at` | string (date-time) or null | When the run finished; `null` until it has. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `interval_end` | string (date-time) or null | End of the interval the run processes; `null` for a job without intervals. |
| `interval_start` | string (date-time) or null | Start of the interval the run processes; `null` for a job without intervals. |
| `job` | object | The run's job as it is now; it may have been redeployed since the run. |
| `job.created_at` | string (date-time) | When it was created. |
| `job.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `job.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `job.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `job.description` | string or null | The description of the job |
| `job.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `job.id` | string (uuid) | Unique ID (a UUID). |
| `job.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `job.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `job.is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `job.is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job.job_definition` | object | Job definition from the dlt deployment manifest. |
| `job.job_definition_engine_version` | integer | Version of the job definition format. |
| `job.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job.job_ref` | string | Canonical job reference, unique per workspace |
| `job.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job.job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `job.name` | string or null | Display name for the job |
| `job.next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `job.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `job.profile` | string | The profile the job runs under |
| `job.public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `job.public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `job.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `job.updated_at` | string (date-time) | When it was last changed. |
| `job.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `job.workspace_id` | string (uuid) | The ID of the workspace the job belongs to |
| `job_version` | object | Job version that this run was dispatched with — carries the exact job_definition, provider and max_run_time_seconds used. |
| `job_version.created_at` | string (date-time) | When it was created. |
| `job_version.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `job_version.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `job_version.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `job_version.description` | string or null | The description of the job |
| `job_version.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `job_version.id` | string (uuid) | Unique ID (a UUID). |
| `job_version.instance_size` | string or null | Instance size this version runs on; `null` on a provider without instance sizing. One of: `small`, `medium`, `large`, `xlarge`. |
| `job_version.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `job_version.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `job_version.job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_version.job_definition_engine_version` | integer | Version of the job definition format. |
| `job_version.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_version.job_id` | string (uuid) | The ID of the job the job version belongs to |
| `job_version.job_ref` | string | Canonical job reference, unique per workspace |
| `job_version.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_version.max_run_time_seconds` | integer | Maximum allowed run duration in seconds for this job version |
| `job_version.memory_mib` | integer or null | Resolved memory in MiB; `null` when the version has no sized hardware. |
| `job_version.name` | string or null | Display name for the job |
| `job_version.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `job_version.profile` | string | The profile the job runs under |
| `job_version.provider` | string | Run provider for runs using this job version (e.g. 'tower', 'modal', 'local') One of: `tower`, `modal`, `local`. |
| `job_version.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `job_version.updated_at` | string (date-time) | When it was last changed. |
| `job_version.vcpu` | number or null | Resolved virtual CPU cores; `null` when the version has no sized hardware. |
| `job_version.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `job_version_id` | string (uuid) | The ID of the job version the run uses. |
| `logs_url` | string or null | A link to the logs of the run |
| `number` | integer | Number of the run within its job, counting up from 1; numbers repeat across jobs. |
| `pipeline_run_summaries` | array of object | One entry per dlt pipeline name the run reported; a pipeline that ran more than once shows its latest report. In no particular order, and empty until telemetry has received the reports. |
| `pipeline_run_summaries[].dataset_name` | string or null | Name of the dataset the pipeline wrote to |
| `pipeline_run_summaries[].destination_name` | string or null | Name of the destination the pipeline wrote to |
| `pipeline_run_summaries[].duration_ms` | integer or null | Duration of the pipeline run in milliseconds |
| `pipeline_run_summaries[].finished_at` | string (date-time) or null | When the pipeline run finished |
| `pipeline_run_summaries[].pipeline_name` | string | Name of the dlt pipeline |
| `pipeline_run_summaries[].pipeline_run_id` | string (uuid) or null | Telemetry ID of the pipeline run |
| `pipeline_run_summaries[].started_at` | string (date-time) or null | When the pipeline run started |
| `pipeline_run_summaries[].status` | string or null | Status of the pipeline run One of: `succeeded`, `failed`. |
| `pipeline_run_summaries[].total_rows` | integer or null | Total number of rows loaded by the pipeline |
| `pipeline_run_summaries[].transaction_id` | string or null | Transaction ID of the pipeline run |
| `previous_job_run_id` | string (uuid) or null | The upstream run whose completion started this one, through a `job.success:` / `job.fail:` trigger or a freshness dependency; `null` for a run started manually, by its own schedule, or by a trigger request. |
| `profile` | string | The name of the profile that was used for the run |
| `started_at` | string (date-time) or null | When the run started; `null` until it has. |
| `status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `trigger` | string | The trigger the run fired with, e.g. `schedule:0 8 * * *`, `manual:jobs.my_job`, `job.success:jobs.upstream`. |
| `triggered_by` | string (uuid) or null | The `organization_membership_id` of the member who started the run; set for any run started through the API, `null` for runs the scheduler, another job or a public URL started. |
| `updated_at` | string (date-time) | When it was last changed. |
| `workspace_id` | string (uuid) | The ID of the workspace the run belongs to |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

## Jobs

### List jobs {#list_jobs}

```text
GET /api/v1/workspaces/{workspace_id}/jobs
```

Gets the jobs of a workspace as a paginated list, most recent run first; jobs that never
ran come last.

Search with `q` over the job name; filter by `ref`, type, archived or paused state, profile,
pipeline, the status of the latest run, trigger kind, category, tags (given without the
`tag:` prefix) or a `created_at` window; sort with paired `sort` and `order` lists.

Archived and live jobs are both listed unless `is_archived` says which to keep.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

**Query parameters**

| Name | Type | Description |
| --- | --- | --- |
| `q` | string or null | Case-insensitive substring match on `name`. |
| `ref` | string or null | Exact match on `job_ref`. |
| `job_type` | array of string or null | Exact match on `job_type`. Repeat the parameter to match any of several values. One of: `batch`, `interactive`, `stream`. |
| `is_archived` | boolean or null | Match `is_archived`. Omit to list both. |
| `is_paused` | boolean or null | Match `is_paused`. Omit to list both. |
| `profile` | array of string or null | Exact match on `profile`. Repeat the parameter to match any of several values. |
| `pipeline_name` | array of string or null | Exact match on `pipeline_name`. Repeat the parameter to match any of several values. |
| `status` | array of string or null | Exact match on the status of the job's most recent run. Repeat the parameter to match any of several values. One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `trigger_kind` | array of string or null | Match jobs declaring a trigger of any given kind. A job may declare several kinds at once; `manual` reflects the job's `expose.manual` setting, which is on by default. One of: `schedule`, `every`, `once`, `webhook`, `http`, `job.success`, `job.fail`, `manual`. |
| `is_self_firing` | boolean or null | Match jobs by whether they declare a trigger that starts them with no person involved: a schedule, an interval, a one-off time, a webhook, an HTTP endpoint, or another job's outcome. |
| `category` | array of string or null | Match jobs declaring any of the given `expose.category` values. Most jobs declare none and match nothing here. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |
| `exclude_category` | array of string or null | Drop jobs declaring any of the given `expose.category` values. A job declaring none is kept. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |
| `tags` | array of string or null | Match jobs carrying any of the given tags. |
| `start` | string (date-time) or null | Lower bound (inclusive) on `created_at`, as a naive local time read in `timezone`, e.g. `2026-03-01T00:00:00`. |
| `end` | string (date-time) or null | Upper bound (exclusive) on `created_at`, as a naive local time read in `timezone`, e.g. `2026-03-11T00:00:00`. |
| `timezone` | string | IANA timezone in which `start` and `end` are read, e.g. `Europe/Berlin`. Default UTC. |
| `sort` | array of string or null | Keys to sort by, applied in the order given. Pairs positionally with `order`, which must have the same number of entries. One of: `last_run_at`, `last_run_ended_at`, `name`, `next_scheduled_run_at`, `created_at`. |
| `recent_job_runs_limit` | integer or null | Attach this many of the job's most recent runs. |
| `downstream_job_runs_limit` | integer or null | Attach this many of the runs each run triggered, newest first. Only runs started by the run's outcome are linked. |
| `downstream_job_runs_category` | string or null | Attach only downstream runs whose job declares this category, so the newest of that kind is found however many other runs came after it. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |

**Pagination:** `limit`, `offset`, `order`, `cursor`, see [Lists and pagination](overview.md#lists-and-pagination)

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/jobs" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/jobs",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "items": [
    {
      "created_at": "2026-01-01T00:00:00Z",
      "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "current_version": {
        "created_at": "2026-01-01T00:00:00Z",
        "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "default_trigger": "<default_trigger>",
        "deployment_module": "<deployment_module>",
        "description": "<description>",
        "freshness": ["<freshness>"],
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "instance_size": "small",
        "interval_end": "2026-01-01T00:00:00Z",
        "interval_start": "2026-01-01T00:00:00Z",
        "job_definition": {},
        "job_definition_engine_version": 0,
        "job_definition_hash": "<job_definition_hash>",
        "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "job_ref": "<job_ref>",
        "job_type": "batch",
        "max_run_time_seconds": 0,
        "memory_mib": 0,
        "name": "<name>",
        "pipeline_name": "<pipeline_name>",
        "profile": "<profile>",
        "provider": "tower",
        "triggers": ["<triggers>"],
        "updated_at": "2026-01-01T00:00:00Z",
        "vcpu": 0,
        "version": 0
      },
      "default_trigger": "<default_trigger>",
      "deployment_module": "<deployment_module>",
      "description": "<description>",
      "downstream_job_runs": [
        {
          "category": "pipeline",
          "duration_seconds": 0,
          "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "job_ref": "<job_ref>",
          "name": "<name>",
          "number": 0,
          "started_at": "2026-01-01T00:00:00Z",
          "status": "pending",
          "trigger": "<trigger>"
        }
      ],
      "freshness": ["<freshness>"],
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "interval_end": "2026-01-01T00:00:00Z",
      "interval_start": "2026-01-01T00:00:00Z",
      "is_archived": false,
      "is_paused": false,
      "job_definition": {},
      "job_definition_engine_version": 0,
      "job_definition_hash": "<job_definition_hash>",
      "job_ref": "<job_ref>",
      "job_type": "batch",
      "job_url": "<job_url>",
      "latest_job_run": {
        "configuration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "created_at": "2026-01-01T00:00:00Z",
        "deployment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "duration_seconds": 0,
        "finished_at": "2026-01-01T00:00:00Z",
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "interval_end": "2026-01-01T00:00:00Z",
        "interval_start": "2026-01-01T00:00:00Z",
        "job_version_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "logs_url": "<logs_url>",
        "number": 0,
        "pipeline_run_summaries": [],
        "previous_job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "profile": "<profile>",
        "started_at": "2026-01-01T00:00:00Z",
        "status": "pending",
        "trigger": "<trigger>",
        "triggered_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "updated_at": "2026-01-01T00:00:00Z",
        "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      },
      "name": "<name>",
      "next_scheduled_run_at": "2026-01-01T00:00:00Z",
      "pipeline_name": "<pipeline_name>",
      "profile": "<profile>",
      "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "public_url": "<public_url>",
      "recent_job_runs": [
        {
          "duration_seconds": 0,
          "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "rows_loaded": 0,
          "started_at": "2026-01-01T00:00:00Z",
          "status": "pending"
        }
      ],
      "triggers": ["<triggers>"],
      "updated_at": "2026-01-01T00:00:00Z",
      "version": 0,
      "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    }
  ],
  "limit": 0,
  "next_cursor": "<next_cursor>",
  "offset": 0,
  "total": 0
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `items` | array of object | The rows of this page, in sort order. |
| `items[].created_at` | string (date-time) | When it was created. |
| `items[].created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `items[].current_version` | object or null | The active job version, carrying its provider, instance size, and resolved hardware. |
| `items[].current_version.created_at` | string (date-time) | When it was created. |
| `items[].current_version.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `items[].current_version.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `items[].current_version.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `items[].current_version.description` | string or null | The description of the job |
| `items[].current_version.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `items[].current_version.id` | string (uuid) | Unique ID (a UUID). |
| `items[].current_version.instance_size` | string or null | Instance size this version runs on; `null` on a provider without instance sizing. One of: `small`, `medium`, `large`, `xlarge`. |
| `items[].current_version.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `items[].current_version.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `items[].current_version.job_definition` | object | Job definition from the dlt deployment manifest. |
| `items[].current_version.job_definition_engine_version` | integer | Version of the job definition format. |
| `items[].current_version.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `items[].current_version.job_id` | string (uuid) | The ID of the job the job version belongs to |
| `items[].current_version.job_ref` | string | Canonical job reference, unique per workspace |
| `items[].current_version.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `items[].current_version.max_run_time_seconds` | integer | Maximum allowed run duration in seconds for this job version |
| `items[].current_version.memory_mib` | integer or null | Resolved memory in MiB; `null` when the version has no sized hardware. |
| `items[].current_version.name` | string or null | Display name for the job |
| `items[].current_version.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `items[].current_version.profile` | string | The profile the job runs under |
| `items[].current_version.provider` | string | Run provider for runs using this job version (e.g. 'tower', 'modal', 'local') One of: `tower`, `modal`, `local`. |
| `items[].current_version.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `items[].current_version.updated_at` | string (date-time) | When it was last changed. |
| `items[].current_version.vcpu` | number or null | Resolved virtual CPU cores; `null` when the version has no sized hardware. |
| `items[].current_version.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `items[].default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `items[].deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `items[].description` | string or null | The description of the job |
| `items[].downstream_job_runs` | array of object or null | Runs the job's last run triggered, newest first. Included only when `downstream_job_runs_limit` is requested; null otherwise. Empty when the last run triggered nothing, since a chained job fires only once its upstream run reaches a terminal state. |
| `items[].downstream_job_runs[].category` | string or null | The category the job declares in `expose.category`, null when it declares none. Callers that want one kind of downstream run, such as agents, filter on this. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |
| `items[].downstream_job_runs[].duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `items[].downstream_job_runs[].id` | string (uuid) | The run ID |
| `items[].downstream_job_runs[].job_id` | string (uuid) | The ID of the job that ran |
| `items[].downstream_job_runs[].job_ref` | string | The job's canonical reference |
| `items[].downstream_job_runs[].name` | string | The job's display name |
| `items[].downstream_job_runs[].number` | integer | The run number within the job |
| `items[].downstream_job_runs[].started_at` | string (date-time) or null | When the run started, null if not yet started |
| `items[].downstream_job_runs[].status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `items[].downstream_job_runs[].trigger` | string | The trigger that started the downstream run |
| `items[].freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `items[].id` | string (uuid) | Unique ID (a UUID). |
| `items[].interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `items[].interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `items[].is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `items[].is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `items[].job_definition` | object | Job definition from the dlt deployment manifest. |
| `items[].job_definition_engine_version` | integer | Version of the job definition format. |
| `items[].job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `items[].job_ref` | string | Canonical job reference, unique per workspace |
| `items[].job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `items[].job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `items[].latest_job_run` | object or null | The latest run of the job; `null` if it has never run. |
| `items[].latest_job_run.configuration_id` | string (uuid) | The ID of the configuration the run uses. |
| `items[].latest_job_run.created_at` | string (date-time) | When it was created. |
| `items[].latest_job_run.deployment_id` | string (uuid) | The ID of the deployment the run uses. |
| `items[].latest_job_run.duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `items[].latest_job_run.finished_at` | string (date-time) or null | When the run finished; `null` until it has. |
| `items[].latest_job_run.id` | string (uuid) | Unique ID (a UUID). |
| `items[].latest_job_run.interval_end` | string (date-time) or null | End of the interval the run processes; `null` for a job without intervals. |
| `items[].latest_job_run.interval_start` | string (date-time) or null | Start of the interval the run processes; `null` for a job without intervals. |
| `items[].latest_job_run.job_version_id` | string (uuid) | The ID of the job version the run uses. |
| `items[].latest_job_run.logs_url` | string or null | A link to the logs of the run |
| `items[].latest_job_run.number` | integer | Number of the run within its job, counting up from 1; numbers repeat across jobs. |
| `items[].latest_job_run.pipeline_run_summaries` | array of object | One entry per dlt pipeline name the run reported; a pipeline that ran more than once shows its latest report. In no particular order, and empty until telemetry has received the reports. |
| `items[].latest_job_run.pipeline_run_summaries[].dataset_name` | string or null | Name of the dataset the pipeline wrote to |
| `items[].latest_job_run.pipeline_run_summaries[].destination_name` | string or null | Name of the destination the pipeline wrote to |
| `items[].latest_job_run.pipeline_run_summaries[].duration_ms` | integer or null | Duration of the pipeline run in milliseconds |
| `items[].latest_job_run.pipeline_run_summaries[].finished_at` | string (date-time) or null | When the pipeline run finished |
| `items[].latest_job_run.pipeline_run_summaries[].pipeline_name` | string | Name of the dlt pipeline |
| `items[].latest_job_run.pipeline_run_summaries[].pipeline_run_id` | string (uuid) or null | Telemetry ID of the pipeline run |
| `items[].latest_job_run.pipeline_run_summaries[].started_at` | string (date-time) or null | When the pipeline run started |
| `items[].latest_job_run.pipeline_run_summaries[].status` | string or null | Status of the pipeline run One of: `succeeded`, `failed`. |
| `items[].latest_job_run.pipeline_run_summaries[].total_rows` | integer or null | Total number of rows loaded by the pipeline |
| `items[].latest_job_run.pipeline_run_summaries[].transaction_id` | string or null | Transaction ID of the pipeline run |
| `items[].latest_job_run.previous_job_run_id` | string (uuid) or null | The upstream run whose completion started this one, through a `job.success:` / `job.fail:` trigger or a freshness dependency; `null` for a run started manually, by its own schedule, or by a trigger request. |
| `items[].latest_job_run.profile` | string | The name of the profile that was used for the run |
| `items[].latest_job_run.started_at` | string (date-time) or null | When the run started; `null` until it has. |
| `items[].latest_job_run.status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `items[].latest_job_run.trigger` | string | The trigger the run fired with, e.g. `schedule:0 8 * * *`, `manual:jobs.my_job`, `job.success:jobs.upstream`. |
| `items[].latest_job_run.triggered_by` | string (uuid) or null | The `organization_membership_id` of the member who started the run; set for any run started through the API, `null` for runs the scheduler, another job or a public URL started. |
| `items[].latest_job_run.updated_at` | string (date-time) | When it was last changed. |
| `items[].latest_job_run.workspace_id` | string (uuid) | The ID of the workspace the run belongs to |
| `items[].name` | string or null | Display name for the job |
| `items[].next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `items[].pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `items[].profile` | string | The profile the job runs under |
| `items[].public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `items[].public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `items[].recent_job_runs` | array of object or null | The job's most recent runs, newest first. Included only when `recent_job_runs_limit` is requested; null otherwise. |
| `items[].recent_job_runs[].duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `items[].recent_job_runs[].id` | string (uuid) | The run ID |
| `items[].recent_job_runs[].rows_loaded` | integer or null | Total rows loaded across the run's pipeline runs; null when not reported. |
| `items[].recent_job_runs[].started_at` | string (date-time) or null | When the run started, null if not yet started |
| `items[].recent_job_runs[].status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `items[].triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `items[].updated_at` | string (date-time) | When it was last changed. |
| `items[].version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `items[].workspace_id` | string (uuid) | The ID of the workspace the job belongs to |
| `limit` | integer | The page size this page was fetched with. |
| `next_cursor` | string or null | Cursor for the page after this one; null on the last. |
| `offset` | integer | How many rows were skipped before this page. |
| `total` | integer | Rows matching the filters, reported up to 10001. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Trigger jobs {#trigger_jobs}

```text
POST /api/v1/workspaces/{workspace_id}/jobs/-trigger
```

Triggers jobs by `job_ids` and/or by `selectors`. A job named in `job_ids` fires its
`default_trigger`, or `manual:<job_ref>` if it has none. A selector is matched against each
job's triggers with shell-style wildcards, e.g. `tag:backfill`, `schedule:*`,
`manual:jobs.my_module.*`; a bare trigger type like `schedule` means `schedule:*`, and `batch`
(or `job`), `interactive` and `stream` select every trigger of jobs of that type. A job
matched by a selector fires the trigger it matched. When a job is matched both by id and by
selector, it fires its `default_trigger` if that is among the matched triggers, otherwise
one of the matched triggers. Archived jobs are never matched.

Jobs start upstream first, and each must pass the freshness check: a job whose upstream is
also triggered in this request is skipped with `skipped_upstream_pending`, and a job whose
upstream is not fresh is skipped with `skipped_fresh`. To start a single job regardless of
its upstreams, use `create_job_run` with `skip_freshness=true`.

Runs start one by one; a failure part-way through leaves the runs created before it.

Answers a receipt: `succeeded` jobs started a run, `skipped` jobs did not (their `status` and
`reasons` say why), and `failed` lists requested ids that name no job or an archived job.
`dry_run` previews which jobs match and pass the freshness check, without creating runs.

Requires the viewer role (or higher) in the workspace. Viewers may only run jobs that use the
read-only `access` profile (by default interactive and agent jobs); any other profile needs
the developer role, and a single such job rejects the whole request with 403.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="body" label="Body">

| Field | Type | Description |
| --- | --- | --- |
| `dry_run` | boolean | Preview which jobs match and pass the freshness check, without creating runs. Limits checked only when a run starts (concurrency, trial, compute minutes, interval bounds) are not evaluated. Default: `false`. |
| `job_ids` | array of string (uuid) or null | Jobs to trigger, each fired via its `default_trigger` (or `manual:<ref>` when it has none). At least one of `job_ids` or `selectors` must be non-empty. |
| `profile` | string or null | Profile override for all triggered runs. |
| `refresh` | boolean | Start each triggered job as a refresh, as `refresh` does for `create_job_run`; the progress of the jobs downstream of them is reset too. Ignored for a job whose `refresh_propagation` is `block`. Skipped jobs are not refreshed. Default: `false`. |
| `selectors` | array of string or null | Selectors matched against each job's triggers with shell-style wildcards; a matching job fires the trigger it matched. A bare trigger type like `schedule` means `schedule:*`; `batch` (or `job`), `interactive` and `stream` select every trigger of jobs of that type. Examples: `tag:backfill`, `schedule:*`, `batch`, `manual:jobs.my_module.*`. At least one of `job_ids` or `selectors` must be non-empty. |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/jobs/-trigger" \
  -H "Authorization: Bearer $DLTHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dry_run": false,
    "job_ids": ["<job_ids>"],
    "profile": "<profile>",
    "refresh": false,
    "selectors": ["<selectors>"]
  }'
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/jobs/-trigger",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
    json={
        "dry_run": False,
        "job_ids": ["<job_ids>"],
        "profile": "<profile>",
        "refresh": False,
        "selectors": ["<selectors>"],
    },
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "failed": [
    {
      "detail": "<detail>",
      "ref": "<ref>"
    }
  ],
  "skipped": [
    {
      "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_ref": "<job_ref>",
      "job_run": {
        "configuration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "created_at": "2026-01-01T00:00:00Z",
        "deployment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "duration_seconds": 0,
        "finished_at": "2026-01-01T00:00:00Z",
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "interval_end": "2026-01-01T00:00:00Z",
        "interval_start": "2026-01-01T00:00:00Z",
        "job_version_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "logs_url": "<logs_url>",
        "number": 0,
        "pipeline_run_summaries": [],
        "previous_job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "profile": "<profile>",
        "started_at": "2026-01-01T00:00:00Z",
        "status": "pending",
        "trigger": "<trigger>",
        "triggered_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "updated_at": "2026-01-01T00:00:00Z",
        "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      },
      "job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "matched_triggers": ["<matched_triggers>"],
      "reasons": ["<reasons>"],
      "status": "triggered",
      "trigger": "<trigger>"
    }
  ],
  "succeeded": [
    {
      "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_ref": "<job_ref>",
      "job_run": {
        "configuration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "created_at": "2026-01-01T00:00:00Z",
        "deployment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "duration_seconds": 0,
        "finished_at": "2026-01-01T00:00:00Z",
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "interval_end": "2026-01-01T00:00:00Z",
        "interval_start": "2026-01-01T00:00:00Z",
        "job_version_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "logs_url": "<logs_url>",
        "number": 0,
        "pipeline_run_summaries": [],
        "previous_job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "profile": "<profile>",
        "started_at": "2026-01-01T00:00:00Z",
        "status": "pending",
        "trigger": "<trigger>",
        "triggered_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "updated_at": "2026-01-01T00:00:00Z",
        "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      },
      "job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "matched_triggers": ["<matched_triggers>"],
      "reasons": ["<reasons>"],
      "status": "triggered",
      "trigger": "<trigger>"
    }
  ]
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `failed` | array of object | Requested job ids that name no job in the workspace, or an archived job. |
| `failed[].detail` | string | Why it failed. |
| `failed[].ref` | string | The item that failed, by its id or natural key. |
| `skipped` | array of object | Jobs that did not start; their `status` and `reasons` say why. |
| `skipped[].job_id` | string (uuid) | The ID of the matched job. |
| `skipped[].job_ref` | string | The job reference of the matched job. |
| `skipped[].job_run` | object or null | The created run; `null` in dry-run mode or when skipped. |
| `skipped[].job_run.configuration_id` | string (uuid) | The ID of the configuration the run uses. |
| `skipped[].job_run.created_at` | string (date-time) | When it was created. |
| `skipped[].job_run.deployment_id` | string (uuid) | The ID of the deployment the run uses. |
| `skipped[].job_run.duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `skipped[].job_run.finished_at` | string (date-time) or null | When the run finished; `null` until it has. |
| `skipped[].job_run.id` | string (uuid) | Unique ID (a UUID). |
| `skipped[].job_run.interval_end` | string (date-time) or null | End of the interval the run processes; `null` for a job without intervals. |
| `skipped[].job_run.interval_start` | string (date-time) or null | Start of the interval the run processes; `null` for a job without intervals. |
| `skipped[].job_run.job_version_id` | string (uuid) | The ID of the job version the run uses. |
| `skipped[].job_run.logs_url` | string or null | A link to the logs of the run |
| `skipped[].job_run.number` | integer | Number of the run within its job, counting up from 1; numbers repeat across jobs. |
| `skipped[].job_run.pipeline_run_summaries` | array of object | One entry per dlt pipeline name the run reported; a pipeline that ran more than once shows its latest report. In no particular order, and empty until telemetry has received the reports. |
| `skipped[].job_run.pipeline_run_summaries[].dataset_name` | string or null | Name of the dataset the pipeline wrote to |
| `skipped[].job_run.pipeline_run_summaries[].destination_name` | string or null | Name of the destination the pipeline wrote to |
| `skipped[].job_run.pipeline_run_summaries[].duration_ms` | integer or null | Duration of the pipeline run in milliseconds |
| `skipped[].job_run.pipeline_run_summaries[].finished_at` | string (date-time) or null | When the pipeline run finished |
| `skipped[].job_run.pipeline_run_summaries[].pipeline_name` | string | Name of the dlt pipeline |
| `skipped[].job_run.pipeline_run_summaries[].pipeline_run_id` | string (uuid) or null | Telemetry ID of the pipeline run |
| `skipped[].job_run.pipeline_run_summaries[].started_at` | string (date-time) or null | When the pipeline run started |
| `skipped[].job_run.pipeline_run_summaries[].status` | string or null | Status of the pipeline run One of: `succeeded`, `failed`. |
| `skipped[].job_run.pipeline_run_summaries[].total_rows` | integer or null | Total number of rows loaded by the pipeline |
| `skipped[].job_run.pipeline_run_summaries[].transaction_id` | string or null | Transaction ID of the pipeline run |
| `skipped[].job_run.previous_job_run_id` | string (uuid) or null | The upstream run whose completion started this one, through a `job.success:` / `job.fail:` trigger or a freshness dependency; `null` for a run started manually, by its own schedule, or by a trigger request. |
| `skipped[].job_run.profile` | string | The name of the profile that was used for the run |
| `skipped[].job_run.started_at` | string (date-time) or null | When the run started; `null` until it has. |
| `skipped[].job_run.status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `skipped[].job_run.trigger` | string | The trigger the run fired with, e.g. `schedule:0 8 * * *`, `manual:jobs.my_job`, `job.success:jobs.upstream`. |
| `skipped[].job_run.triggered_by` | string (uuid) or null | The `organization_membership_id` of the member who started the run; set for any run started through the API, `null` for runs the scheduler, another job or a public URL started. |
| `skipped[].job_run.updated_at` | string (date-time) | When it was last changed. |
| `skipped[].job_run.workspace_id` | string (uuid) | The ID of the workspace the run belongs to |
| `skipped[].job_run_id` | string (uuid) or null | The ID of the created run; `null` in dry-run mode or when skipped. |
| `skipped[].matched_triggers` | array of string | Every trigger that selected this job: its default trigger when named in `job_ids`, plus any selector matches. |
| `skipped[].reasons` | array of string or null | Human-readable reasons for a skip, e.g. which upstream jobs are not fresh; `null` when the job was triggered or no reasons were recorded. |
| `skipped[].status` | string | Whether the job started a run: `triggered` if it did; otherwise why not. `skipped_fresh`: an upstream job is not fresh (see `reasons`). `skipped_upstream_pending`: an upstream job is triggered in the same request. `skipped_out_of_interval`: the run would fall outside the job's `interval_start` / `interval_end`. `skipped_concurrency_limit`: the job is already running as many runs as it allows. `skipped_org_concurrency_limit`: the organization has reached its limit of concurrent runs. `skipped_trial_expired`: the organization's trial has ended. `skipped_minutes_limit`: the organization has used up its compute minutes. `skipped_workspace_archived`: the workspace is archived. One of: `triggered`, `skipped_fresh`, `skipped_upstream_pending`, `skipped_out_of_interval`, `skipped_concurrency_limit`, `skipped_already_covered`, `skipped_trial_expired`, `skipped_minutes_limit`, `skipped_org_concurrency_limit`, `skipped_paused`, `skipped_workspace_archived`. Default: `"triggered"`. |
| `skipped[].trigger` | string | The trigger the job fires with. |
| `succeeded` | array of object | Jobs that started a run, or in dry-run mode would pass the freshness check. |
| `succeeded[].job_id` | string (uuid) | The ID of the matched job. |
| `succeeded[].job_ref` | string | The job reference of the matched job. |
| `succeeded[].job_run` | object or null | The created run; `null` in dry-run mode or when skipped. |
| `succeeded[].job_run.configuration_id` | string (uuid) | The ID of the configuration the run uses. |
| `succeeded[].job_run.created_at` | string (date-time) | When it was created. |
| `succeeded[].job_run.deployment_id` | string (uuid) | The ID of the deployment the run uses. |
| `succeeded[].job_run.duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `succeeded[].job_run.finished_at` | string (date-time) or null | When the run finished; `null` until it has. |
| `succeeded[].job_run.id` | string (uuid) | Unique ID (a UUID). |
| `succeeded[].job_run.interval_end` | string (date-time) or null | End of the interval the run processes; `null` for a job without intervals. |
| `succeeded[].job_run.interval_start` | string (date-time) or null | Start of the interval the run processes; `null` for a job without intervals. |
| `succeeded[].job_run.job_version_id` | string (uuid) | The ID of the job version the run uses. |
| `succeeded[].job_run.logs_url` | string or null | A link to the logs of the run |
| `succeeded[].job_run.number` | integer | Number of the run within its job, counting up from 1; numbers repeat across jobs. |
| `succeeded[].job_run.pipeline_run_summaries` | array of object | One entry per dlt pipeline name the run reported; a pipeline that ran more than once shows its latest report. In no particular order, and empty until telemetry has received the reports. |
| `succeeded[].job_run.pipeline_run_summaries[].dataset_name` | string or null | Name of the dataset the pipeline wrote to |
| `succeeded[].job_run.pipeline_run_summaries[].destination_name` | string or null | Name of the destination the pipeline wrote to |
| `succeeded[].job_run.pipeline_run_summaries[].duration_ms` | integer or null | Duration of the pipeline run in milliseconds |
| `succeeded[].job_run.pipeline_run_summaries[].finished_at` | string (date-time) or null | When the pipeline run finished |
| `succeeded[].job_run.pipeline_run_summaries[].pipeline_name` | string | Name of the dlt pipeline |
| `succeeded[].job_run.pipeline_run_summaries[].pipeline_run_id` | string (uuid) or null | Telemetry ID of the pipeline run |
| `succeeded[].job_run.pipeline_run_summaries[].started_at` | string (date-time) or null | When the pipeline run started |
| `succeeded[].job_run.pipeline_run_summaries[].status` | string or null | Status of the pipeline run One of: `succeeded`, `failed`. |
| `succeeded[].job_run.pipeline_run_summaries[].total_rows` | integer or null | Total number of rows loaded by the pipeline |
| `succeeded[].job_run.pipeline_run_summaries[].transaction_id` | string or null | Transaction ID of the pipeline run |
| `succeeded[].job_run.previous_job_run_id` | string (uuid) or null | The upstream run whose completion started this one, through a `job.success:` / `job.fail:` trigger or a freshness dependency; `null` for a run started manually, by its own schedule, or by a trigger request. |
| `succeeded[].job_run.profile` | string | The name of the profile that was used for the run |
| `succeeded[].job_run.started_at` | string (date-time) or null | When the run started; `null` until it has. |
| `succeeded[].job_run.status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `succeeded[].job_run.trigger` | string | The trigger the run fired with, e.g. `schedule:0 8 * * *`, `manual:jobs.my_job`, `job.success:jobs.upstream`. |
| `succeeded[].job_run.triggered_by` | string (uuid) or null | The `organization_membership_id` of the member who started the run; set for any run started through the API, `null` for runs the scheduler, another job or a public URL started. |
| `succeeded[].job_run.updated_at` | string (date-time) | When it was last changed. |
| `succeeded[].job_run.workspace_id` | string (uuid) | The ID of the workspace the run belongs to |
| `succeeded[].job_run_id` | string (uuid) or null | The ID of the created run; `null` in dry-run mode or when skipped. |
| `succeeded[].matched_triggers` | array of string | Every trigger that selected this job: its default trigger when named in `job_ids`, plus any selector matches. |
| `succeeded[].reasons` | array of string or null | Human-readable reasons for a skip, e.g. which upstream jobs are not fresh; `null` when the job was triggered or no reasons were recorded. |
| `succeeded[].status` | string | Whether the job started a run: `triggered` if it did; otherwise why not. `skipped_fresh`: an upstream job is not fresh (see `reasons`). `skipped_upstream_pending`: an upstream job is triggered in the same request. `skipped_out_of_interval`: the run would fall outside the job's `interval_start` / `interval_end`. `skipped_concurrency_limit`: the job is already running as many runs as it allows. `skipped_org_concurrency_limit`: the organization has reached its limit of concurrent runs. `skipped_trial_expired`: the organization's trial has ended. `skipped_minutes_limit`: the organization has used up its compute minutes. `skipped_workspace_archived`: the workspace is archived. One of: `triggered`, `skipped_fresh`, `skipped_upstream_pending`, `skipped_out_of_interval`, `skipped_concurrency_limit`, `skipped_already_covered`, `skipped_trial_expired`, `skipped_minutes_limit`, `skipped_org_concurrency_limit`, `skipped_paused`, `skipped_workspace_archived`. Default: `"triggered"`. |
| `succeeded[].trigger` | string | The trigger the job fires with. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Get job {#get_job}

```text
GET /api/v1/workspaces/{workspace_id}/jobs/{job_id}
```

Gets a job for a workspace by its ID. Find a job by its ref with `list_jobs`.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `job_id` | string (uuid) | ID of the job |

**Query parameters**

| Name | Type | Description |
| --- | --- | --- |
| `recent_job_runs_limit` | integer or null | Attach this many of the job's most recent runs. |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/jobs/$JOB_ID" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/jobs/<job_id>",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "current_version": {
    "created_at": "2026-01-01T00:00:00Z",
    "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "default_trigger": "<default_trigger>",
    "deployment_module": "<deployment_module>",
    "description": "<description>",
    "freshness": ["<freshness>"],
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "instance_size": "small",
    "interval_end": "2026-01-01T00:00:00Z",
    "interval_start": "2026-01-01T00:00:00Z",
    "job_definition": {},
    "job_definition_engine_version": 0,
    "job_definition_hash": "<job_definition_hash>",
    "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "job_ref": "<job_ref>",
    "job_type": "batch",
    "max_run_time_seconds": 0,
    "memory_mib": 0,
    "name": "<name>",
    "pipeline_name": "<pipeline_name>",
    "profile": "<profile>",
    "provider": "tower",
    "triggers": ["<triggers>"],
    "updated_at": "2026-01-01T00:00:00Z",
    "vcpu": 0,
    "version": 0
  },
  "default_trigger": "<default_trigger>",
  "deployment_module": "<deployment_module>",
  "description": "<description>",
  "downstream_job_runs": [
    {
      "category": "pipeline",
      "duration_seconds": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "job_ref": "<job_ref>",
      "name": "<name>",
      "number": 0,
      "started_at": "2026-01-01T00:00:00Z",
      "status": "pending",
      "trigger": "<trigger>"
    }
  ],
  "freshness": ["<freshness>"],
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "interval_end": "2026-01-01T00:00:00Z",
  "interval_start": "2026-01-01T00:00:00Z",
  "is_archived": false,
  "is_paused": false,
  "job_definition": {},
  "job_definition_engine_version": 0,
  "job_definition_hash": "<job_definition_hash>",
  "job_ref": "<job_ref>",
  "job_type": "batch",
  "job_url": "<job_url>",
  "latest_job_run": {
    "configuration_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "created_at": "2026-01-01T00:00:00Z",
    "deployment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "duration_seconds": 0,
    "finished_at": "2026-01-01T00:00:00Z",
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "interval_end": "2026-01-01T00:00:00Z",
    "interval_start": "2026-01-01T00:00:00Z",
    "job_version_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "logs_url": "<logs_url>",
    "number": 0,
    "pipeline_run_summaries": [
      {
        "dataset_name": "<dataset_name>",
        "destination_name": "<destination_name>",
        "duration_ms": 0,
        "finished_at": "2026-01-01T00:00:00Z",
        "pipeline_name": "<pipeline_name>",
        "pipeline_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "started_at": "2026-01-01T00:00:00Z",
        "status": "succeeded",
        "total_rows": 0,
        "transaction_id": "<transaction_id>"
      }
    ],
    "previous_job_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "profile": "<profile>",
    "started_at": "2026-01-01T00:00:00Z",
    "status": "pending",
    "trigger": "<trigger>",
    "triggered_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "updated_at": "2026-01-01T00:00:00Z",
    "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  },
  "name": "<name>",
  "next_scheduled_run_at": "2026-01-01T00:00:00Z",
  "pipeline_name": "<pipeline_name>",
  "profile": "<profile>",
  "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "public_url": "<public_url>",
  "recent_job_runs": [
    {
      "duration_seconds": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "rows_loaded": 0,
      "started_at": "2026-01-01T00:00:00Z",
      "status": "pending"
    }
  ],
  "triggers": ["<triggers>"],
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `current_version` | object or null | The active job version, carrying its provider, instance size, and resolved hardware. |
| `current_version.created_at` | string (date-time) | When it was created. |
| `current_version.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `current_version.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `current_version.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `current_version.description` | string or null | The description of the job |
| `current_version.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `current_version.id` | string (uuid) | Unique ID (a UUID). |
| `current_version.instance_size` | string or null | Instance size this version runs on; `null` on a provider without instance sizing. One of: `small`, `medium`, `large`, `xlarge`. |
| `current_version.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `current_version.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `current_version.job_definition` | object | Job definition from the dlt deployment manifest. |
| `current_version.job_definition_engine_version` | integer | Version of the job definition format. |
| `current_version.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `current_version.job_id` | string (uuid) | The ID of the job the job version belongs to |
| `current_version.job_ref` | string | Canonical job reference, unique per workspace |
| `current_version.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `current_version.max_run_time_seconds` | integer | Maximum allowed run duration in seconds for this job version |
| `current_version.memory_mib` | integer or null | Resolved memory in MiB; `null` when the version has no sized hardware. |
| `current_version.name` | string or null | Display name for the job |
| `current_version.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `current_version.profile` | string | The profile the job runs under |
| `current_version.provider` | string | Run provider for runs using this job version (e.g. 'tower', 'modal', 'local') One of: `tower`, `modal`, `local`. |
| `current_version.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `current_version.updated_at` | string (date-time) | When it was last changed. |
| `current_version.vcpu` | number or null | Resolved virtual CPU cores; `null` when the version has no sized hardware. |
| `current_version.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `description` | string or null | The description of the job |
| `downstream_job_runs` | array of object or null | Runs the job's last run triggered, newest first. Included only when `downstream_job_runs_limit` is requested; null otherwise. Empty when the last run triggered nothing, since a chained job fires only once its upstream run reaches a terminal state. |
| `downstream_job_runs[].category` | string or null | The category the job declares in `expose.category`, null when it declares none. Callers that want one kind of downstream run, such as agents, filter on this. One of: `pipeline`, `mcp`, `dashboard`, `notebook`, `background_agent`. |
| `downstream_job_runs[].duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `downstream_job_runs[].id` | string (uuid) | The run ID |
| `downstream_job_runs[].job_id` | string (uuid) | The ID of the job that ran |
| `downstream_job_runs[].job_ref` | string | The job's canonical reference |
| `downstream_job_runs[].name` | string | The job's display name |
| `downstream_job_runs[].number` | integer | The run number within the job |
| `downstream_job_runs[].started_at` | string (date-time) or null | When the run started, null if not yet started |
| `downstream_job_runs[].status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `downstream_job_runs[].trigger` | string | The trigger that started the downstream run |
| `freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `id` | string (uuid) | Unique ID (a UUID). |
| `interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_definition_engine_version` | integer | Version of the job definition format. |
| `job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_ref` | string | Canonical job reference, unique per workspace |
| `job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `latest_job_run` | object or null | The latest run of the job; `null` if it has never run. |
| `latest_job_run.configuration_id` | string (uuid) | The ID of the configuration the run uses. |
| `latest_job_run.created_at` | string (date-time) | When it was created. |
| `latest_job_run.deployment_id` | string (uuid) | The ID of the deployment the run uses. |
| `latest_job_run.duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `latest_job_run.finished_at` | string (date-time) or null | When the run finished; `null` until it has. |
| `latest_job_run.id` | string (uuid) | Unique ID (a UUID). |
| `latest_job_run.interval_end` | string (date-time) or null | End of the interval the run processes; `null` for a job without intervals. |
| `latest_job_run.interval_start` | string (date-time) or null | Start of the interval the run processes; `null` for a job without intervals. |
| `latest_job_run.job_version_id` | string (uuid) | The ID of the job version the run uses. |
| `latest_job_run.logs_url` | string or null | A link to the logs of the run |
| `latest_job_run.number` | integer | Number of the run within its job, counting up from 1; numbers repeat across jobs. |
| `latest_job_run.pipeline_run_summaries` | array of object | One entry per dlt pipeline name the run reported; a pipeline that ran more than once shows its latest report. In no particular order, and empty until telemetry has received the reports. |
| `latest_job_run.pipeline_run_summaries[].dataset_name` | string or null | Name of the dataset the pipeline wrote to |
| `latest_job_run.pipeline_run_summaries[].destination_name` | string or null | Name of the destination the pipeline wrote to |
| `latest_job_run.pipeline_run_summaries[].duration_ms` | integer or null | Duration of the pipeline run in milliseconds |
| `latest_job_run.pipeline_run_summaries[].finished_at` | string (date-time) or null | When the pipeline run finished |
| `latest_job_run.pipeline_run_summaries[].pipeline_name` | string | Name of the dlt pipeline |
| `latest_job_run.pipeline_run_summaries[].pipeline_run_id` | string (uuid) or null | Telemetry ID of the pipeline run |
| `latest_job_run.pipeline_run_summaries[].started_at` | string (date-time) or null | When the pipeline run started |
| `latest_job_run.pipeline_run_summaries[].status` | string or null | Status of the pipeline run One of: `succeeded`, `failed`. |
| `latest_job_run.pipeline_run_summaries[].total_rows` | integer or null | Total number of rows loaded by the pipeline |
| `latest_job_run.pipeline_run_summaries[].transaction_id` | string or null | Transaction ID of the pipeline run |
| `latest_job_run.previous_job_run_id` | string (uuid) or null | The upstream run whose completion started this one, through a `job.success:` / `job.fail:` trigger or a freshness dependency; `null` for a run started manually, by its own schedule, or by a trigger request. |
| `latest_job_run.profile` | string | The name of the profile that was used for the run |
| `latest_job_run.started_at` | string (date-time) or null | When the run started; `null` until it has. |
| `latest_job_run.status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `latest_job_run.trigger` | string | The trigger the run fired with, e.g. `schedule:0 8 * * *`, `manual:jobs.my_job`, `job.success:jobs.upstream`. |
| `latest_job_run.triggered_by` | string (uuid) or null | The `organization_membership_id` of the member who started the run; set for any run started through the API, `null` for runs the scheduler, another job or a public URL started. |
| `latest_job_run.updated_at` | string (date-time) | When it was last changed. |
| `latest_job_run.workspace_id` | string (uuid) | The ID of the workspace the run belongs to |
| `name` | string or null | Display name for the job |
| `next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `profile` | string | The profile the job runs under |
| `public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `recent_job_runs` | array of object or null | The job's most recent runs, newest first. Included only when `recent_job_runs_limit` is requested; null otherwise. |
| `recent_job_runs[].duration_seconds` | number or null | Duration of the run in seconds, possibly fractional; `null` until it has finished. |
| `recent_job_runs[].id` | string (uuid) | The run ID |
| `recent_job_runs[].rows_loaded` | integer or null | Total rows loaded across the run's pipeline runs; null when not reported. |
| `recent_job_runs[].started_at` | string (date-time) or null | When the run started, null if not yet started |
| `recent_job_runs[].status` | string | The status of the run One of: `pending`, `starting`, `running`, `completed`, `failed`, `cancelled`, `skipped`, `cancelling`. |
| `triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `workspace_id` | string (uuid) | The ID of the workspace the job belongs to |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Archive job {#archive_job}

```text
POST /api/v1/workspaces/{workspace_id}/jobs/{job_id}/-archive
```

Archives a job. An archived job cannot be run (neither manually nor by schedule);
listings include it unless `is_archived` filters it out. Archiving clears the job's schedule
and pause, and its public URL stops working until it is unarchived. Idempotent.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `job_id` | string (uuid) | ID of the job |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/jobs/$JOB_ID/-archive" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/jobs/<job_id>/-archive",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "default_trigger": "<default_trigger>",
  "deployment_module": "<deployment_module>",
  "description": "<description>",
  "freshness": ["<freshness>"],
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "interval_end": "2026-01-01T00:00:00Z",
  "interval_start": "2026-01-01T00:00:00Z",
  "is_archived": false,
  "is_paused": false,
  "job_definition": {},
  "job_definition_engine_version": 0,
  "job_definition_hash": "<job_definition_hash>",
  "job_ref": "<job_ref>",
  "job_type": "batch",
  "job_url": "<job_url>",
  "name": "<name>",
  "next_scheduled_run_at": "2026-01-01T00:00:00Z",
  "pipeline_name": "<pipeline_name>",
  "profile": "<profile>",
  "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "public_url": "<public_url>",
  "triggers": ["<triggers>"],
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `description` | string or null | The description of the job |
| `freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `id` | string (uuid) | Unique ID (a UUID). |
| `interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_definition_engine_version` | integer | Version of the job definition format. |
| `job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_ref` | string | Canonical job reference, unique per workspace |
| `job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `name` | string or null | Display name for the job |
| `next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `profile` | string | The profile the job runs under |
| `public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `workspace_id` | string (uuid) | The ID of the workspace the job belongs to |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Pause job {#pause_job}

```text
POST /api/v1/workspaces/{workspace_id}/jobs/{job_id}/-pause
```

Pauses a job's scheduled runs. The scheduler stops starting it, including a scheduled run
that was waiting for its upstream jobs. Manual runs, `trigger_jobs` and runs started by
another job's success or failure still run.

Only a job with `next_scheduled_run_at` can be paused; an archived job or one without a
schedule answers 400.

`next_scheduled_run_at` keeps advancing while the job is paused, so resuming produces one run
covering the whole period. A deploy that removes the schedule also removes the pause;
otherwise a deploy leaves it alone. The public URL is untouched. Idempotent.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `job_id` | string (uuid) | ID of the job |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/jobs/$JOB_ID/-pause" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/jobs/<job_id>/-pause",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "default_trigger": "<default_trigger>",
  "deployment_module": "<deployment_module>",
  "description": "<description>",
  "freshness": ["<freshness>"],
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "interval_end": "2026-01-01T00:00:00Z",
  "interval_start": "2026-01-01T00:00:00Z",
  "is_archived": false,
  "is_paused": false,
  "job_definition": {},
  "job_definition_engine_version": 0,
  "job_definition_hash": "<job_definition_hash>",
  "job_ref": "<job_ref>",
  "job_type": "batch",
  "job_url": "<job_url>",
  "name": "<name>",
  "next_scheduled_run_at": "2026-01-01T00:00:00Z",
  "pipeline_name": "<pipeline_name>",
  "profile": "<profile>",
  "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "public_url": "<public_url>",
  "triggers": ["<triggers>"],
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `description` | string or null | The description of the job |
| `freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `id` | string (uuid) | Unique ID (a UUID). |
| `interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_definition_engine_version` | integer | Version of the job definition format. |
| `job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_ref` | string | Canonical job reference, unique per workspace |
| `job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `name` | string or null | Display name for the job |
| `next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `profile` | string | The profile the job runs under |
| `public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `workspace_id` | string (uuid) | The ID of the workspace the job belongs to |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Publish job {#publish_job}

```text
POST /api/v1/workspaces/{workspace_id}/jobs/{job_id}/-publish
```

Enables or regenerates the public URL for a job. This creates a new `public_secret` that
gives access to the job without authentication. Calling it again generates a new
`public_secret`, invalidating any previous public URL. The URL does not work while the job
is archived.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `job_id` | string (uuid) | ID of the job |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/jobs/$JOB_ID/-publish" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/jobs/<job_id>/-publish",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "default_trigger": "<default_trigger>",
  "deployment_module": "<deployment_module>",
  "description": "<description>",
  "freshness": ["<freshness>"],
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "interval_end": "2026-01-01T00:00:00Z",
  "interval_start": "2026-01-01T00:00:00Z",
  "is_archived": false,
  "is_paused": false,
  "job_definition": {},
  "job_definition_engine_version": 0,
  "job_definition_hash": "<job_definition_hash>",
  "job_ref": "<job_ref>",
  "job_type": "batch",
  "job_url": "<job_url>",
  "name": "<name>",
  "next_scheduled_run_at": "2026-01-01T00:00:00Z",
  "pipeline_name": "<pipeline_name>",
  "profile": "<profile>",
  "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "public_url": "<public_url>",
  "triggers": ["<triggers>"],
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `description` | string or null | The description of the job |
| `freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `id` | string (uuid) | Unique ID (a UUID). |
| `interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_definition_engine_version` | integer | Version of the job definition format. |
| `job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_ref` | string | Canonical job reference, unique per workspace |
| `job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `name` | string or null | Display name for the job |
| `next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `profile` | string | The profile the job runs under |
| `public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `workspace_id` | string (uuid) | The ID of the workspace the job belongs to |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Resume job {#resume_job}

```text
POST /api/v1/workspaces/{workspace_id}/jobs/{job_id}/-resume
```

Resumes a paused job, so the scheduler starts it again. The first run after resuming covers
the whole period the job was paused for. Idempotent.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `job_id` | string (uuid) | ID of the job |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/jobs/$JOB_ID/-resume" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/jobs/<job_id>/-resume",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "default_trigger": "<default_trigger>",
  "deployment_module": "<deployment_module>",
  "description": "<description>",
  "freshness": ["<freshness>"],
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "interval_end": "2026-01-01T00:00:00Z",
  "interval_start": "2026-01-01T00:00:00Z",
  "is_archived": false,
  "is_paused": false,
  "job_definition": {},
  "job_definition_engine_version": 0,
  "job_definition_hash": "<job_definition_hash>",
  "job_ref": "<job_ref>",
  "job_type": "batch",
  "job_url": "<job_url>",
  "name": "<name>",
  "next_scheduled_run_at": "2026-01-01T00:00:00Z",
  "pipeline_name": "<pipeline_name>",
  "profile": "<profile>",
  "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "public_url": "<public_url>",
  "triggers": ["<triggers>"],
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `description` | string or null | The description of the job |
| `freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `id` | string (uuid) | Unique ID (a UUID). |
| `interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_definition_engine_version` | integer | Version of the job definition format. |
| `job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_ref` | string | Canonical job reference, unique per workspace |
| `job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `name` | string or null | Display name for the job |
| `next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `profile` | string | The profile the job runs under |
| `public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `workspace_id` | string (uuid) | The ID of the workspace the job belongs to |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Unarchive job {#unarchive_job}

```text
POST /api/v1/workspaces/{workspace_id}/jobs/{job_id}/-unarchive
```

Unarchives a job so it can run again, restoring the schedule and default trigger from its
definition; a pause is not restored. Idempotent.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `job_id` | string (uuid) | ID of the job |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/jobs/$JOB_ID/-unarchive" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/jobs/<job_id>/-unarchive",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "default_trigger": "<default_trigger>",
  "deployment_module": "<deployment_module>",
  "description": "<description>",
  "freshness": ["<freshness>"],
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "interval_end": "2026-01-01T00:00:00Z",
  "interval_start": "2026-01-01T00:00:00Z",
  "is_archived": false,
  "is_paused": false,
  "job_definition": {},
  "job_definition_engine_version": 0,
  "job_definition_hash": "<job_definition_hash>",
  "job_ref": "<job_ref>",
  "job_type": "batch",
  "job_url": "<job_url>",
  "name": "<name>",
  "next_scheduled_run_at": "2026-01-01T00:00:00Z",
  "pipeline_name": "<pipeline_name>",
  "profile": "<profile>",
  "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "public_url": "<public_url>",
  "triggers": ["<triggers>"],
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `description` | string or null | The description of the job |
| `freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `id` | string (uuid) | Unique ID (a UUID). |
| `interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_definition_engine_version` | integer | Version of the job definition format. |
| `job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_ref` | string | Canonical job reference, unique per workspace |
| `job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `name` | string or null | Display name for the job |
| `next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `profile` | string | The profile the job runs under |
| `public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `workspace_id` | string (uuid) | The ID of the workspace the job belongs to |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Unpublish job {#unpublish_job}

```text
POST /api/v1/workspaces/{workspace_id}/jobs/{job_id}/-unpublish
```

Disables the public URL for a job by clearing its `public_secret`. Afterwards the job is
no longer reachable through its public URL, and `public_url` is `null`.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |
| `job_id` | string (uuid) | ID of the job |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/jobs/$JOB_ID/-unpublish" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/jobs/<job_id>/-unpublish",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "created_at": "2026-01-01T00:00:00Z",
  "created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "default_trigger": "<default_trigger>",
  "deployment_module": "<deployment_module>",
  "description": "<description>",
  "freshness": ["<freshness>"],
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "interval_end": "2026-01-01T00:00:00Z",
  "interval_start": "2026-01-01T00:00:00Z",
  "is_archived": false,
  "is_paused": false,
  "job_definition": {},
  "job_definition_engine_version": 0,
  "job_definition_hash": "<job_definition_hash>",
  "job_ref": "<job_ref>",
  "job_type": "batch",
  "job_url": "<job_url>",
  "name": "<name>",
  "next_scheduled_run_at": "2026-01-01T00:00:00Z",
  "pipeline_name": "<pipeline_name>",
  "profile": "<profile>",
  "public_secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "public_url": "<public_url>",
  "triggers": ["<triggers>"],
  "updated_at": "2026-01-01T00:00:00Z",
  "version": 0,
  "workspace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `created_at` | string (date-time) | When it was created. |
| `created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `description` | string or null | The description of the job |
| `freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `id` | string (uuid) | Unique ID (a UUID). |
| `interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_definition_engine_version` | integer | Version of the job definition format. |
| `job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_ref` | string | Canonical job reference, unique per workspace |
| `job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `name` | string or null | Display name for the job |
| `next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `profile` | string | The profile the job runs under |
| `public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `updated_at` | string (date-time) | When it was last changed. |
| `version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `workspace_id` | string (uuid) | The ID of the workspace the job belongs to |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

## Me

### Get me {#get_me}

```text
GET /api/v1/me
```

Gets your user profile and organization memberships. `primary_organization` is where new
workspaces are created by default; `latest_organization` and `latest_workspace` are the ones
you used last. Workspaces are listed per organization by `get_organization_me`.

Each call also accepts your pending invites and creates your playground workspace in any
organization that lacks one. On the first call for a user who has no organization yet, it
creates a personal organization and a playground workspace, so a freshly authenticated user
always gets a 200.

Requires a personal API key or a user login; a workspace API key answers 403.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/me" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/me",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "email": "<email>",
  "latest_organization": {
    "created_at": "2026-01-01T00:00:00Z",
    "dataplane_id": "<dataplane_id>",
    "description": "<description>",
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "<name>",
    "updated_at": "2026-01-01T00:00:00Z"
  },
  "latest_workspace": {
    "archived_at": "2026-01-01T00:00:00Z",
    "created_at": "2026-01-01T00:00:00Z",
    "dataplane_id": "<dataplane_id>",
    "dataplane_url": "<dataplane_url>",
    "description": "<description>",
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "is_playground": false,
    "name": "<name>",
    "organization_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "predefined_profiles": {},
    "updated_at": "2026-01-01T00:00:00Z"
  },
  "name": "<name>",
  "organization_membership_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "organization_memberships": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "is_active": false,
      "organization": {
        "created_at": "2026-01-01T00:00:00Z",
        "dataplane_id": "<dataplane_id>",
        "description": "<description>",
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "name": "<name>",
        "updated_at": "2026-01-01T00:00:00Z"
      },
      "role": "owner"
    }
  ],
  "primary_organization": {
    "created_at": "2026-01-01T00:00:00Z",
    "dataplane_id": "<dataplane_id>",
    "description": "<description>",
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "<name>",
    "updated_at": "2026-01-01T00:00:00Z"
  },
  "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `email` | string | The email of the current user |
| `latest_organization` | object | The organization you used most recently. |
| `latest_organization.created_at` | string (date-time) | When it was created. |
| `latest_organization.dataplane_id` | string or null | Data plane of the organization's workspaces; `null` until a region is set. |
| `latest_organization.description` | string or null | Free-text description of the organization; `null` when none is set. |
| `latest_organization.id` | string (uuid) | Unique ID (a UUID). |
| `latest_organization.name` | string | The name of the organization |
| `latest_organization.updated_at` | string (date-time) | When it was last changed. |
| `latest_workspace` | object or null | The workspace you used last; `null` when none is recorded yet. |
| `latest_workspace.archived_at` | string (date-time) or null | When the workspace was archived; `null` when live. An archived workspace rejects all changes except unarchiving, though owners can still manage its workspace variables. |
| `latest_workspace.created_at` | string (date-time) | When it was created. |
| `latest_workspace.dataplane_id` | string | Identifier of the data plane that holds this workspace's user data. Matches the subdomain of the plane's public entrypoint. |
| `latest_workspace.dataplane_url` | string | Base URL of the workspace's data plane, `https://<dataplane_id>.dlthub.com`, where the Dataplane, Logs and Telemetry APIs are served. Call them with a token from `POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token`. |
| `latest_workspace.description` | string or null | Free-text description of the workspace; `null` when none is set. |
| `latest_workspace.id` | string (uuid) | Unique ID (a UUID). |
| `latest_workspace.is_playground` | boolean | Whether this is the user's auto-created personal playground workspace. A playground has a single member and cannot be updated, archived, shared, or given API keys. Default: `false`. |
| `latest_workspace.name` | string | The name of the workspace |
| `latest_workspace.organization_id` | string (uuid) | The ID of the parent organization |
| `latest_workspace.predefined_profiles` | map of string | Built-in profiles by data access: `DATA_WRITE` → `prod` (runs need developer or owner), `DATA_READ` → `access` (viewers may run). |
| `latest_workspace.updated_at` | string (date-time) | When it was last changed. |
| `name` | string or null | The user's display name (full name); `null` if the identity provider supplied none. |
| `organization_membership_id` | string (uuid) | Your membership id in `latest_organization`. |
| `organization_memberships` | array of object | All organizations the user is a member of |
| `organization_memberships[].id` | string (uuid) | The ID of the organization membership |
| `organization_memberships[].is_active` | boolean | Whether the membership is active |
| `organization_memberships[].organization` | object | The organization. |
| `organization_memberships[].organization.created_at` | string (date-time) | When it was created. |
| `organization_memberships[].organization.dataplane_id` | string or null | Data plane of the organization's workspaces; `null` until a region is set. |
| `organization_memberships[].organization.description` | string or null | Free-text description of the organization; `null` when none is set. |
| `organization_memberships[].organization.id` | string (uuid) | Unique ID (a UUID). |
| `organization_memberships[].organization.name` | string | The name of the organization |
| `organization_memberships[].organization.updated_at` | string (date-time) | When it was last changed. |
| `organization_memberships[].role` | string | The role of the user in the organization One of: `owner`, `member`, `collaborator`. |
| `primary_organization` | object | The organization where new workspaces are created by default. |
| `primary_organization.created_at` | string (date-time) | When it was created. |
| `primary_organization.dataplane_id` | string or null | Data plane of the organization's workspaces; `null` until a region is set. |
| `primary_organization.description` | string or null | Free-text description of the organization; `null` when none is set. |
| `primary_organization.id` | string (uuid) | Unique ID (a UUID). |
| `primary_organization.name` | string | The name of the organization |
| `primary_organization.updated_at` | string (date-time) | When it was last changed. |
| `user_id` | string (uuid) | The ID of the current user |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Organization me {#get_organization_me}

```text
GET /api/v1/organizations/{organization_id}/me
```

Gets your context in an organization: the organization, your role and membership id there,
and the workspaces you can access in it with your role in each.

Any organization member, including collaborators.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `organization_id` | string (uuid) | ID of the organization |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/organizations/$ORGANIZATION_ID/me" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/organizations/<organization_id>/me",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "email": "<email>",
  "organization": {
    "created_at": "2026-01-01T00:00:00Z",
    "dataplane_id": "<dataplane_id>",
    "description": "<description>",
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "<name>",
    "updated_at": "2026-01-01T00:00:00Z"
  },
  "organization_membership_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "role": "owner",
  "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "workspace_memberships": [
    {
      "role": "owner",
      "workspace": {
        "archived_at": "2026-01-01T00:00:00Z",
        "created_at": "2026-01-01T00:00:00Z",
        "dataplane_id": "<dataplane_id>",
        "dataplane_url": "<dataplane_url>",
        "description": "<description>",
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "is_playground": false,
        "name": "<name>",
        "organization_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "predefined_profiles": {},
        "updated_at": "2026-01-01T00:00:00Z"
      }
    }
  ]
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `email` | string | The email of the current user |
| `organization` | object | The organization. |
| `organization.created_at` | string (date-time) | When it was created. |
| `organization.dataplane_id` | string or null | Data plane of the organization's workspaces; `null` until a region is set. |
| `organization.description` | string or null | Free-text description of the organization; `null` when none is set. |
| `organization.id` | string (uuid) | Unique ID (a UUID). |
| `organization.name` | string | The name of the organization |
| `organization.updated_at` | string (date-time) | When it was last changed. |
| `organization_membership_id` | string (uuid) | The ID of the organization member for this organization |
| `role` | string | The role of the user in this organization One of: `owner`, `member`, `collaborator`. |
| `user_id` | string (uuid) | The ID of the current user |
| `workspace_memberships` | array of object | The workspaces you can access in this organization, with your role in each: those you belong to plus, unless you are a collaborator, those you reach through a workspace's default member role. Archived workspaces are not listed. |
| `workspace_memberships[].role` | string | The role of the user in the workspace One of: `owner`, `developer`, `viewer`. |
| `workspace_memberships[].workspace` | object | The workspace |
| `workspace_memberships[].workspace.archived_at` | string (date-time) or null | When the workspace was archived; `null` when live. An archived workspace rejects all changes except unarchiving, though owners can still manage its workspace variables. |
| `workspace_memberships[].workspace.created_at` | string (date-time) | When it was created. |
| `workspace_memberships[].workspace.dataplane_id` | string | Identifier of the data plane that holds this workspace's user data. Matches the subdomain of the plane's public entrypoint. |
| `workspace_memberships[].workspace.dataplane_url` | string | Base URL of the workspace's data plane, `https://<dataplane_id>.dlthub.com`, where the Dataplane, Logs and Telemetry APIs are served. Call them with a token from `POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token`. |
| `workspace_memberships[].workspace.description` | string or null | Free-text description of the workspace; `null` when none is set. |
| `workspace_memberships[].workspace.id` | string (uuid) | Unique ID (a UUID). |
| `workspace_memberships[].workspace.is_playground` | boolean | Whether this is the user's auto-created personal playground workspace. A playground has a single member and cannot be updated, archived, shared, or given API keys. Default: `false`. |
| `workspace_memberships[].workspace.name` | string | The name of the workspace |
| `workspace_memberships[].workspace.organization_id` | string (uuid) | The ID of the parent organization |
| `workspace_memberships[].workspace.predefined_profiles` | map of string | Built-in profiles by data access: `DATA_WRITE` → `prod` (runs need developer or owner), `DATA_READ` → `access` (viewers may run). |
| `workspace_memberships[].workspace.updated_at` | string (date-time) | When it was last changed. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Workspace me {#get_workspace_me}

```text
GET /api/v1/workspaces/{workspace_id}/me
```

Gets your context in a workspace: the workspace, your role in it, its organization and your
role and membership id there.

Also records this workspace as your latest, which `get_me` returns in `latest_workspace`.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/me" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/me",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "email": "<email>",
  "organization": {
    "created_at": "2026-01-01T00:00:00Z",
    "dataplane_id": "<dataplane_id>",
    "description": "<description>",
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "<name>",
    "updated_at": "2026-01-01T00:00:00Z"
  },
  "organization_membership_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "organization_role": "owner",
  "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "workspace": {
    "archived_at": "2026-01-01T00:00:00Z",
    "created_at": "2026-01-01T00:00:00Z",
    "dataplane_id": "<dataplane_id>",
    "dataplane_url": "<dataplane_url>",
    "description": "<description>",
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "is_playground": false,
    "name": "<name>",
    "organization_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "predefined_profiles": {},
    "updated_at": "2026-01-01T00:00:00Z"
  },
  "workspace_role": "owner"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `email` | string | The email of the current user |
| `organization` | object | The workspace's organization. |
| `organization.created_at` | string (date-time) | When it was created. |
| `organization.dataplane_id` | string or null | Data plane of the organization's workspaces; `null` until a region is set. |
| `organization.description` | string or null | Free-text description of the organization; `null` when none is set. |
| `organization.id` | string (uuid) | Unique ID (a UUID). |
| `organization.name` | string | The name of the organization |
| `organization.updated_at` | string (date-time) | When it was last changed. |
| `organization_membership_id` | string (uuid) | The ID of the organization member in the parent organization |
| `organization_role` | string | The role of the user in the parent organization One of: `owner`, `member`, `collaborator`. |
| `user_id` | string (uuid) | The ID of the current user |
| `workspace` | object | The workspace |
| `workspace.archived_at` | string (date-time) or null | When the workspace was archived; `null` when live. An archived workspace rejects all changes except unarchiving, though owners can still manage its workspace variables. |
| `workspace.created_at` | string (date-time) | When it was created. |
| `workspace.dataplane_id` | string | Identifier of the data plane that holds this workspace's user data. Matches the subdomain of the plane's public entrypoint. |
| `workspace.dataplane_url` | string | Base URL of the workspace's data plane, `https://<dataplane_id>.dlthub.com`, where the Dataplane, Logs and Telemetry APIs are served. Call them with a token from `POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token`. |
| `workspace.description` | string or null | Free-text description of the workspace; `null` when none is set. |
| `workspace.id` | string (uuid) | Unique ID (a UUID). |
| `workspace.is_playground` | boolean | Whether this is the user's auto-created personal playground workspace. A playground has a single member and cannot be updated, archived, shared, or given API keys. Default: `false`. |
| `workspace.name` | string | The name of the workspace |
| `workspace.organization_id` | string (uuid) | The ID of the parent organization |
| `workspace.predefined_profiles` | map of string | Built-in profiles by data access: `DATA_WRITE` → `prod` (runs need developer or owner), `DATA_READ` → `access` (viewers may run). |
| `workspace.updated_at` | string (date-time) | When it was last changed. |
| `workspace_role` | string | The role of the user in this workspace One of: `owner`, `developer`, `viewer`. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

## Organizations

### List organizations {#list_organizations}

```text
GET /api/v1/organizations
```

Lists all organizations the current user is a member of, as a paginated list.

Requires any valid credential; no particular role is needed.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Query parameters**

| Name | Type | Description |
| --- | --- | --- |
| `sort` | array of string or null | Keys to sort by, applied in the order given. Pairs positionally with `order`, which must have the same number of entries. One of: `created_at`, `name`. |

**Pagination:** `limit`, `offset`, `order`, `cursor`, see [Lists and pagination](overview.md#lists-and-pagination)

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/organizations" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/organizations",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "items": [
    {
      "created_at": "2026-01-01T00:00:00Z",
      "dataplane_id": "<dataplane_id>",
      "description": "<description>",
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "<name>",
      "updated_at": "2026-01-01T00:00:00Z"
    }
  ],
  "limit": 0,
  "next_cursor": "<next_cursor>",
  "offset": 0,
  "total": 0
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `items` | array of object | The rows of this page, in sort order. |
| `items[].created_at` | string (date-time) | When it was created. |
| `items[].dataplane_id` | string or null | Data plane of the organization's workspaces; `null` until a region is set. |
| `items[].description` | string or null | Free-text description of the organization; `null` when none is set. |
| `items[].id` | string (uuid) | Unique ID (a UUID). |
| `items[].name` | string | The name of the organization |
| `items[].updated_at` | string (date-time) | When it was last changed. |
| `limit` | integer | The page size this page was fetched with. |
| `next_cursor` | string or null | Cursor for the page after this one; null on the last. |
| `offset` | integer | How many rows were skipped before this page. |
| `total` | integer | Rows matching the filters, reported up to 10001. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Get organization {#get_organization}

```text
GET /api/v1/organizations/{organization_id}
```

Gets information about an organization.

Any organization member, including collaborators.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `organization_id` | string (uuid) | ID of the organization |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/organizations/$ORGANIZATION_ID" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/organizations/<organization_id>",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "created_at": "2026-01-01T00:00:00Z",
  "dataplane_id": "<dataplane_id>",
  "description": "<description>",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "<name>",
  "updated_at": "2026-01-01T00:00:00Z"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `created_at` | string (date-time) | When it was created. |
| `dataplane_id` | string or null | Data plane of the organization's workspaces; `null` until a region is set. |
| `description` | string or null | Free-text description of the organization; `null` when none is set. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `name` | string | The name of the organization |
| `updated_at` | string (date-time) | When it was last changed. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Set organization region {#set_region_organization}

```text
POST /api/v1/organizations/{organization_id}/-set-region
```

Sets the organization's region, its data plane. This is one-time and
irreversible: a second attempt returns 409. An unknown `dataplane_id` returns 400. List the
available data planes with `list_dataplanes`.

Requires the organization owner role.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `organization_id` | string (uuid) | ID of the organization |

  </TabItem>
  <TabItem value="body" label="Body">

| Field | Type | Description |
| --- | --- | --- |
| `dataplane_id` | string | **Required.** Data plane id for the organization's region. Cannot be changed once set. |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/organizations/$ORGANIZATION_ID/-set-region" \
  -H "Authorization: Bearer $DLTHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dataplane_id": "<dataplane_id>"}'
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/organizations/<organization_id>/-set-region",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
    json={"dataplane_id": "<dataplane_id>"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "created_at": "2026-01-01T00:00:00Z",
  "dataplane_id": "<dataplane_id>",
  "description": "<description>",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "<name>",
  "updated_at": "2026-01-01T00:00:00Z"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `created_at` | string (date-time) | When it was created. |
| `dataplane_id` | string or null | Data plane of the organization's workspaces; `null` until a region is set. |
| `description` | string or null | Free-text description of the organization; `null` when none is set. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `name` | string | The name of the organization |
| `updated_at` | string (date-time) | When it was last changed. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

## Workspaces

### List workspaces {#list_workspaces}

```text
GET /api/v1/organizations/{organization_id}/workspaces
```

Lists the workspaces under an organization visible to the caller, as a paginated list,
oldest first by default. Organization owners see every workspace; members see the workspaces
they belong to or reach through a workspace's default member role; collaborators see only
the workspaces they belong to.

Each workspace includes member stats: its owners and the count of active members.

Playground workspaces of other members are not included; the caller only sees their own.

Archived workspaces are excluded by default; pass `include_archived=true` to list them.

Any organization member, including collaborators.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `organization_id` | string (uuid) | ID of the organization |

**Query parameters**

| Name | Type | Description |
| --- | --- | --- |
| `sort` | array of string or null | Keys to sort by, applied in the order given. Pairs positionally with `order`, which must have the same number of entries. One of: `created_at`, `name`. |
| `include_archived` | boolean | Include archived workspaces. They are excluded unless set. |

**Pagination:** `limit`, `offset`, `order`, `cursor`, see [Lists and pagination](overview.md#lists-and-pagination)

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/organizations/$ORGANIZATION_ID/workspaces" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/organizations/<organization_id>/workspaces",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "items": [
    {
      "archived_at": "2026-01-01T00:00:00Z",
      "created_at": "2026-01-01T00:00:00Z",
      "dataplane_id": "<dataplane_id>",
      "dataplane_url": "<dataplane_url>",
      "description": "<description>",
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "is_playground": false,
      "member_count": 0,
      "name": "<name>",
      "organization_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "owner_members": [
        {
          "email": "<email>",
          "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "name": "<name>",
          "organization_membership_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "role": "owner",
          "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
        }
      ],
      "predefined_profiles": {},
      "updated_at": "2026-01-01T00:00:00Z"
    }
  ],
  "limit": 0,
  "next_cursor": "<next_cursor>",
  "offset": 0,
  "total": 0
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `items` | array of object | The rows of this page, in sort order. |
| `items[].archived_at` | string (date-time) or null | When the workspace was archived; `null` when live. An archived workspace rejects all changes except unarchiving, though owners can still manage its workspace variables. |
| `items[].created_at` | string (date-time) | When it was created. |
| `items[].dataplane_id` | string | Identifier of the data plane that holds this workspace's user data. Matches the subdomain of the plane's public entrypoint. |
| `items[].dataplane_url` | string | Base URL of the workspace's data plane, `https://<dataplane_id>.dlthub.com`, where the Dataplane, Logs and Telemetry APIs are served. Call them with a token from `POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token`. |
| `items[].description` | string or null | Free-text description of the workspace; `null` when none is set. |
| `items[].id` | string (uuid) | Unique ID (a UUID). |
| `items[].is_playground` | boolean | Whether this is the user's auto-created personal playground workspace. A playground has a single member and cannot be updated, archived, shared, or given API keys. Default: `false`. |
| `items[].member_count` | integer | Number of active human members in the workspace. |
| `items[].name` | string | The name of the workspace |
| `items[].organization_id` | string (uuid) | The ID of the parent organization |
| `items[].owner_members` | array of object | The workspace's active human owners, in the order they joined. |
| `items[].owner_members[].email` | string | The email of the user |
| `items[].owner_members[].id` | string (uuid) | The ID of the workspace membership |
| `items[].owner_members[].name` | string or null | Display name; `null` when the user has no name set. |
| `items[].owner_members[].organization_membership_id` | string (uuid) | The ID of the member's organization membership, which `created_by` and `triggered_by` hold |
| `items[].owner_members[].role` | string | The role of the user in the workspace One of: `owner`, `developer`, `viewer`. |
| `items[].owner_members[].user_id` | string (uuid) | The ID of the user |
| `items[].predefined_profiles` | map of string | Built-in profiles by data access: `DATA_WRITE` → `prod` (runs need developer or owner), `DATA_READ` → `access` (viewers may run). |
| `items[].updated_at` | string (date-time) | When it was last changed. |
| `limit` | integer | The page size this page was fetched with. |
| `next_cursor` | string or null | Cursor for the page after this one; null on the last. |
| `offset` | integer | How many rows were skipped before this page. |
| `total` | integer | Rows matching the filters, reported up to 10001. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Create workspace {#create_workspace}

```text
POST /api/v1/organizations/{organization_id}/workspaces
```

Creates a workspace in the organization; the caller becomes its owner. Send both `name` and
`description` (`description` may be `null`). Answers 409 until the organization's region is
set with `set_region_organization`.

Requires the organization member or owner role.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `organization_id` | string (uuid) | ID of the organization |

  </TabItem>
  <TabItem value="body" label="Body">

| Field | Type | Description |
| --- | --- | --- |
| `description` | string or null | **Required.** Free-text description of the workspace; `null` when none is set. |
| `name` | string | **Required.** The name of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/organizations/$ORGANIZATION_ID/workspaces" \
  -H "Authorization: Bearer $DLTHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"description": "<description>", "name": "<name>"}'
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/organizations/<organization_id>/workspaces",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
    json={"description": "<description>", "name": "<name>"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>201</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "archived_at": "2026-01-01T00:00:00Z",
  "created_at": "2026-01-01T00:00:00Z",
  "dataplane_id": "<dataplane_id>",
  "dataplane_url": "<dataplane_url>",
  "description": "<description>",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "is_playground": false,
  "name": "<name>",
  "organization_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "predefined_profiles": {},
  "updated_at": "2026-01-01T00:00:00Z"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `archived_at` | string (date-time) or null | When the workspace was archived; `null` when live. An archived workspace rejects all changes except unarchiving, though owners can still manage its workspace variables. |
| `created_at` | string (date-time) | When it was created. |
| `dataplane_id` | string | Identifier of the data plane that holds this workspace's user data. Matches the subdomain of the plane's public entrypoint. |
| `dataplane_url` | string | Base URL of the workspace's data plane, `https://<dataplane_id>.dlthub.com`, where the Dataplane, Logs and Telemetry APIs are served. Call them with a token from `POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token`. |
| `description` | string or null | Free-text description of the workspace; `null` when none is set. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `is_playground` | boolean | Whether this is the user's auto-created personal playground workspace. A playground has a single member and cannot be updated, archived, shared, or given API keys. Default: `false`. |
| `name` | string | The name of the workspace |
| `organization_id` | string (uuid) | The ID of the parent organization |
| `predefined_profiles` | map of string | Built-in profiles by data access: `DATA_WRITE` → `prod` (runs need developer or owner), `DATA_READ` → `access` (viewers may run). |
| `updated_at` | string (date-time) | When it was last changed. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Get workspace {#get_workspace}

```text
GET /api/v1/workspaces/{workspace_id}
```

Gets information about a workspace.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "archived_at": "2026-01-01T00:00:00Z",
  "created_at": "2026-01-01T00:00:00Z",
  "dataplane_id": "<dataplane_id>",
  "dataplane_url": "<dataplane_url>",
  "description": "<description>",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "is_playground": false,
  "name": "<name>",
  "organization_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "predefined_profiles": {},
  "updated_at": "2026-01-01T00:00:00Z"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `archived_at` | string (date-time) or null | When the workspace was archived; `null` when live. An archived workspace rejects all changes except unarchiving, though owners can still manage its workspace variables. |
| `created_at` | string (date-time) | When it was created. |
| `dataplane_id` | string | Identifier of the data plane that holds this workspace's user data. Matches the subdomain of the plane's public entrypoint. |
| `dataplane_url` | string | Base URL of the workspace's data plane, `https://<dataplane_id>.dlthub.com`, where the Dataplane, Logs and Telemetry APIs are served. Call them with a token from `POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token`. |
| `description` | string or null | Free-text description of the workspace; `null` when none is set. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `is_playground` | boolean | Whether this is the user's auto-created personal playground workspace. A playground has a single member and cannot be updated, archived, shared, or given API keys. Default: `false`. |
| `name` | string | The name of the workspace |
| `organization_id` | string (uuid) | The ID of the parent organization |
| `predefined_profiles` | map of string | Built-in profiles by data access: `DATA_WRITE` → `prod` (runs need developer or owner), `DATA_READ` → `access` (viewers may run). |
| `updated_at` | string (date-time) | When it was last changed. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Update workspace {#update_workspace}

```text
PATCH /api/v1/workspaces/{workspace_id}
```

Updates the information for a workspace.

Answers 400 for a playground workspace and 409 for an archived one.

Requires the workspace owner role.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="body" label="Body">

| Field | Type | Description |
| --- | --- | --- |
| `description` | string or null | The description of the workspace |
| `name` | string or null | The name of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X PATCH "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID" \
  -H "Authorization: Bearer $DLTHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"description": "<description>", "name": "<name>"}'
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.patch(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
    json={"description": "<description>", "name": "<name>"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "archived_at": "2026-01-01T00:00:00Z",
  "created_at": "2026-01-01T00:00:00Z",
  "dataplane_id": "<dataplane_id>",
  "dataplane_url": "<dataplane_url>",
  "description": "<description>",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "is_playground": false,
  "name": "<name>",
  "organization_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "predefined_profiles": {},
  "updated_at": "2026-01-01T00:00:00Z"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `archived_at` | string (date-time) or null | When the workspace was archived; `null` when live. An archived workspace rejects all changes except unarchiving, though owners can still manage its workspace variables. |
| `created_at` | string (date-time) | When it was created. |
| `dataplane_id` | string | Identifier of the data plane that holds this workspace's user data. Matches the subdomain of the plane's public entrypoint. |
| `dataplane_url` | string | Base URL of the workspace's data plane, `https://<dataplane_id>.dlthub.com`, where the Dataplane, Logs and Telemetry APIs are served. Call them with a token from `POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token`. |
| `description` | string or null | Free-text description of the workspace; `null` when none is set. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `is_playground` | boolean | Whether this is the user's auto-created personal playground workspace. A playground has a single member and cannot be updated, archived, shared, or given API keys. Default: `false`. |
| `name` | string | The name of the workspace |
| `organization_id` | string (uuid) | The ID of the parent organization |
| `predefined_profiles` | map of string | Built-in profiles by data access: `DATA_WRITE` → `prod` (runs need developer or owner), `DATA_READ` → `access` (viewers may run). |
| `updated_at` | string (date-time) | When it was last changed. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Archive workspace {#archive_workspace}

```text
POST /api/v1/workspaces/{workspace_id}/-archive
```

Archives a workspace: clears schedules and rejects further changes until unarchived.
Answers 409 if the workspace is already archived or any run is still active, 400 for a
playground.

Requires the workspace owner role.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/-archive" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/-archive",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "archived_at": "2026-01-01T00:00:00Z",
  "created_at": "2026-01-01T00:00:00Z",
  "dataplane_id": "<dataplane_id>",
  "dataplane_url": "<dataplane_url>",
  "description": "<description>",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "is_playground": false,
  "name": "<name>",
  "organization_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "predefined_profiles": {},
  "updated_at": "2026-01-01T00:00:00Z"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `archived_at` | string (date-time) or null | When the workspace was archived; `null` when live. An archived workspace rejects all changes except unarchiving, though owners can still manage its workspace variables. |
| `created_at` | string (date-time) | When it was created. |
| `dataplane_id` | string | Identifier of the data plane that holds this workspace's user data. Matches the subdomain of the plane's public entrypoint. |
| `dataplane_url` | string | Base URL of the workspace's data plane, `https://<dataplane_id>.dlthub.com`, where the Dataplane, Logs and Telemetry APIs are served. Call them with a token from `POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token`. |
| `description` | string or null | Free-text description of the workspace; `null` when none is set. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `is_playground` | boolean | Whether this is the user's auto-created personal playground workspace. A playground has a single member and cannot be updated, archived, shared, or given API keys. Default: `false`. |
| `name` | string | The name of the workspace |
| `organization_id` | string (uuid) | The ID of the parent organization |
| `predefined_profiles` | map of string | Built-in profiles by data access: `DATA_WRITE` → `prod` (runs need developer or owner), `DATA_READ` → `access` (viewers may run). |
| `updated_at` | string (date-time) | When it was last changed. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Deploy {#deploy_workspace}

```text
POST /api/v1/workspaces/{workspace_id}/-deploy
```

Deploys a set of job definitions from a deployment manifest. Creates new jobs,
updates existing ones, and archives orphaned jobs (those previously deployed from
the same `deployment_module` but no longer present in the manifest).

Answers with one receipt per kind the manifest declares. Under `job_receipt`, `succeeded`
lists each job created, updated or archived with its `outcome` and `skipped` the unchanged
ones; a deploy is all or nothing, so `failed` is always empty.

When `deployment_module` is `null`, this is an ad-hoc deploy: jobs are only added or
updated, never archived. When `deployment_module` is `__deployment__`, the workspace
description is also updated from the manifest.

With `dry_run`, the changes are previewed without being saved; created and updated jobs then
carry the zero UUID as `id` and version 0.

Requires the developer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="body" label="Body">

| Field | Type | Description |
| --- | --- | --- |
| `deployment_module` | string or null | Deployment module the jobs come from, e.g. `__deployment__` for a standard deploy. `null` makes an ad-hoc deploy, which never archives jobs. |
| `description` | string or null | Workspace description, e.g. from the `__deployment__` module docstring. Applied only when `deployment_module` is `__deployment__`. |
| `dry_run` | boolean | Preview the changes without saving them. Created and updated jobs then carry the zero UUID as `id` and version 0. Default: `false`. |
| `job_definition_engine_version` | integer | **Required.** Not used; send the value `dlt` produces. |
| `job_definition_hash` | string | **Required.** Not used; send the value `dlt` produces. |
| `jobs` | array of map of any | **Required.** Job definitions from the deployment manifest. |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/-deploy" \
  -H "Authorization: Bearer $DLTHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "job_definition_engine_version": 0,
    "job_definition_hash": "<job_definition_hash>",
    "jobs": []
  }'
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/-deploy",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
    json={
        "job_definition_engine_version": 0,
        "job_definition_hash": "<job_definition_hash>",
        "jobs": [],
    },
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "deployment_module": "<deployment_module>",
  "job_receipt": {
    "failed": [
      {
        "detail": "<detail>",
        "ref": "<ref>"
      }
    ],
    "skipped": [
      {
        "job": {},
        "outcome": "created",
        "previous_version": {}
      }
    ],
    "succeeded": [
      {
        "job": {},
        "outcome": "created",
        "previous_version": {}
      }
    ]
  },
  "warnings": ["<warnings>"]
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `deployment_module` | string or null | The deployment module the deploy was scoped to. |
| `job_receipt` | object | What a deploy did to the workspace's jobs. |
| `job_receipt.failed` | array of object | Always empty: a deploy is all or nothing, so a failure answers with an error instead. |
| `job_receipt.failed[].detail` | string | Why it failed. |
| `job_receipt.failed[].ref` | string | The item that failed, by its id or natural key. |
| `job_receipt.skipped` | array of object | Jobs with no changes. |
| `job_receipt.skipped[].job` | object | The job as it stands after the deploy. |
| `job_receipt.skipped[].job.created_at` | string (date-time) | When it was created. |
| `job_receipt.skipped[].job.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `job_receipt.skipped[].job.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `job_receipt.skipped[].job.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `job_receipt.skipped[].job.description` | string or null | The description of the job |
| `job_receipt.skipped[].job.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `job_receipt.skipped[].job.id` | string (uuid) | Unique ID (a UUID). |
| `job_receipt.skipped[].job.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `job_receipt.skipped[].job.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `job_receipt.skipped[].job.is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `job_receipt.skipped[].job.is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job_receipt.skipped[].job.job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_receipt.skipped[].job.job_definition_engine_version` | integer | Version of the job definition format. |
| `job_receipt.skipped[].job.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_receipt.skipped[].job.job_ref` | string | Canonical job reference, unique per workspace |
| `job_receipt.skipped[].job.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_receipt.skipped[].job.job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `job_receipt.skipped[].job.name` | string or null | Display name for the job |
| `job_receipt.skipped[].job.next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `job_receipt.skipped[].job.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `job_receipt.skipped[].job.profile` | string | The profile the job runs under |
| `job_receipt.skipped[].job.public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `job_receipt.skipped[].job.public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `job_receipt.skipped[].job.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `job_receipt.skipped[].job.updated_at` | string (date-time) | When it was last changed. |
| `job_receipt.skipped[].job.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `job_receipt.skipped[].job.workspace_id` | string (uuid) | The ID of the workspace the job belongs to |
| `job_receipt.skipped[].outcome` | string | What the deploy did to the job. One of: `created`, `updated`, `archived`, `unchanged`. |
| `job_receipt.skipped[].previous_version` | object or null | The version an updated job had before the deploy. |
| `job_receipt.skipped[].previous_version.created_at` | string (date-time) | When it was created. |
| `job_receipt.skipped[].previous_version.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `job_receipt.skipped[].previous_version.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `job_receipt.skipped[].previous_version.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `job_receipt.skipped[].previous_version.description` | string or null | The description of the job |
| `job_receipt.skipped[].previous_version.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `job_receipt.skipped[].previous_version.id` | string (uuid) | Unique ID (a UUID). |
| `job_receipt.skipped[].previous_version.instance_size` | string or null | Instance size this version runs on; `null` on a provider without instance sizing. One of: `small`, `medium`, `large`, `xlarge`. |
| `job_receipt.skipped[].previous_version.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `job_receipt.skipped[].previous_version.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `job_receipt.skipped[].previous_version.job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_receipt.skipped[].previous_version.job_definition_engine_version` | integer | Version of the job definition format. |
| `job_receipt.skipped[].previous_version.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_receipt.skipped[].previous_version.job_id` | string (uuid) | The ID of the job the job version belongs to |
| `job_receipt.skipped[].previous_version.job_ref` | string | Canonical job reference, unique per workspace |
| `job_receipt.skipped[].previous_version.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_receipt.skipped[].previous_version.max_run_time_seconds` | integer | Maximum allowed run duration in seconds for this job version |
| `job_receipt.skipped[].previous_version.memory_mib` | integer or null | Resolved memory in MiB; `null` when the version has no sized hardware. |
| `job_receipt.skipped[].previous_version.name` | string or null | Display name for the job |
| `job_receipt.skipped[].previous_version.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `job_receipt.skipped[].previous_version.profile` | string | The profile the job runs under |
| `job_receipt.skipped[].previous_version.provider` | string | Run provider for runs using this job version (e.g. 'tower', 'modal', 'local') One of: `tower`, `modal`, `local`. |
| `job_receipt.skipped[].previous_version.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `job_receipt.skipped[].previous_version.updated_at` | string (date-time) | When it was last changed. |
| `job_receipt.skipped[].previous_version.vcpu` | number or null | Resolved virtual CPU cores; `null` when the version has no sized hardware. |
| `job_receipt.skipped[].previous_version.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `job_receipt.succeeded` | array of object | Jobs created, updated or archived. |
| `job_receipt.succeeded[].job` | object | The job as it stands after the deploy. |
| `job_receipt.succeeded[].job.created_at` | string (date-time) | When it was created. |
| `job_receipt.succeeded[].job.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `job_receipt.succeeded[].job.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `job_receipt.succeeded[].job.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `job_receipt.succeeded[].job.description` | string or null | The description of the job |
| `job_receipt.succeeded[].job.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `job_receipt.succeeded[].job.id` | string (uuid) | Unique ID (a UUID). |
| `job_receipt.succeeded[].job.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `job_receipt.succeeded[].job.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `job_receipt.succeeded[].job.is_archived` | boolean | Whether the job is archived. Archived jobs cannot be run and their public URL stops working. Default: `false`. |
| `job_receipt.succeeded[].job.is_paused` | boolean | Whether the job's scheduled runs are paused. The scheduler does not start it, including a scheduled run that was waiting for its upstream jobs; manual runs, runs started by a trigger request, and runs started by another job's success or failure still run. Only a job with `next_scheduled_run_at` can be paused, and a deploy that removes the schedule clears the pause. Default: `false`. |
| `job_receipt.succeeded[].job.job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_receipt.succeeded[].job.job_definition_engine_version` | integer | Version of the job definition format. |
| `job_receipt.succeeded[].job.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_receipt.succeeded[].job.job_ref` | string | Canonical job reference, unique per workspace |
| `job_receipt.succeeded[].job.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_receipt.succeeded[].job.job_url` | string or null | The URL of the job in the web app for an interactive job; `null` otherwise. |
| `job_receipt.succeeded[].job.name` | string or null | Display name for the job |
| `job_receipt.succeeded[].job.next_scheduled_run_at` | string (date-time) or null | When the job is next scheduled to run; `null` if it has no schedule. |
| `job_receipt.succeeded[].job.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `job_receipt.succeeded[].job.profile` | string | The profile the job runs under |
| `job_receipt.succeeded[].job.public_secret` | string (uuid) or null | The secret in the job's public URL; `null` when the job is not published. |
| `job_receipt.succeeded[].job.public_url` | string or null | The public URL where the job can be accessed without authentication; `null` when the job is not published. It does not work while the job is archived. |
| `job_receipt.succeeded[].job.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `job_receipt.succeeded[].job.updated_at` | string (date-time) | When it was last changed. |
| `job_receipt.succeeded[].job.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `job_receipt.succeeded[].job.workspace_id` | string (uuid) | The ID of the workspace the job belongs to |
| `job_receipt.succeeded[].outcome` | string | What the deploy did to the job. One of: `created`, `updated`, `archived`, `unchanged`. |
| `job_receipt.succeeded[].previous_version` | object or null | The version an updated job had before the deploy. |
| `job_receipt.succeeded[].previous_version.created_at` | string (date-time) | When it was created. |
| `job_receipt.succeeded[].previous_version.created_by` | string (uuid) | The `organization_membership_id` of the member who created the job. |
| `job_receipt.succeeded[].previous_version.default_trigger` | string or null | The trigger fired when a run is started without naming one, computed from the job definition. |
| `job_receipt.succeeded[].previous_version.deployment_module` | string or null | Deployment module the job was deployed from, e.g. `__deployment__`; `null` for a job deployed outside a module. |
| `job_receipt.succeeded[].previous_version.description` | string or null | The description of the job |
| `job_receipt.succeeded[].previous_version.freshness` | array of string or null | Freshness constraint strings (computed from job_definition) |
| `job_receipt.succeeded[].previous_version.id` | string (uuid) | Unique ID (a UUID). |
| `job_receipt.succeeded[].previous_version.instance_size` | string or null | Instance size this version runs on; `null` on a provider without instance sizing. One of: `small`, `medium`, `large`, `xlarge`. |
| `job_receipt.succeeded[].previous_version.interval_end` | string (date-time) or null | Upper bound of the job's work window, from `interval.end` in its definition; `null` when there is no upper bound. The schedule stops once the next run would pass it. |
| `job_receipt.succeeded[].previous_version.interval_start` | string (date-time) or null | Lower bound of the job's work window, from `interval.start` in its definition; `null` when the job has no bounded window. |
| `job_receipt.succeeded[].previous_version.job_definition` | object | Job definition from the dlt deployment manifest. |
| `job_receipt.succeeded[].previous_version.job_definition_engine_version` | integer | Version of the job definition format. |
| `job_receipt.succeeded[].previous_version.job_definition_hash` | string | Hash of the job definition; a deploy compares it to detect changes. |
| `job_receipt.succeeded[].previous_version.job_id` | string (uuid) | The ID of the job the job version belongs to |
| `job_receipt.succeeded[].previous_version.job_ref` | string | Canonical job reference, unique per workspace |
| `job_receipt.succeeded[].previous_version.job_type` | string | The type of the job: batch, interactive, or stream One of: `batch`, `interactive`, `stream`. |
| `job_receipt.succeeded[].previous_version.max_run_time_seconds` | integer | Maximum allowed run duration in seconds for this job version |
| `job_receipt.succeeded[].previous_version.memory_mib` | integer or null | Resolved memory in MiB; `null` when the version has no sized hardware. |
| `job_receipt.succeeded[].previous_version.name` | string or null | Display name for the job |
| `job_receipt.succeeded[].previous_version.pipeline_name` | string or null | Pipeline name this job operates on (computed from job_definition) |
| `job_receipt.succeeded[].previous_version.profile` | string | The profile the job runs under |
| `job_receipt.succeeded[].previous_version.provider` | string | Run provider for runs using this job version (e.g. 'tower', 'modal', 'local') One of: `tower`, `modal`, `local`. |
| `job_receipt.succeeded[].previous_version.triggers` | array of string or null | Every trigger the job answers to, computed from its definition; selectors match against these. |
| `job_receipt.succeeded[].previous_version.updated_at` | string (date-time) | When it was last changed. |
| `job_receipt.succeeded[].previous_version.vcpu` | number or null | Resolved virtual CPU cores; `null` when the version has no sized hardware. |
| `job_receipt.succeeded[].previous_version.version` | integer | Version number, starting at 1 and incremented by each update to the job: a job's current version, or a job version's own number. |
| `warnings` | array of string | Always empty at present. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Issue dataplane token {#issue_dataplane_token_workspace}

```text
POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token
```

Issues a data plane token for this workspace, valid for 10 minutes. Send it as
`Authorization: Bearer <token>` to the services at the workspace's `dataplane_url`.

Viewers and developers can read files, logs and telemetry; owners can also read and change
workspace variables, even on an archived workspace. Cache the token and request a new one
before `expires_at`.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/-issue-dataplane-token" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/-issue-dataplane-token",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "expires_at": 0,
  "token": "<token>"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `expires_at` | integer | Unix timestamp (seconds) when the token expires. Clients should refresh before this moment. |
| `token` | string | Data plane token; send it as `Authorization: Bearer <token>`. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### Unarchive workspace {#unarchive_workspace}

```text
POST /api/v1/workspaces/{workspace_id}/-unarchive
```

Unarchives a workspace, restoring write access and recomputing schedules.
Answers 409 if the workspace is not archived.

Requires the workspace owner role.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl -X POST "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/-unarchive" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.post(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/-unarchive",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "archived_at": "2026-01-01T00:00:00Z",
  "created_at": "2026-01-01T00:00:00Z",
  "dataplane_id": "<dataplane_id>",
  "dataplane_url": "<dataplane_url>",
  "description": "<description>",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "is_playground": false,
  "name": "<name>",
  "organization_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "predefined_profiles": {},
  "updated_at": "2026-01-01T00:00:00Z"
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `archived_at` | string (date-time) or null | When the workspace was archived; `null` when live. An archived workspace rejects all changes except unarchiving, though owners can still manage its workspace variables. |
| `created_at` | string (date-time) | When it was created. |
| `dataplane_id` | string | Identifier of the data plane that holds this workspace's user data. Matches the subdomain of the plane's public entrypoint. |
| `dataplane_url` | string | Base URL of the workspace's data plane, `https://<dataplane_id>.dlthub.com`, where the Dataplane, Logs and Telemetry APIs are served. Call them with a token from `POST /api/v1/workspaces/{workspace_id}/-issue-dataplane-token`. |
| `description` | string or null | Free-text description of the workspace; `null` when none is set. |
| `id` | string (uuid) | Unique ID (a UUID). |
| `is_playground` | boolean | Whether this is the user's auto-created personal playground workspace. A playground has a single member and cannot be updated, archived, shared, or given API keys. Default: `false`. |
| `name` | string | The name of the workspace |
| `organization_id` | string (uuid) | The ID of the parent organization |
| `predefined_profiles` | map of string | Built-in profiles by data access: `DATA_WRITE` → `prod` (runs need developer or owner), `DATA_READ` → `access` (viewers may run). |
| `updated_at` | string (date-time) | When it was last changed. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">409</code> Conflict
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>

### List workspace members {#list_workspace_members}

```text
GET /api/v1/workspaces/{workspace_id}/members
```

Lists a workspace's direct members as a paginated list, oldest first. Workspace API keys are
listed too, with `kind=service_account`; pass one or more `kind` values to filter.
Organization owners and members who reach the workspace only through its default member
role are not listed.

Requires the viewer role (or higher) in the workspace.

<div class="api-details">
<details>
<summary>Request</summary>

<Tabs groupId="api-request">
  <TabItem value="parameters" label="Parameters">

**Path parameters**

| Name | Type | Description |
| --- | --- | --- |
| `workspace_id` | string (uuid) | ID of the workspace |

**Query parameters**

| Name | Type | Description |
| --- | --- | --- |
| `kind` | array of string or null | Exact match on `kind`. Repeat the parameter to match any of several values. One of: `human`, `service_account`, `managed`. |
| `sort` | array of string or null | Keys to sort by, applied in the order given. Pairs positionally with `order`, which must have the same number of entries. One of: `created_at`, `email`. |

**Pagination:** `limit`, `offset`, `order`, `cursor`, see [Lists and pagination](overview.md#lists-and-pagination)

  </TabItem>
  <TabItem value="curl" label="curl">

```sh
curl "https://api.dlthub.com/api/v1/workspaces/$WORKSPACE_ID/members" \
  -H "Authorization: Bearer $DLTHUB_API_KEY"
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import os

import requests

response = requests.get(
    "https://api.dlthub.com/api/v1/workspaces/<workspace_id>/members",
    headers={"Authorization": f"Bearer {os.environ['DLTHUB_API_KEY']}"},
)
response.raise_for_status()
print(response.json())
```

  </TabItem>
</Tabs>

</details>
</div>

<div class="api-details">
<details>
<summary>Response (<code>200</code>, <code className="api-error">4xx</code>, <code className="api-error">5xx</code>)</summary>

<Tabs groupId="api-response">
  <TabItem value="example" label="Example">

```json
{
  "items": [
    {
      "email": "<email>",
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "<name>",
      "organization_membership_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "role": "owner",
      "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    }
  ],
  "limit": 0,
  "next_cursor": "<next_cursor>",
  "offset": 0,
  "total": 0
}
```

  </TabItem>
  <TabItem value="fields" label="Fields">

| Field | Type | Description |
| --- | --- | --- |
| `items` | array of object | The rows of this page, in sort order. |
| `items[].email` | string | The email of the user |
| `items[].id` | string (uuid) | The ID of the workspace membership |
| `items[].name` | string or null | Display name; `null` when the user has no name set. |
| `items[].organization_membership_id` | string (uuid) | The ID of the member's organization membership, which `created_by` and `triggered_by` hold |
| `items[].role` | string | The role of the user in the workspace One of: `owner`, `developer`, `viewer`. |
| `items[].user_id` | string (uuid) | The ID of the user |
| `limit` | integer | The page size this page was fetched with. |
| `next_cursor` | string or null | Cursor for the page after this one; null on the last. |
| `offset` | integer | How many rows were skipped before this page. |
| `total` | integer | Rows matching the filters, reported up to 10001. |

  </TabItem>
  <TabItem value="errors" label="Errors">

- <code className="api-error">400</code> Bad Request
- <code className="api-error">401</code> Unauthorized
- <code className="api-error">403</code> Forbidden
- <code className="api-error">404</code> Not Found
- <code className="api-error">500</code> Internal Server Error

Each error returns the [error body](overview.md#errors).

  </TabItem>
</Tabs>

</details>
</div>
