Queries
6. Org API: identity¶
GET /identity/security_metadata¶
Returns your service client filter and a hash that identifies your access.
- URL:
https://<org>.api.comodash.io/v2/identity/security_metadata - Scope: none. Any valid
dash_apitoken works. - SDK equivalent: none.
The filter lists every service client in your service_client_id claim plus 901. It is 1=1 (all rows) if your claim includes 0, or if your highest role is AllServiceClientAnalyst or ZoneAdmin.
This filter does not drop service clients that your role cannot read.
Response 200
| Field | Type | Description |
|---|---|---|
athena_service_client_where_clause | string | SQL condition, for example service_client_id IN ('12', '901'), 1=1 or 1=0. |
service_client_hash | string | 16-character hash of your roles and service clients. It changes when your access changes. |
{
"athena_service_client_where_clause": "service_client_id IN ('12', '13', '901')",
"service_client_hash": "3f9c2a7b1d4e5f60"
}
Errors
| Status | Body | Cause and fix |
|---|---|---|
| 403 | {"error": "Unauthorized", "message": "No service client provided in access token"} | Your token has no service_client_id claim. Ask your administrator to assign a service client. |
| 403 | {"error": "Unauthorized", "message": "Sub not provided in access token"}, "no JTI in the access token", "No resource access provided in access token" or "No resource access provided for dash_api in access token" | The token lacks a required claim, or you have no roles on dash_api. |
| 403 | {"error": "Unauthorized", "message": "Invalid Service Client claim (claim in incorrect format: ...)"} or "No service client is configured" | The service_client_id claim is not in the form [12 13], or is empty. |
| 500 | {"error": "Error", "message": "There was a problem returning the security metadata"} | Unexpected error. Contact support. |
curl -s "$API/identity/security_metadata" -H "Authorization: Bearer $TOKEN"
r = requests.get(f"{API}/identity/security_metadata", headers=HEADERS)
r.raise_for_status()
print(r.json()["athena_service_client_where_clause"])
7. Queries¶
7.1 Lifecycle¶
POST /query ──► QUEUED ──► RUNNING ──► SUCCEEDED ──► GET /query/{id}/result (first 1,000 rows)
│ └► GET /query/{id}/csv (full result)
├──► FAILED (read status.stateChangeReason)
└──► CANCELLED (after DELETE /query/{id})
| State | Meaning | Final? |
|---|---|---|
QUEUED | Accepted, waiting to run. | no |
RUNNING | Running. | no |
SUCCEEDED | Finished. Results are ready. | yes |
FAILED | Failed. status.stateChangeReason says why. | yes |
CANCELLED | Stopped by a cancel request. | yes |
Polling: call GET /query/{query_id} every few seconds until the state is final. The SDK polls every 5 seconds. Do not poll more often than once a second.
7.2 POST /query: start a query¶
Starts a SQL query and returns its ID straight away.
- URL:
https://<org>.api.comodash.io/v2/query - Scope:
query:write - SDK equivalent:
Query(config, query_text=...)
Request body
| Field | Type | Required | Description |
|---|---|---|---|
query | string | yes | SQL text, in the Amazon Athena (Trino) dialect. |
database | string | no | Default database for unqualified table names. Defaults to your lake database: <org>_lake_v2 on the Org API, <zone>_lake on the Zone API. |
{"query": "select * from policies where service_client_id IN ('12', '901') limit 10"}
Response 202
{"queryId": "06ba1d95-8c4f-460c-90b3-bc68fddf2fde"}
Errors
| Status | Message | Cause and fix |
|---|---|---|
| 400 | JSON not well formed. | The body is missing or is not valid JSON. |
| 400 | MissingParameterError: "query" parameter missing from request. | Add the query field. |
| 400 | The query engine's message, e.g. An error occurred (InvalidRequestException) when calling the StartQueryExecution operation: line 1:8: ... | The engine rejected the SQL when it was submitted. Fix the SQL. |
| 403 | You seem to have a permissions problem. You may be trying to perform a query you do not have permission to execute. | The query uses a database or table you cannot access. |
| 403 | You seem to have a permissions problem. You are trying to access data you do not have permissions for. | Your role or service clients do not allow this. |
| 403 | You don't have the right permissions to create tables there. | Only some roles can create tables, and only in some databases. See section 4.3. |
| 403 | You don't have the right permissions to execute that query. | Same as above, for other statements. |
| 409 | The engine's message | A CREATE TABLE AS target location already has data. Use a new table name, or drop and clean up the old table. |
| 500 | An Internal Server Error occurred. Please contact support if this error persists. | Unexpected error. This includes tokens with missing or malformed claims (see 3.6). |
QUERY_ID=$(curl -s -X POST "$API/query" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"query": "select count(*) as n from policies"}' | jq -r .queryId)
echo "$QUERY_ID"
r = requests.post(f"{API}/query", headers=HEADERS,
json={"query": "select count(*) as n from policies"})
r.raise_for_status()
query_id = r.json()["queryId"]
7.3 GET /query/{query_id}: get query status¶
- URL:
https://<org>.api.comodash.io/v2/query/06ba1d95-8c4f-460c-90b3-bc68fddf2fde - Scope:
query:read - SDK equivalent:
Query.get_query_info(),Query.state(),Query.wait_to_complete()
Response 200
| Field | Type | Description |
|---|---|---|
query | string | The SQL text. |
queryId | string | The query ID. |
statementType | string | DML, DDL or UTILITY. |
status.state | string | QUEUED, RUNNING, SUCCEEDED, FAILED or CANCELLED. |
status.submissionDateTime | string | When the query was submitted, UTC, format MM-DD-YYYY HH:MM:SS. |
status.completionDateTime | string | When it finished. Only present once finished. |
status.stateChangeReason | string | Why it failed. Permission errors are replaced with the friendly messages listed under POST /query. |
{
"query": "select count(*) as n from policies",
"queryId": "06ba1d95-8c4f-460c-90b3-bc68fddf2fde",
"statementType": "DML",
"status": {
"state": "SUCCEEDED",
"submissionDateTime": "10-13-2026 09:58:01",
"completionDateTime": "10-13-2026 09:58:04"
}
}
Errors: 404 with An error occurred (InvalidRequestException): QueryExecution <id> was not found for an unknown ID. 500 for unexpected errors.
curl -s "$API/query/$QUERY_ID" -H "Authorization: Bearer $TOKEN" | jq .status
import time
while True:
status = requests.get(f"{API}/query/{query_id}", headers=HEADERS).json()["status"]
if status["state"] in ("SUCCEEDED", "FAILED", "CANCELLED"):
break
time.sleep(5)
if status["state"] != "SUCCEEDED":
raise RuntimeError(status.get("stateChangeReason"))
7.4 DELETE /query/{query_id}: cancel a query¶
- URL:
https://<org>.api.comodash.io/v2/query/06ba1d95-8c4f-460c-90b3-bc68fddf2fde - Scope:
query:write - SDK equivalent:
Query.stop()
Returns 200 with no body. The query moves to CANCELLED. Errors: 404 for an unknown ID, 500 for unexpected errors.
curl -s -X DELETE "$API/query/$QUERY_ID" -H "Authorization: Bearer $TOKEN" -o /dev/null -w "%{http_code}\n"
requests.delete(f"{API}/query/{query_id}", headers=HEADERS).raise_for_status()
7.5 GET /query/{query_id}/result: first page of results¶
Returns up to 1,000 rows of a SUCCEEDED query as JSON. On this page the first row holds the column names. Every value is a string.
This endpoint returns only the first page. If the response contains nextToken, there are more rows. Use the CSV download for the full result.
- URL:
https://<org>.api.comodash.io/v2/query/06ba1d95-8c4f-460c-90b3-bc68fddf2fde/result - Scope:
query:read - SDK equivalent: none (the SDK uses the CSV download).
Response 200
| Field | Type | Description |
|---|---|---|
resultSet.Rows[] | array | One item per row. The first row holds the column names. |
resultSet.Rows[].Data[].VarCharValue | string | Cell value. Left out for NULL. |
resultSet.ResultSetMetadata.ColumnInfo[] | array | Column details: Name, Label, Type (varchar, bigint, double, decimal, date, timestamp, …), Precision, Scale, Nullable, CaseSensitive. |
nextToken | string | Present when there are more than 1,000 rows. |
{
"resultSet": {
"Rows": [
{"Data": [{"VarCharValue": "policy_number"}, {"VarCharValue": "face_amount"}]},
{"Data": [{"VarCharValue": "P0001"}, {"VarCharValue": "20000"}]}
],
"ResultSetMetadata": {
"ColumnInfo": [
{"Name": "policy_number", "Label": "policy_number", "Type": "varchar", "Precision": 2147483647, "Scale": 0, "Nullable": "UNKNOWN", "CaseSensitive": true},
{"Name": "face_amount", "Label": "face_amount", "Type": "bigint", "Precision": 19, "Scale": 0, "Nullable": "UNKNOWN", "CaseSensitive": false}
]
}
}
}
Errors: 404 for an unknown ID. 403 permission messages as for POST /query. 500 for unexpected errors.
page = requests.get(f"{API}/query/{query_id}/result", headers=HEADERS).json()
rows = [[c.get("VarCharValue") for c in r["Data"]] for r in page["resultSet"]["Rows"]]
header, data = rows[0], rows[1:]
7.6 GET /query/{query_id}/csv: download the full result¶
Redirects to a pre-signed HTTPS link for the full result as a CSV file. The query must be SUCCEEDED.
- URL:
https://<org>.api.comodash.io/v2/query/06ba1d95-8c4f-460c-90b3-bc68fddf2fde/csv - Scope:
query:read - SDK equivalent:
Query.download_csv(path),Query.get_csv_for_streaming()
Response 302
Locationheader: the pre-signed link.- Body:
{"location": "<the same link>"}.
Important:
- Do not send your
Authorizationheader to the pre-signed link. The link carries its own signature, and the storage service rejects requests that have both. curl (7.58 and later) and Pythonrequestsdrop the header when a redirect changes host. - The link expires quickly (at most 15 minutes). Start the download straight away. If it expires, call this endpoint again.
- The CSV has a header row. Values are quoted.
Errors: any problem, including a query that is not SUCCEEDED or does not exist, returns 500 {"message": "Internal Server Error"}. Check the query status first.
curl -s -L "$API/query/$QUERY_ID/csv" -H "Authorization: Bearer $TOKEN" -o result.csv
r = requests.get(f"{API}/query/{query_id}/csv", headers=HEADERS,
allow_redirects=False)
r.raise_for_status() # 302
with requests.get(r.headers["Location"], stream=True) as dl: # no auth header
dl.raise_for_status()
with open("result.csv", "wb") as f:
for chunk in dl.iter_content(1024 * 1024):
f.write(chunk)