Dataplane API
Workspace data kept on the data plane: workspace variables and the files of uploaded deployments and configurations.
Base URL and authentication
Each workspace keeps its data on the data plane of its region. Its services are not on
api.dlthub.com and do not accept API keys. To call them:
- Read
dataplane_urlfromGET https://api.dlthub.com/api/v1/workspaces/{workspace_id}. This is the base URL of every data plane service: Dataplane, Logs and Telemetry API. - Exchange your API key for a data plane token with
POST https://api.dlthub.com/api/v1/workspaces/{workspace_id}/-issue-dataplane-token. The token is valid for one workspace untilexpires_at, currently 10 minutes after it was issued; request a new one before then. - Call the service at
dataplane_urlwithAuthorization: Bearer <token>.
Every workspace member can read logs, telemetry and file lists. Only workspace owners can read
or change variables, so workspace API keys, which are DEVELOPER or VIEWER, cannot. Uploads use
a separate token: POST .../deployments/-issue-upload (or /configurations/-issue-upload)
returns an upload_token and the upload_url to send the files to, both valid for one upload
and 10 minutes.
The data planes today:
| Region | dataplane_url |
|---|---|
Europe (eu1, europe-west4) | https://eu1.dlthub.com |
US East (us1, us-east4) | https://us1.dlthub.com |
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.
Files
Get configuration files manifest
GET /api/v1/workspaces/{workspace_id}/configurations/{configuration_id}/files
Lists every file in an uploaded configuration: path, size and SHA3-256 digest. Any workspace member can call it. Answers 404 when the workspace has no configuration with this id. To get the latest configuration's id, call GET https://api.dlthub.com/api/v1/workspaces/{workspace_id}/configurations/latest.
Request
- Parameters
- curl
- Python
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_id | string (uuid) | ID of the workspace |
configuration_id | string (uuid) | ID of the configuration |
curl "$DATAPLANE_URL/api/v1/workspaces/$WORKSPACE_ID/configurations/$CONFIGURATION_ID/files" \
-H "Authorization: Bearer $DATAPLANE_TOKEN"
import os
import requests
response = requests.get(
"<dataplane_url>/api/v1/workspaces/<workspace_id>/configurations/<configuration_id>/files",
headers={"Authorization": f"Bearer {os.environ['DATAPLANE_TOKEN']}"},
)
response.raise_for_status()
print(response.json())
Response (200, 4xx, 5xx)
- Example
- Fields
- Errors
{
"engine_version": 0,
"files": [
{
"linkname": "<linkname>",
"relative_path": "<relative_path>",
"sha3_256": "<sha3_256>",
"size_bytes": 0
}
]
}
| Field | Type | Description |
|---|---|---|
engine_version | integer | Version of the manifest format. |
files | array of object | The files in the package, sorted by relative_path. |
files[].linkname | string or null | Symlink target; set for symlink entries only. |
files[].relative_path | string | Path of the file inside the package, with / separators: relative to the workspace root for a deployment, and to the .dlt settings folder for a configuration. |
files[].sha3_256 | string | Base64 SHA3-256 of the file contents, or of the linkname string for a symlink. |
files[].size_bytes | integer | Size of the file in bytes; 0 for a symlink. |
400Bad Request401Unauthorized403Forbidden404Not Found500Internal Server Error
Each error returns the error body.
Get deployment files manifest
GET /api/v1/workspaces/{workspace_id}/deployments/{deployment_id}/files
Lists every file in an uploaded deployment: path, size and SHA3-256 digest. Any workspace member can call it. Answers 404 when the workspace has no deployment with this id. To get the latest deployment's id, call GET https://api.dlthub.com/api/v1/workspaces/{workspace_id}/deployments/latest.
Request
- Parameters
- curl
- Python
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_id | string (uuid) | ID of the workspace |
deployment_id | string (uuid) | ID of the deployment |
curl "$DATAPLANE_URL/api/v1/workspaces/$WORKSPACE_ID/deployments/$DEPLOYMENT_ID/files" \
-H "Authorization: Bearer $DATAPLANE_TOKEN"
import os
import requests
response = requests.get(
"<dataplane_url>/api/v1/workspaces/<workspace_id>/deployments/<deployment_id>/files",
headers={"Authorization": f"Bearer {os.environ['DATAPLANE_TOKEN']}"},
)
response.raise_for_status()
print(response.json())
Response (200, 4xx, 5xx)
- Example
- Fields
- Errors
{
"engine_version": 0,
"files": [
{
"linkname": "<linkname>",
"relative_path": "<relative_path>",
"sha3_256": "<sha3_256>",
"size_bytes": 0
}
]
}
| Field | Type | Description |
|---|---|---|
engine_version | integer | Version of the manifest format. |
files | array of object | The files in the package, sorted by relative_path. |
files[].linkname | string or null | Symlink target; set for symlink entries only. |
files[].relative_path | string | Path of the file inside the package, with / separators: relative to the workspace root for a deployment, and to the .dlt settings folder for a configuration. |
files[].sha3_256 | string | Base64 SHA3-256 of the file contents, or of the linkname string for a symlink. |
files[].size_bytes | integer | Size of the file in bytes; 0 for a symlink. |
400Bad Request401Unauthorized403Forbidden404Not Found500Internal Server Error
Each error returns the error body.
Uploads
Upload configuration bytes
POST /api/v1/workspaces/{workspace_id}/configurations/{configuration_id}/-upload
Uploads the configuration package for the id you got from configurations/-issue-upload, using that call's upload_token. Profiles are read from <profile>.config.toml and <profile>.secrets.toml file names. On success the configuration is created and returned. A package that cannot be read answers 400; a request body over 10 MB answers 413.
Request
- Parameters
- Body
- curl
- Python
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_id | string (uuid) | ID of the workspace |
configuration_id | string (uuid) | ID of the configuration |
Sent as multipart/form-data.
| Field | Type | Description |
|---|---|---|
file | string (binary) |
curl -X POST "$DATAPLANE_URL/api/v1/workspaces/$WORKSPACE_ID/configurations/$CONFIGURATION_ID/-upload" \
-H "Authorization: Bearer $UPLOAD_TOKEN" \
-F "file=@<file>"
import os
import requests
response = requests.post(
"<dataplane_url>/api/v1/workspaces/<workspace_id>/configurations/<configuration_id>/-upload",
headers={"Authorization": f"Bearer {os.environ['UPLOAD_TOKEN']}"},
files={"file": open("<file>", "rb")},
)
response.raise_for_status()
print(response.json())
Response (200, 4xx, 5xx)
- Example
- Fields
- Errors
{
"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"
}
| 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. |
400Bad Request401Unauthorized403Forbidden413Request Entity Too Large500Internal Server Error
Each error returns the error body.
Upload deployment bytes
POST /api/v1/workspaces/{workspace_id}/deployments/{deployment_id}/-upload
Uploads the package for the deployment id you got from deployments/-issue-upload, using that call's upload_token. file is the tar.gz package built by dlt (it must contain manifest.yaml). requirements_file is the workspace requirements manifest. On success the deployment is created and returned. A package that cannot be read answers 400; a request body over 10 MB answers 413.
Request
- Parameters
- Body
- curl
- Python
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_id | string (uuid) | ID of the workspace |
deployment_id | string (uuid) | ID of the deployment |
Sent as multipart/form-data.
| Field | Type | Description |
|---|---|---|
file | string (binary) | Required. The deployment package: a tar.gz built by dlt that contains manifest.yaml. |
requirements_file | string (binary) | Required. The workspace requirements manifest. |
curl -X POST "$DATAPLANE_URL/api/v1/workspaces/$WORKSPACE_ID/deployments/$DEPLOYMENT_ID/-upload" \
-H "Authorization: Bearer $UPLOAD_TOKEN" \
-F "file=@<file>" \
-F "requirements_file=@<requirements_file>"
import os
import requests
response = requests.post(
"<dataplane_url>/api/v1/workspaces/<workspace_id>/deployments/<deployment_id>/-upload",
headers={"Authorization": f"Bearer {os.environ['UPLOAD_TOKEN']}"},
files={"file": open("<file>", "rb"), "requirements_file": open("<requirements_file>", "rb")},
)
response.raise_for_status()
print(response.json())
Response (200, 4xx, 5xx)
- Example
- Fields
- Errors
{
"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"
}
| 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. |
400Bad Request401Unauthorized403Forbidden413Request Entity Too Large500Internal Server Error
Each error returns the error body.
Variables
List workspace variables
GET /api/v1/workspaces/{workspace_id}/variables
Lists workspace variables: the workspace-wide scope first, then each profile scope that holds variables, sorted by profile name. Pass scope_profile or is_workspace_scope=true to read one scope; an empty scope returns an empty list. Secret values are always null. Only workspace owners, and job runs in the workspace, can call this.
Request
- Parameters
- curl
- Python
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_id | string (uuid) | ID of the workspace |
Query parameters
| Name | Type | Description |
|---|---|---|
scope_profile | string or null | Read only this profile's scope. |
is_workspace_scope | boolean or null | true reads only the workspace-wide scope. Cannot be combined with scope_profile; false is rejected. |
curl "$DATAPLANE_URL/api/v1/workspaces/$WORKSPACE_ID/variables" \
-H "Authorization: Bearer $DATAPLANE_TOKEN"
import os
import requests
response = requests.get(
"<dataplane_url>/api/v1/workspaces/<workspace_id>/variables",
headers={"Authorization": f"Bearer {os.environ['DATAPLANE_TOKEN']}"},
)
response.raise_for_status()
print(response.json())
Response (200, 4xx, 5xx)
- Example
- Fields
- Errors
{
"scopes": [
{
"profile": "<profile>",
"variables": [
{
"created_at": "2026-01-01T00:00:00Z",
"created_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "<name>",
"type": "plain",
"updated_at": "2026-01-01T00:00:00Z",
"updated_by": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"value": "<value>"
}
]
}
]
}
| Field | Type | Description |
|---|---|---|
scopes | array of object | The workspace-wide scope first (always present, possibly empty), then each profile that holds variables, sorted by name. |
scopes[].profile | string or null | Profile owning this scope; null is the workspace-wide scope |
scopes[].variables | array of object | Variables in this scope, secrets redacted |
400Bad Request401Unauthorized403Forbidden500Internal Server Error
Each error returns the error body.
Change workspace variables
PATCH /api/v1/workspaces/{workspace_id}/variables
Create, update and delete variables in one scope, applied atomically. profile null targets the workspace-wide scope. Upserts create or replace by name. Deleting a name that was not set is reported as not_found rather than failing the request. Results list upserts first, then deletes. At most 200 variables per scope (400 variables_cap_exceeded). Reserved names are rejected case-insensitively (400 reserved_variable_names). A concurrent change answers 409 variables_scope_locked; retry. In a run, profile values override workspace-wide values with the same name. Only workspace owners can call this.
Request
- Parameters
- Body
- curl
- Python
Path parameters
| Name | Type | Description |
|---|---|---|
workspace_id | string (uuid) | ID of the workspace |
| Field | Type | Description |
|---|---|---|
deletes | array of string | Variable names to remove |
profile | string or null | Required. Profile whose scope to change: letters, digits, _ and -, starting with a letter or digit, up to 64 characters; null targets the workspace-wide scope |
upserts | array of object | Variables to create or update |
curl -X PATCH "$DATAPLANE_URL/api/v1/workspaces/$WORKSPACE_ID/variables" \
-H "Authorization: Bearer $DATAPLANE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"profile": "<profile>"}'
import os
import requests
response = requests.patch(
"<dataplane_url>/api/v1/workspaces/<workspace_id>/variables",
headers={"Authorization": f"Bearer {os.environ['DATAPLANE_TOKEN']}"},
json={"profile": "<profile>"},
)
response.raise_for_status()
print(response.json())
Response (200, 4xx, 5xx)
- Example
- Fields
- Errors
{
"results": [
{
"name": "<name>",
"status": "upserted"
}
]
}
| Field | Type | Description |
|---|---|---|
results | array of object | Outcome per requested variable |
results[].name | string | Environment variable name |
results[].status | string | What happened to this variable: upserted (created or updated), removed, or not_found (a delete named a variable that does not exist). One of: upserted, removed, not_found. |
400Bad Request401Unauthorized403Forbidden409Conflict500Internal Server Error
Each error returns the error body.