Synchronize asset data from your asset management or telematics systems to Scan2Evolve. Create, update, and manage asset records, assign meters, and sync meter readings in a single batch operation.
Process 100-500 assets per request. Each asset is processed independently for reliability.
Safe to retry failed requests. Same externalAssetId with same data produces the same result.
Assign meters to assets, sync current readings, and upload historical meter reading data.
Track meters expected from sync, monitor last seen timestamps, and detect missing syncs.
{
"assets": [
{
"externalAssetId": "ASSET-001",
"name": "Water Pump"
}
]
}Use this pattern for daily/hourly syncs. Uses currentReading in meters[] array.
{
"syncBatchId": "batch-2024-11-18-001",
"assets": [
{
"externalAssetId": "ASSET-100245",
"code": "PUMP-001",
"name": "Main Water Pump",
"assetTypeName": "Pump",
"siteCode": "SITE-42",
"serial": "SN-12345",
"status": "ACTIVE",
"description": "Primary water pump for building A",
"customFields": {
"manufacturer": "ABC Pumps",
"model": "XP-5000",
"installationDate": "2020-01-15"
},
"meters": [
{
"meterCode": "ODOMETER_KM",
"isActive": true,
"isApplicable": true,
"equipmentStatus": "EQUIPPED",
"expectedFromSync": true,
"currentReading": {
"value": 25000,
"recordedAt": "2024-11-18T10:30:00Z",
"source": "SYNC",
"status": "NORMAL",
"note": "Latest reading from telematics"
}
},
{
"meterCode": "HOURS",
"isActive": true,
"isApplicable": true,
"equipmentStatus": "EQUIPPED",
"expectedFromSync": true,
"currentReading": {
"value": 5000,
"unit": "HOURS",
"recordedAt": "2024-11-18T10:30:00Z",
"source": "SYNC",
"status": "NORMAL"
}
}
]
},
{
"externalAssetId": "ASSET-100246",
"code": "VEHICLE-001",
"name": "Delivery Truck #1",
"assetTypeName": "Vehicle",
"siteCode": "WAREHOUSE-A",
"serial": "VIN-987654321",
"status": "ACTIVE",
"meters": [
{
"meterCode": "ODOMETER_KM",
"isActive": true,
"expectedFromSync": true,
"equipmentStatus": "EQUIPPED",
"currentReading": {
"value": 125000,
"recordedAt": "2024-11-18T10:30:00Z",
"source": "SYNC",
"status": "NORMAL"
}
}
]
}
]
}{
"syncBatchId": "batch-2024-11-18-001",
"results": [
{
"externalAssetId": "ASSET-100245",
"status": "UPDATED",
"assetId": "asset_9b3c8d7e6f5a4b3c2d1e0f",
"warnings": []
},
{
"externalAssetId": "ASSET-100246",
"status": "UPDATED",
"assetId": "asset_8a2b7c6d5e4f3a2b1c0d9e",
"warnings": []
}
],
"errors": []
}lastSeenInSyncAt) is automatically updated for meters with expectedFromSync: true.Use this pattern for first-time sync or backfill. Uses both currentReading and meterReadings array.
{
"syncBatchId": "batch-initial-setup-001",
"assets": [
{
"externalAssetId": "ASSET-100245",
"code": "PUMP-001",
"name": "Main Water Pump",
"assetTypeName": "Pump",
"siteCode": "SITE-42",
"serial": "SN-12345",
"status": "ACTIVE",
"description": "Primary water pump for building A",
"customFields": {
"manufacturer": "ABC Pumps",
"model": "XP-5000",
"installationDate": "2020-01-15"
},
"meters": [
{
"meterCode": "ODOMETER_KM",
"isActive": true,
"isApplicable": true,
"equipmentStatus": "EQUIPPED",
"initialReading": 15000,
"installedAt": "2020-01-15T00:00:00Z",
"expectedFromSync": true,
"validationThresholds": {
"maxDeltaPerDay": 1000
},
"currentReading": {
"value": 25000,
"recordedAt": "2024-11-18T10:30:00Z",
"source": "SYNC",
"status": "NORMAL",
"note": "Latest reading from telematics"
}
},
{
"meterCode": "HOURS",
"isActive": true,
"isApplicable": true,
"equipmentStatus": "EQUIPPED",
"initialReading": 0,
"expectedFromSync": true,
"currentReading": {
"value": 5000,
"unit": "HOURS",
"recordedAt": "2024-11-18T10:30:00Z",
"source": "SYNC",
"status": "NORMAL"
}
}
],
"meterReadings": [
{
"meterCode": "ODOMETER_KM",
"value": 24000,
"recordedAt": "2024-11-17T10:00:00Z",
"source": "SYNC",
"status": "NORMAL"
},
{
"meterCode": "ODOMETER_KM",
"value": 24500,
"recordedAt": "2024-11-17T18:00:00Z",
"source": "SYNC",
"status": "NORMAL"
},
{
"meterCode": "ODOMETER_KM",
"value": 24800,
"unit": "KM",
"recordedAt": "2024-11-18T08:00:00Z",
"source": "SYNC",
"status": "NORMAL"
}
],
"metadata": {
"externalSystem": "FleetManager",
"lastSync": "2024-11-18T10:30:00Z"
}
},
{
"externalAssetId": "ASSET-100246",
"code": "VEHICLE-001",
"name": "Delivery Truck #1",
"assetTypeName": "Vehicle",
"siteCode": "WAREHOUSE-A",
"serial": "VIN-987654321",
"status": "ACTIVE",
"meters": [
{
"meterCode": "ODOMETER_KM",
"isActive": true,
"expectedFromSync": true,
"equipmentStatus": "EQUIPPED",
"currentReading": {
"value": 125000,
"recordedAt": "2024-11-18T10:30:00Z",
"source": "SYNC",
"status": "NORMAL"
}
}
],
"meterReadings": [
{
"meterCode": "ODOMETER_KM",
"value": 123000,
"unit": "KM",
"recordedAt": "2024-11-17T08:00:00Z",
"source": "SYNC",
"status": "NORMAL"
},
{
"meterCode": "ODOMETER_KM",
"value": 124000,
"unit": "KM",
"recordedAt": "2024-11-17T18:00:00Z",
"source": "SYNC",
"status": "NORMAL"
}
]
}
]
}{
"syncBatchId": "batch-initial-setup-001",
"results": [
{
"externalAssetId": "ASSET-100245",
"status": "CREATED",
"assetId": "asset_9b3c8d7e6f5a4b3c2d1e0f",
"warnings": []
},
{
"externalAssetId": "ASSET-100246",
"status": "CREATED",
"assetId": "asset_8a2b7c6d5e4f3a2b1c0d9e",
"warnings": []
}
],
"errors": []
}currentReading and historical meterReadings are processed. The currentReading updates telematics tracking, while meterReadings are checked for duplicates before being created.| Field | Type | Required | Description |
|---|---|---|---|
externalAssetId | string | Required | Unique identifier from your external system. Used as the primary key for upsert operations. Must be unique within your organization. |
name | string | Required | Asset name. |
code | string | Optional | Asset code. If not provided, externalAssetId is used as the code. Must be unique within your organization. |
assetTypeName | string | Optional | Asset type name (matched by name). Asset type must exist in your organization. If assetTypeName is provided but the asset type doesn't exist, the sync will fail with an error. Ensure asset types are created before syncing assets that reference them. |
siteCode | string | Optional | Site code (matched by code). Site must exist in your organization. If not found, a warning is added and the asset is created without a site assignment. |
serial | string | Optional | Serial number of the asset. |
status | enum | Optional | Asset status: ACTIVE, INACTIVE, or RETIRED. Defaults to ACTIVE. Setting to INACTIVE or RETIRED deactivates all asset meters. |
description | string | Optional | Asset description. |
customFields | object | Optional | Custom fields as a JSON object. Can store any additional information about the asset (e.g., manufacturer, model, installation date). |
meters | array | Optional | Array of meter assignment objects. See Meters and Readings section below. |
meterReadings | array | Optional | Array of historical meter reading objects. See Meters and Readings section below. |
metadata | object | Optional | Additional metadata as a JSON object. Can store any additional information (e.g., external system name, last sync timestamp). |
The meters array assigns meters to assets and optionally includes the current reading:
{
"syncBatchId": "batch-003",
"assets": [
{
"externalAssetId": "ASSET-003",
"code": "VEHICLE-001",
"name": "Delivery Truck #1",
"assetTypeName": "Vehicle",
"siteCode": "WAREHOUSE-A",
"status": "ACTIVE",
"meters": [
{
"meterCode": "ODOMETER_KM",
"isActive": true,
"expectedFromSync": true,
"equipmentStatus": "EQUIPPED",
"currentReading": {
"value": 25000,
"unit": "KM",
"recordedAt": "2024-11-18T10:30:00Z",
"source": "SYNC"
}
}
]
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
meterCode | string | Required | Meter code (matched by exact code - case-sensitive). Meter must exist in your organization. If not found, the meter assignment is rejected, a warning is added, and the attempt is logged for admin review in Admin → Meters → Missing Meters. |
isActive | boolean | Optional | Whether the meter is active for this asset. Defaults to true. |
isApplicable | boolean | Optional | Whether the meter applies to this asset. Defaults to true. |
equipmentStatus | enum | Optional | Equipment status: UNKNOWN, EQUIPPED, or NOT_EQUIPPED. Defaults to UNKNOWN. |
initialReading | number | Optional | Initial reading value when the meter was installed. |
installedAt | string | Optional | Installation date in ISO 8601 format (e.g., 2020-01-15T00:00:00Z). |
expectedFromSync | boolean | Optional | Whether readings are expected from sync (for telematics tracking). Defaults to false. When true, the system tracks lastSeenInSyncAt and consecutiveMissingSyncs. |
validationThresholds | object | Optional | Validation thresholds as a JSON object (e.g., { "maxDeltaPerDay": 1000 }). |
currentReading | object | Optional | Current reading object. See Current Reading section below. |
The currentReading object within a meter assignment provides the latest reading value:
| Field | Type | Required | Description |
|---|---|---|---|
value | number | Required | Reading value (integer or decimal). |
recordedAt | string | Optional | Recording date in ISO 8601 format. Defaults to current time if not provided. |
source | enum | Optional | Reading source: SYNC or INSPECTION. Defaults to SYNC. |
status | enum | Optional | Reading status: NORMAL or NOT_EQUIPPED. Defaults to NORMAL. |
note | string | Optional | Optional note about the reading. |
The unit field is requiredfor all meter readings and must match the meter's unit definition exactly (case-sensitive). This prevents data corruption.
If unit is missing or doesn't match: Reading is rejected and a warning is added to the response.
| Scenario | Meter Unit | Reading Unit | Result |
|---|---|---|---|
| Correct unit | KM | KM | ✅ Accepted |
| Case mismatch | KM | km | ❌ Rejected |
| Wrong unit | KM | MILES | ❌ Rejected |
| Missing unit | KM | not provided | ❌ Rejected |
"Unit is required for meter 'ODOMETER_KM'. Please specify the unit in your sync payload.""Unit mismatch for meter 'ODOMETER_KM': expected 'KM', received 'MILES'. Reading rejected to prevent data corruption."KM, MILES, M, FTHOURS, MINUTES, SECONDSKWH, MWH, KWPSI, BAR, KPAC, FLITRE, GALLON, M3The meterReadings array allows you to upload historical meter reading data:
{
"meterReadings": [
{
"meterCode": "ODOMETER_KM",
"value": 24000,
"unit": "KM",
"recordedAt": "2024-11-17T10:00:00Z",
"source": "SYNC",
"status": "NORMAL"
},
{
"meterCode": "ODOMETER_KM",
"value": 24500,
"unit": "KM",
"recordedAt": "2024-11-17T18:00:00Z",
"source": "SYNC",
"status": "NORMAL"
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
meterCode | string | Required | Meter code (matched by exact code - case-sensitive). Meter must exist in your organization. If not found, the reading is rejected, a warning is added, and the attempt is logged for admin review in Admin → Meters → Missing Meters. |
value | number | Required | Reading value (integer or decimal). |
unit | string | Required | Unit must match meter's unit exactly (case-sensitive). Prevents data corruption. Example: "KM", "HOURS", "PSI", "C". If unit is missing or doesn't match, reading is rejected. |
recordedAt | string | Optional | Recording date in ISO 8601 format. Defaults to current time if not provided. |
source | enum | Optional | Reading source: SYNC or INSPECTION. Defaults to SYNC. |
status | enum | Optional | Reading status: NORMAL or NOT_EQUIPPED. Defaults to NORMAL. |
note | string | Optional | Optional note about the reading. |
The system automatically prevents duplicate readings by checking if a reading with the same value and timestamp (within 1 second) already exists. Duplicate readings are silently skipped.
The choice between currentReading and meterReadings depends on your use case. Using the wrong method can affect telematics tracking and performance.
For regular syncs (daily/hourly): Always use currentReading in the meters[] array.
lastSeenInSyncAt timestampconsecutiveMissingSyncs to 0{
"assets": [
{
"externalAssetId": "VEHICLE-001",
"meters": [
{
"meterCode": "ODOMETER_KM",
"expectedFromSync": true,
"currentReading": {
"value": 25000,
"unit": "KM",
"recordedAt": "2024-11-18T10:30:00Z",
"source": "SYNC"
}
}
]
}
]
}lastSeenInSyncAt update{
"assets": [
{
"externalAssetId": "VEHICLE-001",
"meterReadings": [
{
"meterCode": "ODOMETER_KM",
"value": 24000,
"unit": "KM",
"recordedAt": "2024-11-17T10:00:00Z",
"source": "SYNC"
},
{
"meterCode": "ODOMETER_KM",
"value": 24500,
"unit": "KM",
"recordedAt": "2024-11-17T18:00:00Z",
"source": "SYNC"
}
]
}
]
}| Scenario | What to Send | Why |
|---|---|---|
| Regular Sync (Daily/Hourly) | currentReading in meters[] | Updates telematics tracking, efficient, enables health monitoring |
| Initial Setup (First Time Sync) | currentReading + meterReadings | Latest reading updates tracking, historical data fills in the past |
| Catch-Up After Downtime (Missed Syncs) | currentReading + meterReadings | Latest reading updates tracking, missed readings fill the gap |
| Historical Backfill (Bulk Import) | meterReadings only | Duplicate protection, efficient bulk upload |
Don't use meterReadings for regular syncs. This will:
lastSeenInSyncAt won't be updated)The system matches entities using the following logic:
| Entity | Match By | Example |
|---|---|---|
| Asset | code or externalAssetId | { "code": "PUMP-001" } |
| Asset Type | name | { "assetTypeName": "Pump" } |
| Site | code | { "siteCode": "SITE-42" } |
| Meter | code (exact match - case-sensitive) | { "meterCode": "ODOMETER_KM" }Note: Must match exactly. "ODOMETER_KM" ≠ "odometer_km" ≠ "ODOMETER_KM_H" |
{
"syncBatchId": "batch-2024-11-18-001",
"results": [
{
"externalAssetId": "ASSET-100245",
"status": "UPDATED",
"assetId": "asset_9b3c8d7e6f5a4b3c2d1e0f",
"warnings": []
},
{
"externalAssetId": "ASSET-100246",
"status": "CREATED",
"assetId": "asset_8a2b7c6d5e4f3a2b1c0d9e",
"warnings": []
},
{
"externalAssetId": "ASSET-100247",
"status": "CREATED",
"assetId": "asset_7z1y6x5w4v3u2t1s0r9q",
"warnings": []
}
],
"errors": []
}{
"syncBatchId": "batch-2024-11-18-002",
"results": [
{
"externalAssetId": "ASSET-100245",
"status": "CREATED",
"assetId": "asset_9b3c8d7e6f5a4b3c2d1e0f",
"warnings": [
"Meter with exact code 'HOURS' not found. Please create the meter in Admin → Meters → Missing Meters before syncing."
]
}
],
"errors": [
{
"externalAssetId": "ASSET-777",
"message": "name is required"
},
{
"externalAssetId": "ASSET-888",
"message": "Asset type 'NonExistentType' not found. Please create the asset type in-app before syncing assets."
}
]
}| Field | Type | Description |
|---|---|---|
syncBatchId | string | Echo of the syncBatchId from your request, if provided. |
results | array | Array of asset processing results. Each result contains:
|
errors | array | Array of error objects for assets that failed to process. Each error contains:
|
| Status | Description |
|---|---|
CREATED | Asset was newly created |
UPDATED | Existing asset was updated |
DISABLED | Asset was retired or inactivated |
FAILED | Asset processing failed (check errors array for details) |
Process assets in batches of 100-500 for optimal performance. Smaller batches (50-100) are recommended for initial syncs or when dealing with complex asset data including many meters and readings.
The endpoint is idempotent. You can safely retry the same request multiple times. The externalAssetId is used as the unique key, so sending the same asset data multiple times will result in updates rather than duplicates.
Always check both the results and errors arrays in the response. Even if some assets fail, others may have succeeded. Implement retry logic for transient errors (e.g., rate limiting, temporary database issues).
Always use ISO 8601 format for dates (e.g., 2024-11-18T10:30:00Z). See Date/Time Format documentation for details.
Before syncing assets, ensure the following exist in your organization:
assetTypeNamesiteCodemeterCode. Meters must match exactly(case-sensitive). If a meter doesn't exist, the sync willreject the meter assignment/reading and log it for admin review.Missing sites will result in warnings, and assets will be created without site associations. Missing asset types will cause the sync to fail with an error.
When a meter code doesn't exist (exact match required), the system follows this workflow:
Meter assignment or reading is rejected. The meter code must match exactly (case-sensitive). No auto-creation occurs.
Attempt is logged in MissingMeterAttempt table with full context (asset info, readings, sync type, etc.).
Admin reviews attempts in Admin → Meters → Missing Meters, creates the meter, and retries the sync.
"ENGINE_HOURS" ≠ "engine_hours" ≠ "ENGINE_HOURS_H". This ensures data integrity and prevents duplicate meters.For telematics integrations:
expectedFromSync: true for meters that should receive readings from synccurrentReading in meter assignments for the latest reading (most efficient)meterReadings array for uploading historical reading datalastSeenInSyncAt and consecutiveMissingSyncs for health monitoringWhen managing asset status:
status: "RETIRED" or "INACTIVE" to deactivate assets and all their meters| Error Message | Cause | Solution |
|---|---|---|
externalAssetId is required | Missing or empty externalAssetId | Ensure every asset has a unique externalAssetId |
name is required | Missing or empty name | Provide a name field for every asset |
assets array is required | Missing assets array in request | Include an assets array in your request |
assets array cannot be empty | Empty assets array | Include at least one asset in the assets array |
The following scenarios result in errors or warnings:
assetTypeNameis provided but doesn't exist, the sync will fail with an error. Asset types must be created before syncing assets.The following scenarios result in warnings (not errors) - the asset will still be processed, but the missing resources will be skipped:
| Warning Message | Cause | Solution |
|---|---|---|
Asset type 'X' not found. Please create the asset type in-app before syncing assets. | Asset type doesn't exist in your organization | Create the asset type in-app first, then sync. The sync will fail if assetTypeNameis provided but the asset type doesn't exist. |
Site with code 'X' not found, asset will be created without site | Site doesn't exist in your organization | Create the site in-app first, then sync |
Meter with exact code 'X' not found. Please create the meter in Admin → Meters → Missing Meters before syncing. | Meter doesn't exist in your organization (exact code match required - case-sensitive) | Create the meter in-app first, then sync. The attempt is logged in Admin → Meters → Missing Meters for review. Meter codes must match exactly (case-sensitive). |
Meter with exact code 'X' not found. Please create the meter in Admin → Meters → Missing Meters before syncing. | Meter doesn't exist when syncing meter readings (exact code match required - case-sensitive) | Create the meter in-app first, then sync readings. The attempt is logged in Admin → Meters → Missing Meters for review. Meter codes must match exactly (case-sensitive). |
Always check the warnings array in the response to identify missing resources.
| Error Message | Cause | Solution |
|---|---|---|
Invalid token | Invalid or expired API key | Check your API token and regenerate if necessary |
Missing Authorization header | No Authorization header in request | Include Authorization header with Bearer token or Basic auth |
If you exceed the rate limit, you'll receive a 429 response with rate limit headers. Implement exponential backoff and retry after the specified Retry-After period.
See Error Handling for details on rate limiting.