Data Sources
Manage external data source connections such as databases, cloud storage, and data warehouses. Register, test, and share data source connections across your organization.
All endpoints require a valid API key with the appropriate datasources:* scope.
Base URL: https://<your-instance>/api/v1
DataSource Object
{
"_id": "ds_abc123",
"name": "production-postgres",
"label": "Production PostgreSQL",
"description": "Main production database for user records",
"type": "postgres",
"category": "relational",
"status": "connected",
"testConnection": {
"lastTested": "2025-01-18T16:30:00Z",
"success": true,
"message": "Connection successful"
},
"metadata": {
"tables": ["users", "orders", "products"],
"schemas": ["public"],
"databases": ["app_db"],
"buckets": [],
"size": 104857600,
"rowCount": 250000
},
"owner": "user_001",
"organizationId": "org_xyz789",
"tags": ["production"],
"permissions": {
"isPublic": false,
"allowAllUsers": false,
"allowedUsers": ["user_002", "user_003"]
},
"usageCount": 12,
"createdAt": "2025-01-10T08:00:00Z",
"updatedAt": "2025-01-18T16:30:00Z"
}
owneris the id of the user who registered the data source.categoryis set from thetype(for examplerelationalforpostgres).statusisconnectedorerrorfrom the connection test run when the data source is created or its credentials change;testConnectionholds that test's result.metadatais present once metadata has been fetched with the metadata endpoint;sizeandrowCountarenullwhere the store has no such measure.
Note: The
credentialsfield is never included in standard responses. Use the dedicated credentials endpoint to retrieve connection credentials.
GET /api/v1/data-sources
List all data sources accessible to the authenticated user.
Scope: datasources:read
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
q | string | No | -- | Search by name, label, or description |
type | string | No | -- | Filter by type, one of the data source types; all means no filter |
category | string | No | -- | Filter by category: relational, document, key-value, graph, vector, multi-model, spreadsheet, cloud-storage, message-queue, api; all means no filter |
status | string | No | -- | Filter by status: connected, disconnected, error |
limit | integer | No | 50 | Maximum number of results to return (1-200) |
cursor | string | No | meta.nextCursor of the previous page; omit for the first page | |
sort | string | No | -createdAt | Sort field, prefix with - for descending, e.g. name, -updatedAt |
Response 200 OK
{
"data": [
{
"_id": "ds_abc123",
"name": "production-postgres",
"label": "Production PostgreSQL",
"description": "Main production database for user records",
"type": "postgres",
"category": "relational",
"status": "connected",
"testConnection": {
"lastTested": "2025-01-18T16:30:00Z",
"success": true,
"message": "Connection successful"
},
"owner": "user_001",
"organizationId": "org_xyz789",
"tags": [],
"permissions": {
"isPublic": false,
"allowAllUsers": false,
"allowedUsers": ["user_002"]
},
"usageCount": 0,
"createdAt": "2025-01-10T08:00:00Z",
"updatedAt": "2025-01-18T16:30:00Z"
}
],
"meta": {
"total": 15,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}
POST /api/v1/data-sources
Register a new external data source connection.
Scope: datasources:write
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Identifier name; must not match another data source you own (in your organization) |
label | string | Yes | Human-readable display name |
type | string | Yes | One of the data source types |
credentials | object | Yes | Connection credentials; the fields depend on the type (see examples below) |
description | string | No | Description of the data source |
tags | string[] | No | Tags (default: []) |
No other fields are accepted: an unknown field (for example category or metadata) returns 400 validation-error. category is set from type.
The platform tests the connection before saving. The data source is created either way: with status connected if the test passed, or error if it failed (testConnection.message says why).
Data Source Types
type is one of: mysql, postgres, mssql, oracle, redshift, snowflake, bigquery, cockroachdb, cratedb, timescaledb, questdb, clickhouse, singlestore, greenplum, mongodb, elasticsearch, dynamodb, firestore, supabase, couchdb, couchbase, redis, memcached, neo4j, neptune, arangodb, tigergraph, milvus, pinecone, weaviate, qdrant, chroma, pgvector, vespa, marqo, surrealdb, airtable, google-sheets, baserow, nocodb, seatable, grist, s3, gcs, minio, azure-blob, rabbitmq, kafka, sqs, pulsar, mqtt, api.
Categories, set from the type: relational, document, key-value, graph, vector, multi-model, spreadsheet, cloud-storage, message-queue, api.
Database credentials example:
{
"name": "analytics-mysql",
"label": "Analytics MySQL",
"type": "mysql",
"credentials": {
"host": "db.example.com",
"port": 3306,
"username": "analytics_reader",
"password": "secure-password-here",
"database": "analytics"
},
"description": "Read-only MySQL connection for analytics queries",
"tags": ["analytics"]
}
S3 credentials example:
{
"name": "data-lake-s3",
"label": "Data Lake S3",
"type": "s3",
"credentials": {
"bucketName": "my-data-lake",
"region": "us-east-1",
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
},
"description": "S3 bucket for raw data ingestion"
}
Snowflake credentials example:
{
"name": "warehouse-snowflake",
"label": "Snowflake Warehouse",
"type": "snowflake",
"credentials": {
"account": "xy12345.us-east-1",
"username": "ETL_USER",
"password": "secure-password-here",
"warehouse": "COMPUTE_WH",
"database": "ANALYTICS",
"schema": "PUBLIC"
},
"description": "Snowflake data warehouse for reporting"
}
Response 201 Created
The new data source, without its credentials.
Errors: 400 validation-error for a missing required field (listed in details), a type not in the list, or an unknown field; 409 duplicate if you already have a data source with this name; 403 governance-blocked if the organization's governance requirements for data sources are not met.
GET /api/v1/data-sources/:id
Retrieve a single data source by its ID. Credentials are excluded from the response.
Scope: datasources:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The data source ID |
Response 200 OK
{
"data": {
"_id": "ds_abc123",
"name": "production-postgres",
"label": "Production PostgreSQL",
"description": "Main production database for user records",
"type": "postgres",
"category": "relational",
"status": "connected",
"testConnection": {
"lastTested": "2025-01-18T16:30:00Z",
"success": true,
"message": "Connection successful"
},
"metadata": {
"tables": ["users", "orders", "products"],
"schemas": ["public"],
"databases": ["app_db"],
"buckets": [],
"size": 104857600,
"rowCount": 250000
},
"owner": "user_001",
"organizationId": "org_xyz789",
"tags": ["production"],
"permissions": {
"isPublic": false,
"allowAllUsers": false,
"allowedUsers": ["user_002", "user_003"]
},
"usageCount": 12,
"createdAt": "2025-01-10T08:00:00Z",
"updatedAt": "2025-01-18T16:30:00Z"
},
"meta": {
"requestId": "req_abc123"
}
}
PATCH /api/v1/data-sources/:id
Update an existing data source. Only provided fields are updated. The type cannot be changed after creation.
Scope: datasources:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The data source ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Updated identifier name |
label | string | No | Updated display name |
credentials | object | No | Replacement connection credentials (the whole object, for the data source's type); the connection is tested again and status updated |
description | string | No | Updated description |
tags | string[] | No | Replacement tag list |
No other fields are accepted: an unknown field (including type, category or metadata) returns 400 validation-error.
{
"label": "Production PostgreSQL (Primary)",
"credentials": {
"host": "db-primary.example.com",
"port": 5432,
"username": "app_user",
"password": "new-secure-password",
"database": "app_db"
},
"description": "Updated primary production database connection"
}
Response 200 OK
The updated data source, without its credentials.
Errors: 400 validation-error for an unknown field; 404 not-found if the data source does not exist; 403 forbidden if you cannot edit it.
DELETE /api/v1/data-sources/:id
Permanently delete a data source registration. This does not affect the external data source itself, only the connection record on the platform.
Scope: datasources:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The data source ID |
Response 204 No Content
No response body.
POST /api/v1/data-sources/:id/test
Test the connection to the external data source using the stored credentials. Returns the connectivity status and any error details.
Scope: datasources:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The data source ID |
Response 200 OK (success)
{
"data": {
"success": true,
"message": "Connection successful"
},
"meta": {
"requestId": "req_abc123"
}
}
Response 200 OK (failure)
{
"data": {
"success": false,
"message": "Connection refused: host db.example.com port 5432"
},
"meta": {
"requestId": "req_abc123"
}
}
GET /api/v1/data-sources/:id/metadata
Retrieve what the data source holds: databases, schemas, tables or collections, buckets, indexes or topics, with sizes and row counts where the system reports them. A table's columns come from table-columns; a bucket's files from objects. An api key and an mqtt broker hold no catalogue and have no metadata.
Scope: datasources:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The data source ID |
Response 200 OK (database example)
{
"data": {
"tables": ["orders", "users"],
"schemas": ["public"],
"databases": ["app_db"],
"buckets": [],
"size": 52428800,
"rowCount": 99630
},
"meta": {
"requestId": "req_abc123"
}
}
Response 200 OK (S3 example)
{
"data": {
"tables": [],
"schemas": [],
"databases": [],
"buckets": ["my-data-lake"],
"size": 5368709120,
"rowCount": 12450
},
"meta": {
"requestId": "req_abc123"
}
}
GET /api/v1/data-sources/:id/status
The data source's last known health, read-only (use POST /data-sources/:id/test to probe it now): its status and the time and, when it failed, the message of its last connection test. A data source that was never tested has null for both.
Scope: datasources:read
Response 200 OK
{
"data": {
"datasourceId": "ds_abc123",
"status": "error",
"lastTestedAt": "2026-10-10T12:00:00.000Z",
"lastError": "connection refused",
"type": "postgres",
"updatedAt": "2026-10-10T12:00:00.000Z"
},
"meta": {
"requestId": "req_abc123"
}
}
status is connected (last test passed), error (last test failed), testing, or disconnected (never tested).
GET /api/v1/data-sources/:id/table-columns
Describe one table of the data source: its columns (name, type, nullable, key), row count and size. "Table" is what the type holds: a SQL table, a collection, an index, a sheet, a node label, a queue or topic, a Redis key pattern. A collection whose store declares no schema is described from a sample of its documents (sampled gives how many). Every type except object storage (use objects) and API keys.
Scope: datasources:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
table | string | Yes | The table as metadata lists it (schema.table outside the default schema) |
schema | string | No | A schema or namespace, for example public |
Response 200 OK
{
"data": {
"datasourceId": "ds_abc123",
"table": "orders",
"schema": null,
"columns": {
"columns": [
{ "name": "id", "type": "integer", "nullable": false, "key": "PRIMARY" },
{ "name": "status", "type": "text", "nullable": true, "key": null }
],
"rowCount": 99630,
"size": 52428800
}
},
"meta": {
"requestId": "req_abc123"
}
}
GET /api/v1/data-sources/:id/objects
List the files and folders at one level of an object-storage data source (s3, minio, gcs, azure-blob). Pass prefix to descend into a folder; keys are relative to it.
Scope: datasources:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
bucket | string | No | The bucket (container) to list; the data source's own bucket when omitted |
prefix | string | No | The folder to list, for example exports/ |
Response 200 OK
{
"data": {
"bucket": "my-data-lake",
"prefix": "exports/",
"contents": [
{ "key": "2026/", "size": 0, "lastModified": null, "isFolder": true },
{ "key": "orders.csv", "size": 18342, "lastModified": "2026-10-09T08:15:00.000Z", "isFolder": false }
]
},
"meta": {
"requestId": "req_abc123"
}
}
GET /api/v1/data-sources/:id/credentials
Retrieve the stored connection credentials for a data source. Handle the response securely and avoid logging credential values.
Scope: datasources:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The data source ID |
Response 200 OK (database)
{
"data": {
"host": "db.example.com",
"port": 5432,
"username": "app_user",
"password": "secure-password-here",
"database": "app_db"
},
"meta": {
"requestId": "req_abc123"
}
}
Response 200 OK (S3)
{
"data": {
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"region": "us-east-1",
"bucket": "my-data-lake"
},
"meta": {
"requestId": "req_abc123"
}
}
Sharing
Who can reach a data source is the platform's one sharing shape: its owner, its members
(role editor can use and change it, user can only use it) and its visibility
(public: every user can find and use it; in a multi-tenant deployment, every user of
its organization). Changing it stays with its owner and editors.
| Method | Path | Does | Scope |
|---|---|---|---|
| GET | /api/v1/data-sources/:id/permissions | Owner, members (userId, role) and visibility | datasources:read |
| POST | /api/v1/data-sources/:id/permissions/members | Share with a user: { "userId", "role": "editor" | "user" } | datasources:write |
| DELETE | /api/v1/data-sources/:dataSourceId/permissions/members/:userId | Stop sharing with a user | datasources:write |
| PATCH | /api/v1/data-sources/:id/permissions | { "visibility": "public" | "private" } | datasources:write |
Each, except a member's removal (204), answers the permissions as they are now:
{
"data": {
"resourceId": "<data source id>",
"owner": "<user id>",
"members": [{ "userId": "<user id>", "role": "user" }],
"visibility": "private"
},
"meta": { "requestId": "req_abc123" }
}
Error Responses
All endpoints may return the following error responses:
| Status | Description |
|---|---|
400 Bad Request | Invalid request body or parameters, or a share with someone who cannot receive it (such as yourself) |
401 Unauthorized | Missing or invalid API key |
403 Forbidden | Insufficient scope for the requested operation |
404 Not Found | Data source not found or not accessible |
409 Conflict | Data source name already exists in the organization |
422 Unprocessable Entity | Validation error on request body |
429 Too Many Requests | Rate limit exceeded |
500 Internal Server Error | Unexpected server error |
{
"type": "urn:strongly:problem:validation-error",
"title": "Validation failed",
"status": 400,
"detail": "Missing required fields",
"code": "validation-error",
"requestId": "req_abc123",
"errors": [
{ "field": "credentials", "message": "credentials is required", "type": "required" }
]
}