Skip to content

Data model

Every guide on this site walks the same structure. This page is that structure in one place.

  • Company your Sitemark organisation company-level datasets component models — the catalogue of panel, inverter and structure types your sites are built from
    • Site a physical site: a solar park, a wind farm site-level datasets design files (DXF and PDF overlays), tickets, and components — the physical elements on site, which persist across flights
      • Operation one drone flight or data capture on that site operation-level datasets detection outputs, orthomosaics, digital surface models, point clouds, contour lines, annotations
Every dataset hangs off one of these three levels, and the level is visible in the dataset itself: a company-level dataset has no site_id and no operation_id, a site-level one has a site_id only, an operation-level one has both. That is also what decides which filter returns it.

A layer lives at the level it belongs to: detections come out of a single flight, so they are operation-level; components describe the site itself and outlive any one flight, so they are site-level.

This is what decides which filter you scope by, and it is the thing that most often catches people out:

You want Filter by Why
Detections (DEVIATIONS, SOLAR_ANOMALIES, WIND_ANOMALIES, …) and annotations (ANNOTATIONS) operation_id, or site_id for every flight at once Both are produced per capture
Components (TRENCHES, SOLAR_PANELS, FENCES, ROADS, …) site_id Components are design objects tracked on the site, across flights
Tickets (PUNCH_LIST_ITEMS) site_id Tickets persist across flights

Filtering components or tickets by operation_id returns nothing, because they are not attached to a single flight.

A dataset’s type says how the data is stored, and its semantic_type says what the data represents. Only one type holds layer features:

geojson-layer vector

Holds layer features — one row per record, each with its own geometry and properties. These are what /layerFeatures returns.

  • deviations, solar anomalies
  • trenches, panels, fences and other components
  • tickets, annotations

Every other type no features

Imagery, models and files. The data lives in tiles or files rather than in records, so these datasets have no layer features.

  • tile-layer, tile-layer-raw — visual and thermal orthomosaics, surface models
  • 3d-point-cloud, 3d-mesh — point clouds and 3D models
  • photos, georeferenced-pdf, dxf

So a thermal orthomosaic and a digital surface model are both stored as tile-layer-raw but differ in semantic_type (THERMAL versus DIGITAL_SURFACE_MODEL), while deviations and tickets are both geojson-layer and differ the same way. Querying /layerFeatures for a dataset that is not a geojson-layer returns nothing — there are no records to return.

type is a filter, not a response field: List datasets returns semantic_type but not type. To get only the datasets that hold records, filter on it — GET /datasets?site_id=<id>&type=geojson-layer.

A layer feature’s properties object is keyed by property-definition UUID rather than by name, because property schemas are configurable per dataset. List property definitions is the dictionary for those UUIDs — name, type, and the option list for dropdown properties:

Terminal window
curl -X GET "https://api.sitemark.com/propertyDefinitions?_pageSize=1000" \
-H "Authorization: ApiKey <your-api-key>"

Fetch it once, build a UUID → name map, and use it to turn feature properties into readable rows.

Two things to know before you do:

  • Resolve across every dataset you have access to rather than filtering by dataset_id. A dataset can inherit property definitions from a parent dataset, so a UUID on a feature is not always defined on that feature’s own dataset.
  • Not every key is a UUID. Built-in properties — STATUS, ASSIGNEE, VOLUME and the like — are keyed by their own name and are not returned by List property definitions. Leave any key that is not a UUID as it is instead of looking it up.