Skip to main content

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:

  1. Submit a query with your parameters. This returns a queryExecutionId immediately.
  2. Poll the status endpoint with the ID until the query completes.
  3. 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

NameTypeData typeDescription
gridUidrequiredstringThe UID of the Grid to query data for

Request body

JSON object with the following fields:

NameTypeData typeDescription
startTimeMsrequired*int32Time range start value (Unix time in milliseconds, e.g. 1771677696000)
endTimeMsrequired*int32Time range end value (Unix time in milliseconds)
startTimeISOrequired*stringTime range start value (ISO 8601 UTC date-time string, e.g. 2026-02-21T12:41:36.000Z)
endTimeISOrequired*stringTime range end value (ISO 8601 UTC date-time string)
granularityMsoptionalint32Aggregate data to desired time interval between data points (milliseconds). Defaults to no aggregation
columnsoptionalstring[]The data columns to include. The available columns are listed in the data types section. Defaults to all
targetsoptionalstring[]The IDs of the targets to retrieve data for. Defaults to all
dataTypeoptionalstringThe 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 statusStatusContent-typeResponse
202Acceptedapplication/json{ "queryExecutionId": "..." }
400Invalid requestapplication/jsonError
404Grid not foundapplication/jsonError
429Too many requestsapplication/jsonError

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

NameTypeData typeDescription
gridUidrequiredstringThe UID of the Grid the query was submitted for
queryExecutionIdrequiredstringThe ID returned by the submit endpoint

Responses

HTTP statusStatusContent-typeResponse
202In progressapplication/json{ "status": "QUEUED" | "RUNNING" }
200Completeapplication/json{ "downloadUrl": "..." } — time-limited presigned URL to the CSV file
404Grid not foundapplication/jsonError
500Query failedapplication/jsonError

Step 3 – Fetch data

Download the data by making a get request (unauthenticated) to the confidential downloadUrl provided in the previous step.

note

The presigned download URL returned in downloadUrl is time-limited and should be used promptly.


Fetching measurement data using legacy endpoint

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
NameTypeData typeDescription
gridUidrequiredstringThe UID of the Grid to query data for
Query parameters
NameTypeData typeDescription
startTimeMsrequired*int32Time range start value (Unix time in milliseconds, e.g. 1724773826000)
endTimeMsrequired*int32Time range end value (Unix time in milliseconds)
startTimeISOrequired*stringTime range start value (ISO 8601 UTC date-time string, e.g. 2024-08-27T15:50:26.000Z)
endTimeISOrequired*stringTime range end value (ISO 8601 UTC date-time string)
granularityMsoptionalint32Aggregate data to desired time interval between data points (milliseconds). Defaults to no aggregation
columnsoptionalstringThe data columns to include. Separate multiple values with commas. The available columns are listed in the data types section. Defaults to all
targetsoptionalstringThe IDs of the targets to retrieve data for. Separate multiple values with commas. Defaults to all
dataTypeoptionalstringThe 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 statusStatusContent-typeResponse
307SuccessRedirect to a presigned download URL for the CSV result file
400Invalid requestapplication/jsonError
404Grid not foundapplication/jsonError
429Too many requestsapplication/jsonError

Data Types

Position Data (CSV)

ColumnData typeDescription
target_idstringThe ID of the target
timestampstringThe time of the measurement in UTC (ISO 8601 date-time string)
xfloat64Target x coordinate
yfloat64Target y coordinate
zfloat64Target z coordinate
yawfloat64Target yaw angle in degrees
pitchfloat64Target pitch angle in degrees
rollfloat64Target roll angle in degrees
error_xfloat64Target x coordinate error estimate
error_yfloat64Target y coordinate error estimate
error_zfloat64Target z coordinate error estimate
error_yawfloat64Target yaw angle error estimate in degrees
error_pitchfloat64Target pitch angle error estimate in degrees
error_rollfloat64Target 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)

FieldTypeData typeDescription
target_idstringThe ID of the target
timestamprequiredstringThe time of the measurement (ISO 8601 timestamp)
drequiredfloat64The distance in meters
error_drequiredfloat64The distance error estimate in meters

Example

"timestamp","d"
"2025-01-14T22:56:01.5782653Z","84.298151721545321"

Error

FieldData typeDescription
statusintA HTTP status code
messagestringA human-readable error description

Example

{
"status": 400, "message": "Bad request: Requested non-existent column 'foo'"
}

Examples

The examples use the Requests client, which can be installed using:

pip3 install requests~=2.34
note

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")