Browse docs

Asset Sync

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.

v1.0

Key Features

Batch Processing

Process 100-500 assets per request. Each asset is processed independently for reliability.

Idempotent Operations

Safe to retry failed requests. Same externalAssetId with same data produces the same result.

Meter Management

Assign meters to assets, sync current readings, and upload historical meter reading data.

Telematics Support

Track meters expected from sync, monitor last seen timestamps, and detect missing syncs.

Request Body Schema

Minimal Request (Required Fields Only)

Example: Simple Asset
{
  "assets": [
    {
      "externalAssetId": "ASSET-001",
      "name": "Water Pump"
    }
  ]
}

Complete Example Requests

Example 1: Regular Sync (Without Historical Readings)

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"
          }
        }
      ]
    }
  ]
}
Response: Regular Sync
{
  "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": []
}
Example 2: Initial Setup with Historical Readings

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"
        }
      ]
    }
  ]
}
Response: Initial Setup with Historical Readings
{
  "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": []
}

Asset Fields

FieldTypeRequiredDescription
externalAssetIdstringRequiredUnique identifier from your external system. Used as the primary key for upsert operations. Must be unique within your organization.
namestringRequiredAsset name.
codestringOptionalAsset code. If not provided, externalAssetId is used as the code. Must be unique within your organization.
assetTypeNamestringOptionalAsset 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.
siteCodestringOptionalSite 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.
serialstringOptionalSerial number of the asset.
statusenumOptionalAsset status: ACTIVE, INACTIVE, or RETIRED. Defaults to ACTIVE. Setting to INACTIVE or RETIRED deactivates all asset meters.
descriptionstringOptionalAsset description.
customFieldsobjectOptionalCustom fields as a JSON object. Can store any additional information about the asset (e.g., manufacturer, model, installation date).
metersarrayOptionalArray of meter assignment objects. See Meters and Readings section below.
meterReadingsarrayOptionalArray of historical meter reading objects. See Meters and Readings section below.
metadataobjectOptionalAdditional metadata as a JSON object. Can store any additional information (e.g., external system name, last sync timestamp).

Meters and Readings

Meter Assignment

The meters array assigns meters to assets and optionally includes the current reading:

Example: Asset with Meters
{
  "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"
          }
        }
      ]
    }
  ]
}
FieldTypeRequiredDescription
meterCodestringRequiredMeter 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.
isActivebooleanOptionalWhether the meter is active for this asset. Defaults to true.
isApplicablebooleanOptionalWhether the meter applies to this asset. Defaults to true.
equipmentStatusenumOptionalEquipment status: UNKNOWN, EQUIPPED, or NOT_EQUIPPED. Defaults to UNKNOWN.
initialReadingnumberOptionalInitial reading value when the meter was installed.
installedAtstringOptionalInstallation date in ISO 8601 format (e.g., 2020-01-15T00:00:00Z).
expectedFromSyncbooleanOptionalWhether readings are expected from sync (for telematics tracking). Defaults to false. When true, the system tracks lastSeenInSyncAt and consecutiveMissingSyncs.
validationThresholdsobjectOptionalValidation thresholds as a JSON object (e.g., { "maxDeltaPerDay": 1000 }).
currentReadingobjectOptionalCurrent reading object. See Current Reading section below.

Current Reading

The currentReading object within a meter assignment provides the latest reading value:

FieldTypeRequiredDescription
valuenumberRequiredReading value (integer or decimal).
recordedAtstringOptionalRecording date in ISO 8601 format. Defaults to current time if not provided.
sourceenumOptionalReading source: SYNC or INSPECTION. Defaults to SYNC.
statusenumOptionalReading status: NORMAL or NOT_EQUIPPED. Defaults to NORMAL.
notestringOptionalOptional note about the reading.

Unit Validation (Strict)

ScenarioMeter UnitReading UnitResult
Correct unitKMKM✅ Accepted
Case mismatchKMkm❌ Rejected
Wrong unitKMMILES❌ Rejected
Missing unitKMnot provided❌ Rejected
Error Messages
  • Missing unit: "Unit is required for meter 'ODOMETER_KM'. Please specify the unit in your sync payload."
  • Unit mismatch: "Unit mismatch for meter 'ODOMETER_KM': expected 'KM', received 'MILES'. Reading rejected to prevent data corruption."
Common Units
Distance: KM, MILES, M, FT
Time: HOURS, MINUTES, SECONDS
Energy: KWH, MWH, KW
Pressure: PSI, BAR, KPA
Temperature: C, F
Volume: LITRE, GALLON, M3

Historical Meter Readings

The 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"
    }
  ]
}
FieldTypeRequiredDescription
meterCodestringRequiredMeter 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.
valuenumberRequiredReading value (integer or decimal).
unitstringRequiredUnit 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.
recordedAtstringOptionalRecording date in ISO 8601 format. Defaults to current time if not provided.
sourceenumOptionalReading source: SYNC or INSPECTION. Defaults to SYNC.
statusenumOptionalReading status: NORMAL or NOT_EQUIPPED. Defaults to NORMAL.
notestringOptionalOptional note about the reading.

When to Use currentReading vs meterReadings

Use currentReading
For Regular Syncs (Daily/Hourly)
  • ✅ Updates telematics tracking automatically
  • ✅ Sets lastSeenInSyncAt timestamp
  • ✅ Resets consecutiveMissingSyncs to 0
  • ✅ More efficient (one reading per meter)
  • ✅ Faster processing
  • ✅ Enables health monitoring
Example:
{
  "assets": [
    {
      "externalAssetId": "VEHICLE-001",
      "meters": [
        {
          "meterCode": "ODOMETER_KM",
          "expectedFromSync": true,
          "currentReading": {
            "value": 25000,
            "unit": "KM",
            "recordedAt": "2024-11-18T10:30:00Z",
            "source": "SYNC"
          }
        }
      ]
    }
  ]
}
Use meterReadings
For Historical Data / Initial Setup
  • ✅ Duplicate protection (same value within 1 second)
  • ✅ Bulk upload support (hundreds of readings)
  • ✅ Efficient for historical backfill
  • ✅ Creates AssetMeter automatically if needed
  • ❌ Does NOT update telematics tracking
  • ❌ No lastSeenInSyncAt update
Example:
{
  "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"
        }
      ]
    }
  ]
}

Decision Guide

ScenarioWhat to SendWhy
Regular Sync
(Daily/Hourly)
currentReading in meters[]Updates telematics tracking, efficient, enables health monitoring
Initial Setup
(First Time Sync)
currentReading + meterReadingsLatest reading updates tracking, historical data fills in the past
Catch-Up After Downtime
(Missed Syncs)
currentReading + meterReadingsLatest reading updates tracking, missed readings fill the gap
Historical Backfill
(Bulk Import)
meterReadings onlyDuplicate protection, efficient bulk upload

Matching Logic

The system matches entities using the following logic:

EntityMatch ByExample
Assetcode or externalAssetId{ "code": "PUMP-001" }
Asset Typename{ "assetTypeName": "Pump" }
Sitecode{ "siteCode": "SITE-42" }
Metercode (exact match - case-sensitive){ "meterCode": "ODOMETER_KM" }
Note: Must match exactly. "ODOMETER_KM" ≠ "odometer_km" ≠ "ODOMETER_KM_H"

Response Schema

Success Response

Example: All assets processed successfully
{
  "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": []
}

Partial Success Response

Example: Some assets succeeded, others failed
{
  "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."
    }
  ]
}
FieldTypeDescription
syncBatchIdstringEcho of the syncBatchId from your request, if provided.
resultsarrayArray of asset processing results. Each result contains:
  • externalAssetId - The asset ID from your request
  • status - CREATED, UPDATED, DISABLED, or FAILED
  • assetId - Internal asset ID (present if successful)
  • warnings - Array of warning messages (e.g., missing asset types, sites, or meters)
errorsarrayArray of error objects for assets that failed to process. Each error contains:
  • externalAssetId - The asset ID from your request
  • message - Error message describing what went wrong

Status Values

StatusDescription
CREATEDAsset was newly created
UPDATEDExisting asset was updated
DISABLEDAsset was retired or inactivated
FAILEDAsset processing failed (check errors array for details)

Best Practices

Batch Size

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.

Idempotency

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.

Error Handling

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

Date Formats

Always use ISO 8601 format for dates (e.g., 2024-11-18T10:30:00Z). See Date/Time Format documentation for details.

Prerequisites

Before syncing assets, ensure the following exist in your organization:

  • Asset Types: Create asset types in-app before syncing assets with assetTypeName
  • Sites: Create sites in-app before syncing assets with siteCode
  • Meters: Create meters in-app before syncing assets with meterCode. 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.

Missing Meters Workflow

When a meter code doesn't exist (exact match required), the system follows this workflow:

1
Reject

Meter assignment or reading is rejected. The meter code must match exactly (case-sensitive). No auto-creation occurs.

2
Log

Attempt is logged in MissingMeterAttempt table with full context (asset info, readings, sync type, etc.).

3
Review

Admin reviews attempts in Admin → Meters → Missing Meters, creates the meter, and retries the sync.

Telematics Integration

For telematics integrations:

  • Set expectedFromSync: true for meters that should receive readings from sync
  • Use currentReading in meter assignments for the latest reading (most efficient)
  • Use meterReadings array for uploading historical reading data
  • Monitor lastSeenInSyncAt and consecutiveMissingSyncs for health monitoring
  • Sync regularly (daily or hourly) to keep data current

Asset Status Management

When managing asset status:

  • Set status: "RETIRED" or "INACTIVE" to deactivate assets and all their meters
  • Historical data (readings, inspections) is preserved when assets are retired
  • Retired assets are no longer available for new inspections

Common Error Scenarios

Validation Errors

Error MessageCauseSolution
externalAssetId is requiredMissing or empty externalAssetIdEnsure every asset has a unique externalAssetId
name is requiredMissing or empty nameProvide a name field for every asset
assets array is requiredMissing assets array in requestInclude an assets array in your request
assets array cannot be emptyEmpty assets arrayInclude at least one asset in the assets array

Resource Not Found Errors and Warnings

The following scenarios result in errors or warnings:

The following scenarios result in warnings (not errors) - the asset will still be processed, but the missing resources will be skipped:

Warning MessageCauseSolution
Asset type 'X' not found. Please create the asset type in-app before syncing assets.Asset type doesn't exist in your organizationCreate 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 siteSite doesn't exist in your organizationCreate 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.

Authentication Errors

Error MessageCauseSolution
Invalid tokenInvalid or expired API keyCheck your API token and regenerate if necessary
Missing Authorization headerNo Authorization header in requestInclude Authorization header with Bearer token or Basic auth

Rate Limiting

Top