Insert Data
The Data API allows you to insert time-series data into the Capture platform using two authentication methods: JWT tokens or API tokens.
Timestamps are specified in UTC time.
Endpoint
| URL | https://portal.captureplatform.com/api/data |
| METHOD | POST |
Authentication Methods
The Data API supports two authentication methods. Choose the one that best fits your use case.
Option 1: JWT Token Authentication
When to use: Use JWT tokens when you have edge devices or loggers that need to authenticate and send data directly to the platform. This method is ideal for IoT devices and embedded systems that collect data autonomously.
To be able to insert data, a logger user is needed and a "retention to write" needs to be attached to the logger.
Headers
| Header | Value |
|---|---|
| AuthVersion | V0.0.1 |
| DataVersion | V0.0.3 |
| Authorization | Bearer <Received auth token> |
Example Request
curl -X POST "https://portal.captureplatform.com/api/data" \
-H "AuthVersion: V0.0.1" \
-H "DataVersion: V0.0.3" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"Metrics": [
{
"Name": "temperature",
"Tags": {
"device_id": "logger_001",
"location": "warehouse_A"
},
"Fields": {
"value": 22.5,
"humidity": 65
},
"Timestamp": "1708876800"
}
],
"Timestamp": "1708876800"
}'
Option 2: API Token Authentication
When to use: Use API tokens for backend services, scheduled jobs, or applications that integrate with the Capture platform. This method provides more flexibility as you can specify the target database and retention policy directly in the request.
Make sure the API token has write permissions on the desired database.
Headers
| Header | Value |
|---|---|
| AuthVersion | V0.0.1 |
| DataVersion | V0.0.5 |
| Content-Type | application/json |
| Authorization | ApiToken <Your api token> |
Query Parameters
| Parameter | Value |
|---|---|
| Database | <UID of the database> |
| Retention | <Name of the retention> |
The UID of the database can be found in the edit database panel on the Capture platform.
Example Request
curl -X POST "https://portal.captureplatform.com/api/data?Database=a1b2c3d4-e5f6-7890-abcd-ef1234567890&Retention=oneYear" \
-H "AuthVersion: V0.0.1" \
-H "DataVersion: V0.0.5" \
-H "Content-Type: application/json" \
-H "Authorization: ApiToken cap_1a2b3c4d5e6f7g8h9i0j" \
-d '{
"Metrics": [
{
"Name": "production_data",
"Tags": {
"machine_id": "CNC_001",
"shift": "morning"
},
"Fields": {
"parts_produced": 150,
"defect_count": 2,
"efficiency": 98.7
},
"Timestamp": "1708876800"
}
],
"Timestamp": "1708876800"
}'
Key Differences:
| Feature | JWT Token | API Token |
|---|---|---|
| Use Case | Edge devices, IoT sensors, loggers | Backend services, integrations, scheduled jobs |
| Database Target | Pre-configured with logger | Specified in query parameters |
| Flexibility | Fixed retention per logger | Can switch databases and retentions |
| Token Management | Obtained via authentication flow | Generated and managed in Capture platform |
| Best For | Autonomous data collection at the edge | Centralized data management and integration |
Request Body
The request body structure is the same regardless of the authentication method used. It contains an array of Metrics with a MeasurementName, Tags, Fields, and Timestamps. We call this object a CaptureObject.
Here's a complete example showing how to insert data into two different measurements. This example includes 2 rows for MeasurementName (with different serial numbers and machine IDs) and 1 row for DifferentMeasurementName:
{
"Metrics": [
{
"Name": "MeasurementName",
"Tags": {
"SerialNumber": "123456",
"MachineId": "Machine1"
},
"Fields": {
"sensor1": 20,
"sensor2": 50.5
},
"Timestamp": "1585554027" // Time of data collection. This gets inserted into the database.
},
{
"Name": "MeasurementName",
"Tags": {
"SerialNumber": "123457",
"MachineId": "Machine2"
},
"Fields": {
"sensor1": 30,
"sensor2": 15.9
},
"Timestamp": "1585554028"
},
{
"Name": "DifferentMeasurementName",
"Tags": {
"OperatorId": "654321"
},
"Fields": {
"action1": "This is an action"
},
"Timestamp": "1585559852"
}
],
"Timestamp": "1588765687" // Optional. Used in background to sort data for more efficient inserting.
}
This request will create the following tables in your database:
MeasurementName table:
| timestamp | SerialNumber (tag) | MachineId (tag) | sensor1 (field) | sensor2 (field) |
|---|---|---|---|---|
| 1585554027 | 123456 | Machine1 | 20.0 | 50.5 |
| 1585554028 | 123457 | Machine2 | 30.0 | 15.9 |
DifferentMeasurementName table:
| timestamp | OperatorId (tag) | action1 (field) |
|---|---|---|
| 1585559852 | 654321 | "This is an action" |
Field Types
The value of the field can be one of the following types.
Primitives
| Type | Example |
|---|---|
| Float | "sensor1": 30.0 |
| Int | "sensor1": 30 |
| String | "sensor1": "30" |
| Bool | "sensor1": true |
If a field contains a base64 encoded blob, there is a limit of 1024 bytes. If you need to store larger blobs, link a blobstore and those items will be automatically uploaded to the blobstore.
Binary / Blob Fields
To upload binary data (images, files, etc.) directly to a linked blobstore, add the DataType: PlainWithBinary header to your request and encode the field value as a data URI:
data:<mimetype>[;<key>=<value>]*;base64,<base64-encoded-data>
The field value in the database will be replaced with the blob URL after a successful upload.
Supported metadata parameters:
| Parameter | Description | Example |
|---|---|---|
ext | File extension to use in the blob name | ext=jpeg |
dstpath | Custom path/filename in the blobstore. If omitted, the path is auto-generated as <measurement>/<field>/<timestamp>_<tagshash>.<ext> | dstpath=images/camera1/photo.jpg |
Example — uploading a JPEG image:
curl -X POST "https://portal.captureplatform.com/api/data?Database=<db-uid>&Retention=oneYear" \
-H "AuthVersion: V0.0.1" \
-H "DataVersion: V0.0.5" \
-H "DataType: PlainWithBinary" \
-H "Content-Type: application/json" \
-H "Authorization: ApiToken YOUR_SECRET_API_TOKEN_HERE" \
-d '{
"Metrics": [
{
"Name": "inspections",
"Tags": { "camera": "cam_01" },
"Fields": {
"image": "data:image/jpeg;ext=jpeg;base64,/9j/4AAQSkZJRgAB..."
},
"Timestamp": "1708876800"
}
]
}'
After the request, the image field in the database will contain the blobstore URL instead of the raw data URI.
Mind that, just like in capture data that you can choose the field name.
Objects
"Defect": {
"X": 250,
"Y": 10000,
"Size-X": 50,
"Size-Y": 100
}
Objects like the example above will be flattened into separate fields in the database.
| Defect_X | Defect_Y | Defect_Size-X | Defect_Size-Y |
|---|---|---|---|
| 250 | 10000 | 50 | 100 |
Array of Objects
"Defects": [
{
"X": 250,
"Y": 10000,
"Size-X": 50,
"Size-Y": 100,
"#Type": "defect1"
},
{
"X": 300,
"Y": 10000,
"Size-X": 50,
"Size-Y": 100,
"#Type": "defect1"
},
{
"X": 250,
"Y": 10000,
"Size-X": 50,
"Size-Y": 100,
"#Type": "defect2"
}
]
The following example will result in:
| Defects_X | Defects_Y | Defects_Size-X | Defects_Size-Y | Type (tag) | arrIndex (tag) |
|---|---|---|---|---|---|
| 250 | 10000 | 50 | 100 | defect1 | 0 |
| 300 | 10000 | 50 | 100 | defect1 | 1 |
| 250 | 10000 | 50 | 100 | defect2 | 2 |
When multiple objects in an array share the same timestamp, an arrIndex tag is automatically added to make each row unique. This tag indicates the position in the array (starting from 0).
You can also create custom tags by prefixing field names with #. In the example above, #Type becomes a tag named Type in the database instead of a field.
CDC Object (Change Data Capture)
CDC (Change Data Capture) is a specialized format for tracking data modifications over time. Use CDC when you need to:
- Track the history of changes to data points
- Implement update and delete operations on existing data
- Mirror database operations from external systems
Let's look at an example of creating a new sensor reading in the measurement_name table:
Unescaped CDC Object:
{
"before": null,
"after": {
"time": "2026-02-16 10:52:00.000 +0100",
"gateway_id": "241776eb-945a-4a53-958c-9b8681e42af0",
"gateway": "Logger1",
"name": "sensor1",
"value_count": 1000,
"value_avg": 50,
"value_min": 40,
"value_max": 60,
"tags": "{\"Connection_Tag\": \"connection_1\", \"loggerActivationUid\": \"241776eb-945a-4a53-958c-9b8681e42af0\"}"
},
"source": {
"db": "capture",
"table": "measurement_name"
},
"op": "c",
"ts_ns": 1771235558000000000
}
This CDC object must be stringified and placed in the Fields. Here's the complete request:
{
"Metrics": [
{
"Name": "cdc_example",
"Tags": {},
"Fields": {
"cdc_payload": "{\"before\": null,\"after\": {\"time\": \"2026-02-16 10:52:00.000 +0100\",\"gateway_id\": \"241776eb-945a-4a53-958c-9b8681e42af0\",\"gateway\": \"Logger1\",\"name\": \"sensor1\",\"value_count\": 1000,\"value_avg\": 50,\"value_min\": 40,\"value_max\": 60,\"tags\": \"{\\\"Connection_Tag\\\": \\\"connection_1\\\", \\\"loggerActivationUid\\\": \\\"241776eb-945a-4a53-958c-9b8681e42af0\\\"}\"},\"source\": {\"db\": \"capture\",\"table\": \"measurement_name\"},\"op\": \"c\",\"ts_ns\": 1771235558000000000}"
},
"Timestamp": "1771235558"
}
]
}
This creates the following row in the measurement_name table:
| time | gateway_id | gateway | name | value_count | value_avg | value_min | value_max | tags |
|---|---|---|---|---|---|---|---|---|
| "2026-02-16 10:52:00.000 +0100" | "241776eb-945a-4a53-958c-9b8681e42af0" | "Logger1" | "sensor1" | 1000 | 50 | 40 | 60 | {"Connection_Tag": "connection_1", "loggerActivationUid": "241776eb-945a-4a53-958c-9b8681e42af0"} |
Understanding the CDC Structure:
The CDC object contains several key components that control how data is inserted or modified:
CaptureObject Structure Requirements
Name: Must start withcdc_(e.g.,cdc_example). The rest of the name is arbitrary.Tags: Must be an empty object{}.Fields: Contains the stringified CDC object. The field key name (e.g.,cdc_payload) is arbitrary - the actual table name comes fromsource.tablein the CDC object.
CDC Object Fields
before
The current state of the data point in the database. In our example, this is null because we're creating a new row that doesn't exist yet.
| Operation Type | before Value |
|---|---|
Create (c) | null |
Read (r) | Current data |
Update (u) | Current data |
Delete (d) | Current data |
after
The new state of the data point after the operation. In our example, this contains all the sensor data we want to insert:
{
"time": "2026-02-16 10:52:00.000 +0100",
"gateway_id": "241776eb-945a-4a53-958c-9b8681e42af0",
"gateway": "Logger1",
"name": "sensor1",
"value_count": 1000,
"value_avg": 50,
"value_min": 40,
"value_max": 60,
"tags": "{\"Connection_Tag\": \"connection_1\", \"loggerActivationUid\": \"241776eb-945a-4a53-958c-9b8681e42af0\"}"
}
The structure of after must match your target table schema exactly - each field in this object becomes a column in the database.
| Operation Type | after Value |
|---|---|
Create (c) | New data |
Read (r) | Current data |
Update (u) | Updated data |
Delete (d) | null |
source
Specifies where this data should be written:
db: The database name (in our example:"capture")table: The table/measurement name (in our example:"measurement_name")
op (operation)
The type of operation to perform. In our example, "c" means we're creating a new data point.
| Operation | Description | Use Case |
|---|---|---|
"c" | Create - Insert a new data point | Adding new sensor readings |
"r" | Read - Read-only operation (rarely used for API) | Replication/synchronization only |
"u" | Update - Modify an existing data point | Correcting existing data |
"d" | Delete - Remove an existing data point | Removing invalid data |
ts_ns
The timestamp of the operation in nanoseconds since Unix epoch. In our example: 1771235558000000000 represents the time when this change occurred.