Skip to main content

Data API Reference

The Data API reads, writes, and aggregates records stored in Corva datasets and discovers the dataset definitions available to the authenticated customer.

Base URL: https://data.corva.ai

All examples accept either an API key or a Bearer token:

Authorization: API YOUR_API_KEY
Authorization: Bearer YOUR_JWT
Maintained reference

The required parameters, validation limits, and examples below are verified against the current Data API routes and models. The interactive API explorer is useful for trying requests, but this page is the customer reference for the workflows shown here.

Dataset record shape

Dataset records share a common envelope. Fields in data are specific to the selected dataset.

FieldTypeDescription
_idstringMongoDB document ID.
company_idintegerOwning company ID, when present.
asset_idintegerPlatform asset ID, commonly a well's asset_id.
versionintegerRecord schema/version value.
providerstringDataset provider, such as corva.
collectionstringDataset collection name, such as wits.
dataobjectDataset-specific payload.
timestampintegerUnix timestamp in milliseconds for time-indexed records, when present.
measured_depthnumberMeasured depth for depth-indexed records, when present.
log_identifierstringApp-stream/log scope for depth data, when present.
stage_numberintegerCompletion stage number, when present.
datetimestringISO 8601 time for time-series collections, when present.
metadataobjectTime-series metadata, including company_id or asset_id, when present.

Representative response:

[
{
"_id": "507f1f77bcf86cd799439011",
"company_id": 100,
"asset_id": 12345,
"version": 1,
"provider": "corva",
"collection": "wits",
"timestamp": 1715600000000,
"data": {
"hole_depth": 10482.5,
"bit_depth": 10476.2
}
}
]

Insert dataset records

POST/api/v1/data/{provider}/{dataset}/

Insert records by sending a JSON array. A single request must contain at least 1 and no more than 1,000 records. Split larger writes into batches of 1,000 or fewer records.

The caller needs write permission for the dataset and access to the referenced company and asset.

Body fieldTypeRequiredDescription
versionintegerYesRecord schema/version value.
asset_idintegerFor time/depth dataAsset ID associated with the record.
timestampintegerFor time dataUnix timestamp for a time-indexed record.
measured_depthnumberFor depth dataMeasured depth for a depth-indexed record.
log_identifierstringFor depth dataApp-stream/log scope.
dataobjectDataset dependentDataset-specific record payload. Follow the selected dataset's schema.
curl 'https://data.corva.ai/api/v1/data/example-provider/example-dataset/' \
--request POST \
--header "Authorization: API ${CORVA_API_KEY}" \
--header 'Content-Type: application/json' \
--data '[
{
"version": 1,
"asset_id": 12345,
"timestamp": 1715600000000,
"data": {"value": 42.1}
},
{
"version": 1,
"asset_id": 12345,
"timestamp": 1715600001000,
"data": {"value": 42.4}
}
]'
records = [
{
"version": 1,
"asset_id": 12345,
"timestamp": 1715600000000,
"data": {"value": 42.1},
},
{
"version": 1,
"asset_id": 12345,
"timestamp": 1715600001000,
"data": {"value": 42.4},
},
]

response = requests.post(
"https://data.corva.ai/api/v1/data/example-provider/example-dataset/",
headers={"Authorization": authorization},
json=records,
timeout=30,
)
response.raise_for_status()
result = response.json()

A successful response reports inserted IDs and any failed records:

{
"inserted_ids": [
"507f1f77bcf86cd799439011",
"507f1f77bcf86cd799439012"
],
"failed_count": 0,
"messages": []
}
Batch writes

Do not resend an entire batch blindly after a partial failure. Check inserted_ids, failed_count, and messages, then retry only records that were not inserted.

Find dataset records

GET/api/v1/data/{provider}/{dataset}/

Path parameters

ParameterTypeRequiredDescription
providerstringYesDataset provider, such as corva.
datasetstringYesDataset collection name, such as wits.

Query parameters

ParameterTypeRequiredDefaultDescription
queryJSON object encoded as a stringYesMongoDB-style match conditions.
sortJSON object encoded as a stringYesSort fields using 1 for ascending and -1 for descending.
limitintegerYesMaximum records to return; 1 through 10000.
skipintegerNo0Records to skip; must be 0 or greater.
fieldscomma-separated stringNoall fieldsFields to return, such as asset_id,timestamp,data.hole_depth.
include_countbooleanNofalseWhen true, returns the total matching count in the Total response header.

query and sort must be valid JSON objects, not JavaScript or Python object syntax.

Example: latest records for one well

curl --get 'https://data.corva.ai/api/v1/data/corva/wits/' \
--header "Authorization: API ${CORVA_API_KEY}" \
--data-urlencode 'query={"asset_id":12345}' \
--data-urlencode 'sort={"timestamp":-1}' \
--data-urlencode 'limit=100' \
--data-urlencode 'fields=asset_id,timestamp,data.hole_depth,data.bit_depth'
import json
import os
import requests

response = requests.get(
"https://data.corva.ai/api/v1/data/corva/wits/",
headers={"Authorization": f"API {os.environ['CORVA_API_KEY']}"},
params={
"query": json.dumps({"asset_id": 12345}),
"sort": json.dumps({"timestamp": -1}),
"limit": 100,
"fields": "asset_id,timestamp,data.hole_depth,data.bit_depth",
},
timeout=30,
)
response.raise_for_status()
records = response.json()

Common query conditions

Goalquery value before URL encoding
One asset{"asset_id":12345}
Several assets{"asset_id":{"$in":[12345,67890]}}
Time range{"asset_id":12345,"timestamp":{"$gte":1715600000000,"$lt":1715686400000}}
Depth range{"asset_id":12345,"measured_depth":{"$gte":10000,"$lt":11000}}
Completion stage{"asset_id":12345,"stage_number":12}
Nested data field{"asset_id":12345,"data.status":"active"}

Count records

GET/api/v1/data/{provider}/{dataset}/count/
ParameterTypeRequiredDescription
queryJSON object encoded as a stringYesMatch conditions.
curl --get 'https://data.corva.ai/api/v1/data/corva/wits/count/' \
--header "Authorization: API ${CORVA_API_KEY}" \
--data-urlencode 'query={"asset_id":12345}'
{
"count": 42810
}

Use this endpoint when only the count is needed. For records plus a count, use include_count=true on the find endpoint and read the Total response header.

Find records with a JSON request body

POST/api/v1/data/{provider}/{dataset}/list/

This endpoint performs the same read operation as the GET endpoint but keeps large or complex query values out of the URL.

Body fieldTypeRequiredDefaultDescription
queryobjectYesMatch conditions.
sortobjectYesSort conditions.
limitintegerYes1 through 10000.
skipintegerNo0Records to skip.
fieldsstringNoall fieldsComma-separated fields.
include_countbooleanNofalseAdd the total to the Total response header.
curl 'https://data.corva.ai/api/v1/data/corva/wits/list/' \
--request POST \
--header "Authorization: API ${CORVA_API_KEY}" \
--header 'Content-Type: application/json' \
--data '{
"query": {
"asset_id": 12345,
"timestamp": {"$gte": 1715600000000, "$lt": 1715686400000}
},
"sort": {"timestamp": 1},
"skip": 0,
"limit": 1000,
"fields": "asset_id,timestamp,data.hole_depth",
"include_count": true
}'

Simple aggregation

GET/api/v1/data/{provider}/{dataset}/aggregate/
ParameterTypeRequiredDefaultDescription
matchJSON object encoded as a stringYes$match expression.
sortJSON object encoded as a stringYesSort expression.
limitintegerYes1 through 10000. Applied before grouping.
skipintegerNo0Records to skip before grouping.
groupJSON object encoded as a stringNo$group expression.
projectJSON object encoded as a stringNo$project expression.
curl --get 'https://data.corva.ai/api/v1/data/corva/wits/aggregate/' \
--header "Authorization: API ${CORVA_API_KEY}" \
--data-urlencode 'match={"asset_id":12345}' \
--data-urlencode 'sort={"timestamp":-1}' \
--data-urlencode 'limit=10000' \
--data-urlencode 'group={"_id":"$asset_id","max_hole_depth":{"$max":"$data.hole_depth"}}' \
--data-urlencode 'project={"_id":0,"asset_id":"$_id","max_hole_depth":1}'
[
{
"asset_id": 12345,
"max_hole_depth": 10482.5
}
]

Aggregation pipeline

Use a pipeline for multiple aggregation stages. Prefer POST when the pipeline is non-trivial.

POST/api/v1/data/{provider}/{dataset}/aggregate/pipeline/

The body must contain a non-empty stages array.

curl 'https://data.corva.ai/api/v1/data/corva/wits/aggregate/pipeline/' \
--request POST \
--header "Authorization: API ${CORVA_API_KEY}" \
--header 'Content-Type: application/json' \
--data '{
"stages": [
{"$match": {"asset_id": 12345}},
{"$sort": {"timestamp": -1}},
{"$limit": 1000},
{"$group": {
"_id": "$asset_id",
"max_hole_depth": {"$max": "$data.hole_depth"}
}},
{"$project": {
"_id": 0,
"asset_id": "$_id",
"max_hole_depth": 1
}}
]
}'

The GET form uses the same path and accepts the entire array in a required stages query parameter:

GET /api/v1/data/{provider}/{dataset}/aggregate/pipeline/?stages=[...]

Only allowed aggregation stages can run. Pipelines must be non-empty, and write-oriented stages such as $merge are not available to ordinary customer read requests.

Discover datasets

Search accessible definitions

GET/api/v1/dataset/
ParameterTypeRequiredDescription
searchstringNoSearch by dataset name. Defaults to an empty search.
plottablebooleanNoFilter by whether the dataset can be plotted.
temporarybooleanNoFilter temporary datasets.
curl --get 'https://data.corva.ai/api/v1/dataset/' \
--header "Authorization: API ${CORVA_API_KEY}" \
--data-urlencode 'search=wits' \
--data-urlencode 'temporary=false'

Representative definition:

[
{
"id": 15,
"company_id": 100,
"provider": "corva",
"name": "corva#wits",
"friendly_name": "wits",
"description": "WITS data",
"schema": {},
"data_type": "time",
"plottable": true,
"temporary": false,
"permission_workflow": {
"read": "request",
"write": "request",
"delete": "request"
},
"indexes": [],
"statistics": {
"size": 1000000,
"count": 282034,
"storage_size": 8839043,
"index_size": 199234282
}
}
]

List datasets by company

GET/api/v1/dataset/company/
ParameterTypeRequiredDescription
plottablebooleanNoFilter by whether the dataset can be plotted.
typestringNoDataset type: time, depth, reference, or timeseries.
curl --get 'https://data.corva.ai/api/v1/dataset/company/' \
--header "Authorization: API ${CORVA_API_KEY}" \
--data-urlencode 'type=time'

The response groups available datasets by company name:

{
"Example Company": [
{
"id": 44,
"provider": "corva",
"name": "activities",
"type": "time"
}
]
}

Get one definition

GET/api/v1/dataset/{provider}/{name}/
curl 'https://data.corva.ai/api/v1/dataset/corva/wits/' \
--header "Authorization: API ${CORVA_API_KEY}"

Paging safely

For record reads, increment skip by the number of records returned and keep the same deterministic sort. Stop when the response contains fewer records than limit. If records can be added during a long export, prefer cursor-like time/depth windows to a changing global offset.

skip = 0
limit = 10000

while True:
response = requests.get(
"https://data.corva.ai/api/v1/data/corva/wits/",
headers={"Authorization": authorization},
params={
"query": json.dumps({"asset_id": 12345}),
"sort": json.dumps({"timestamp": 1, "_id": 1}),
"skip": skip,
"limit": limit,
},
timeout=30,
)
response.raise_for_status()
batch = response.json()
if not batch:
break

process(batch)
skip += len(batch)
if len(batch) < limit:
break

Errors

StatusCommon cause
401 UnauthorizedInvalid API key/Bearer token or malformed Authorization header.
403 ForbiddenThe identity lacks dataset, company, asset, or well permissions.
404 Not FoundProvider or dataset does not exist or is not visible to the identity.
422 Unprocessable ContentMissing required fields; invalid JSON or sort direction; a value outside its range; or an insert body containing fewer than 1 or more than 1,000 records.

For task-oriented explanations, see Read Dataset Records and Aggregate Dataset Data. The interactive Data API explorer remains available for endpoints not yet covered here.