curl --request POST \
--url https://api.teamduo.ai/teams/{teamId}/datasources/{id}/query \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"sql": "SELECT status, count(*) FROM public.orders GROUP BY 1",
"limit": 100
}
'{
"columns": [
{
"name": "<string>",
"dataType": "<string>"
}
],
"rows": [
[
"<unknown>"
]
],
"rowCount": 123,
"truncated": true,
"durationMs": 123
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "Table \"public.salaries\" is not exposed.",
"code": "QUERY_DENIED"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "RATE_LIMITED",
"retryAfter": 123
}Run a read-only query
Runs one read-only query against an exposed datasource. The body shape depends on the datasource type: sql for PostgreSQL or BigQuery, collection+filter (optionally pipeline) for MongoDB. Every query is checked against this datasource’s exposure rules before it runs; anything outside it is refused by name — see Security model.
Budgeted per team rather than per caller: every MCP query and every API-key query share one rate limit, because an IP or key bucket can’t tell a busy team from a runaway agent.
curl --request POST \
--url https://api.teamduo.ai/teams/{teamId}/datasources/{id}/query \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"sql": "SELECT status, count(*) FROM public.orders GROUP BY 1",
"limit": 100
}
'{
"columns": [
{
"name": "<string>",
"dataType": "<string>"
}
],
"rows": [
[
"<unknown>"
]
],
"rowCount": 123,
"truncated": true,
"durationMs": 123
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "Table \"public.salaries\" is not exposed.",
"code": "QUERY_DENIED"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "RATE_LIMITED",
"retryAfter": 123
}Authorizations
An API key (sk_live_...) created under Settings → API keys, or a short-lived JWT access token from a browser session. A key only reaches the scope it was granted; see API keys and scopes.
Path Parameters
Datasource id from GET /teams/{teamId}/datasources.
Body
Response
The query ran.
Show child attributes
Show child attributes
Each row is an array positioned to match columns.
True when more rows matched than were returned. Narrow the query rather than raising the limit — the per-team cap applies regardless.