Time Series Data API
General
The Koherent Time Series Data API is used for retrieving historical measurement data, via a HTTP REST API.
API Base URL
The base URL for all API calls is
https://api.koherent.io
Firewalls
If using corporate firewalls or similar software, it might be necessary to whitelist the above base URL and the URL used for downloading measurement data:
https://koherent-reservoir-analytics-prod.s3.eu-north-1.amazonaws.com
Authentication
Auth is required for all API endpoints and is done by sending the provided API key in the Authorization header in the format
"Authorization": "Bearer your-api-key"
Koherent Management & Data API keys are long strings beginning with the identifier kma_. The keys are user and use-case specific and should be considered confidential. If you suspect that your API key has been compromised, please contact Koherent to have it reset.
Fetching measurement data
Due to technical constraints, there may be a delay of up to 24 hours between measurement and the data being available to query via this API.
Query limits: The time range between query start and end date is limited to a maximum of 7 days. The maximum number of query requests is also limited per day.
Measurement data is retrieved using an asynchronous three-step flow:
- Submit a query with your parameters. This returns a
queryExecutionIdimmediately. - Poll the status endpoint with the ID until the query completes.
- Download the measurement data from the provided download URL.
Step 1 — Submit measurement data query
POST /grids/:gridUid/data/measurements/queries
Submit a query for historical measurement data. The query runs asynchronously — this endpoint returns immediately with a queryExecutionId that you use to check the result.
Path parameters
| Name | Type | Data type | Description |
|---|---|---|---|
| gridUid | required | string | The UID of the Grid to query data for |
Request body
JSON object with the following fields:
| Name | Type | Data type | Description |
|---|---|---|---|
| startTimeMs | required* | int32 | Time range start value (Unix time in milliseconds, e.g. 1771677696000) |
| endTimeMs | required* | int32 | Time range end value (Unix time in milliseconds) |
| startTimeISO | required* | string | Time range start value (ISO 8601 UTC date-time string, e.g. 2026-02-21T12:41:36.000Z) |
| endTimeISO | required* | string | Time range end value (ISO 8601 UTC date-time string) |
| granularityMs | optional | int32 | Aggregate data to desired time interval between data points (milliseconds). Defaults to no aggregation |
| columns | optional | string[] | The data columns to include. The available columns are listed in the data types section. Defaults to all |
| targets | optional | string[] | The IDs of the targets to retrieve data for. Defaults to all |
| dataType | optional | string | The type of data to retrieve. Either position or distance. Defaults to position |
*) Either startTimeMs and endTimeMs OR startTimeISO and endTimeISO must be provided — not both.
Responses
| HTTP status | Status | Content-type | Response |
|---|---|---|---|
| 202 | Accepted | application/json | { "queryExecutionId": "..." } |
| 400 | Invalid request | application/json | Error |
| 404 | Grid not found | application/json | Error |
| 429 | Too many requests | application/json | Error |
Step 2 — Poll query status
GET /grids/:gridUid/data/measurements/queries/:queryExecutionId
Poll the status of a submitted query. Returns the current status while the query is running, and returns a download URL for the data once it has completed.
A polling interval of 1 second is a good starting point. This can be lowered or increased based on the actual duration of the query.
Path parameters
| Name | Type | Data type | Description |
|---|---|---|---|
| gridUid | required | string | The UID of the Grid the query was submitted for |
| queryExecutionId | required | string | The ID returned by the submit endpoint |
Responses
| HTTP status | Status | Content-type | Response |
|---|---|---|---|
| 202 | In progress | application/json | { "status": "QUEUED" | "RUNNING" } |
| 200 | Complete | application/json | { "downloadUrl": "..." } — time-limited presigned URL to the CSV file |
| 404 | Grid not found | application/json | Error |
| 500 | Query failed | application/json | Error |
Step 3 – Fetch data
Download the data by making a get request (unauthenticated) to the confidential downloadUrl provided in the previous step.
The presigned download URL returned in downloadUrl is time-limited and should be used promptly.
Fetching measurement data using legacy endpoint
This endpoint is retained for backwards compatibility. It may time out on large queries and should not be used for new integrations. Use the async endpoints above instead.
GET /grids/:gridUid/data/measurements
Fetches measurements synchronously. The HTTP connection is held open while the query runs, which can cause gateway timeouts on larger time ranges or dense datasets.
Path parameters
| Name | Type | Data type | Description |
|---|---|---|---|
| gridUid | required | string | The UID of the Grid to query data for |
Query parameters
| Name | Type | Data type | Description |
|---|---|---|---|
| startTimeMs | required* | int32 | Time range start value (Unix time in milliseconds, e.g. 1724773826000) |
| endTimeMs | required* | int32 | Time range end value (Unix time in milliseconds) |
| startTimeISO | required* | string | Time range start value (ISO 8601 UTC date-time string, e.g. 2024-08-27T15:50:26.000Z) |
| endTimeISO | required* | string | Time range end value (ISO 8601 UTC date-time string) |
| granularityMs | optional | int32 | Aggregate data to desired time interval between data points (milliseconds). Defaults to no aggregation |
| columns | optional | string | The data columns to include. Separate multiple values with commas. The available columns are listed in the data types section. Defaults to all |
| targets | optional | string | The IDs of the targets to retrieve data for. Separate multiple values with commas. Defaults to all |
| dataType | optional | string | The type of data to retrieve. Either position or distance. Defaults to position |
*) Either startTimeMs and endTimeMs OR startTimeISO and endTimeISO must be provided — not both.
Responses
| HTTP status | Status | Content-type | Response |
|---|---|---|---|
| 307 | Success | — | Redirect to a presigned download URL for the CSV result file |
| 400 | Invalid request | application/json | Error |
| 404 | Grid not found | application/json | Error |
| 429 | Too many requests | application/json | Error |
Data Types
Position Data (CSV)
| Column | Data type | Description |
|---|---|---|
target_id | string | The ID of the target |
timestamp | string | The time of the measurement in UTC (ISO 8601 date-time string) |
x | float64 | Target x coordinate |
y | float64 | Target y coordinate |
z | float64 | Target z coordinate |
yaw | float64 | Target yaw angle in degrees |
pitch | float64 | Target pitch angle in degrees |
roll | float64 | Target roll angle in degrees |
error_x | float64 | Target x coordinate error estimate |
error_y | float64 | Target y coordinate error estimate |
error_z | float64 | Target z coordinate error estimate |
error_yaw | float64 | Target yaw angle error estimate in degrees |
error_pitch | float64 | Target pitch angle error estimate in degrees |
error_roll | float64 | Target roll angle error estimate in degrees |
Example
"timestamp","x","y"
"2026-04-27T13:04:57.850255286Z","61.448910842784292","-0.1233040417274127"
"2026-04-27T13:04:57.950255286Z","61.448901621545484","-0.12332864493494869"
Distance Data (CSV)
| Field | Type | Data type | Description |
|---|---|---|---|
target_id | string | The ID of the target | |
timestamp | required | string | The time of the measurement (ISO 8601 timestamp) |
d | required | float64 | The distance in meters |
error_d | required | float64 | The distance error estimate in meters |
Example
"timestamp","d"
"2025-01-14T22:56:01.5782653Z","84.298151721545321"
Error
| Field | Data type | Description |
|---|---|---|
status | int | A HTTP status code |
message | string | A human-readable error description |
Example
{
"status": 400, "message": "Bad request: Requested non-existent column 'foo'"
}
Examples
- Python
- Shell
The examples use the Requests client, which can be installed using:
pip3 install requests~=2.34
A Python version >= 3.8 is required.
Submit a query, poll until complete, and print the result:
import time
import requests
api_key = "your-api-key"
grid_uid = "some-grid-uid-123"
base_url = f"https://api.koherent.io/grids/{grid_uid}/data/measurements"
headers = {"Authorization": f"Bearer {api_key}"}
# Step 1: Submit the query
response = requests.post(
f"{base_url}/queries",
headers=headers,
json={
"startTimeMs": 1778544000000,
"endTimeMs": 1778634000000,
"columns": ["timestamp", "x", "y", "z"],
"granularityMs": 1000,
}
)
response.raise_for_status()
query_execution_id = response.json()["queryExecutionId"]
print(f"Submitted query: {query_execution_id}")
# Step 2: Poll for completion
while True:
status_response = requests.get(
f"{base_url}/queries/{query_execution_id}",
headers=headers,
)
status_response.raise_for_status()
body = status_response.json()
if "downloadUrl" in body:
download_url = body["downloadUrl"]
break
print(f"Query status: {body['status']}")
time.sleep(5)
# Step 3: Download the CSV from the presigned URL (no auth header needed)
data_response = requests.get(download_url)
data_response.raise_for_status()
with open("data.csv", "wb") as f:
f.write(data_response.content)
print("Data written to data.csv")
Submit a query, poll until complete, and write the result to data.csv:
#!/bin/bash
set -e
GRID_UID="some-grid-uid"
API_KEY="your-api-key"
BASE_URL="https://api.koherent.io/grids/${GRID_UID}/data/measurements"
# Step 1: Submit the query
QUERY_ID=$(curl --silent --fail \
--json '{"startTimeISO":"2026-05-10T00:00:00Z","endTimeISO":"2026-05-12T00:00:00Z","columns":["timestamp","target_id","x","y","z"],"granularityMs":1000}' \
--header "Authorization: Bearer ${API_KEY}" \
"${BASE_URL}/queries" | jq -r '.queryExecutionId')
echo "Submitted query: ${QUERY_ID}"
# Step 2: Poll until complete
while true; do
RESPONSE=$(curl --silent --fail \
--header "Authorization: Bearer ${API_KEY}" \
"${BASE_URL}/queries/${QUERY_ID}")
DOWNLOAD_URL=$(echo "${RESPONSE}" | jq -r '.downloadUrl // empty')
if [ -n "${DOWNLOAD_URL}" ]; then
break # Query complete
fi
echo "Status: $(echo "${RESPONSE}" | jq -r '.status')"
sleep 5
done
# Step 3: Download the CSV from the presigned URL (no auth header needed)
curl --silent --fail "${DOWNLOAD_URL}" > data.csv
echo "Data written to data.csv"