Skip to main content

Devices

Devices are the moving entities you track in SpatialFlow. When a device's location crosses a geofence boundary, SpatialFlow detects the transition and triggers automated workflows.

What is a Device?​

A device represents any tracked entity that sends location updates to SpatialFlow. Each device has a unique identifier, maintains location history, and can trigger geofence events when it moves in and out of defined boundaries.

Real-world examples:

  • Delivery vehicles sending GPS updates every 30 seconds
  • Mobile phones tracking field service technicians
  • IoT sensors on shipping containers
  • GPS trackers on fleet assets or equipment

Device Properties​

Every device in SpatialFlow has these core properties:

Device UUID (Server ID)​

  • Server-generated UUID returned as id
  • Required for device API route params (e.g., /devices/{uuid})
  • Store this value and reuse it for all device routes

Device ID (Required)​

  • Your unique, client-defined identifier for the device
  • 1-255 characters
  • Must be unique within your workspace
  • Used for display, filtering, and payloads (not for route params)
  • Example: truck-005, driver-alice, sensor-warehouse-1

Name (Required)​

  • Human-readable label for the device
  • 1-255 characters
  • Example: "Delivery Truck 5", "Alice's Phone", "Warehouse Sensor"

Device Type​

  • Categorizes the kind of device being tracked
  • Defaults to mobile
  • Options:
    • mobile: Mobile phones or tablets
    • vehicle: Cars, trucks, delivery vehicles
    • iot: IoT sensors and beacons
    • tracker: Dedicated GPS trackers
    • other: Custom device types

Metadata (Optional)​

  • Custom JSON object for storing additional attributes
  • Use for filtering, routing, or application-specific data
  • Example:
{
"driver_name": "Alice Johnson",
"vehicle_type": "delivery_van",
"customer_id": "ACME123",
"shift": "morning"
}

Last Known Location​

  • Automatically updated with each location update
  • Includes latitude, longitude, and timestamp
  • Used to detect geofence entry/exit transitions

Location updates can also include accuracy in meters, heading in degrees clockwise from north, and optional battery telemetry: battery_level as an integer percentage from 0 through 100 and battery_charging as a boolean. Send these values with the timestamp of the observation, including when uploading queued locations. Omit measurements the device cannot provide; zero is a valid reading, not a substitute for an unknown value.

Device responses expose last_accuracy, last_heading, last_battery_level, last_battery_charging, and last_battery_time. Battery age is independent of location age: a new location without battery telemetry does not make the previous battery reading current. Older queued observations do not replace newer readings.

In the Live map, an accuracy circle describes uncertainty around the recorded fix. It does not guarantee the device's present position. A heading arrow requires a usable heading measurement; a stationary device or a source without heading may have none. Last-known locations and geofence membership can remain visible after a device stops sharing, so check the observation time before acting.

Location freshness is separate from reporting health. The map's Live category means a fix within two minutes. On-shift reporting allows fifteen minutes to accommodate stationary mobile heartbeats, which may be ten minutes apart. Paused and off-shift devices are excluded from the expected-reporting count.

Active Status​

  • Boolean flag to enable/disable tracking
  • Inactive devices don't process location updates
  • Useful for temporarily pausing tracking without deletion

Registering Devices​

Via API​

Register a new device using the REST API:

curl -X POST https://api.spatialflow.io/api/v1/devices/ \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"device_id": "truck-005",
"name": "Delivery Truck 5",
"device_type": "vehicle",
"metadata": {
"driver": "Alice Johnson",
"route": "downtown",
"capacity_kg": 1000
}
}'

Response:

{
"id": "123e4567-e89b-12d3-a456-426614174000",
"device_id": "truck-005",
"name": "Delivery Truck 5",
"device_type": "vehicle",
"is_active": true,
"shift_status": "off",
"shift_started_at": null,
"shift_paused_at": null,
"shift_ended_at": null,
"shift_resumed_at": null,
"last_location": null,
"last_location_time": null,
"last_heading": null,
"current_session_notes": "",
"in_geofence_ids": [],
"in_geofence_entries": {},
"created_at": "2025-11-10T10:00:00Z",
"updated_at": "2025-11-10T10:00:00Z"
}

A device_id names one device in a workspace. Registering a device_id you already registered there returns your existing device with status 200. If another member, or an API key another member created, has already registered it in the workspace, the request fails with status 409 and error_code DEVICE_ID_IN_USE; choose a different ID. Other workspaces can use the same device_id.

Via Dashboard​

The SpatialFlow Dashboard provides a visual interface for device management:

  1. Navigate to Devices in the sidebar
  2. Click Add Device, which opens the Add New Device dialog
  3. Fill in the device details:
    • Device ID
    • Display Name
    • Device Type
  4. Click Create Device
Team phones don't use this form

A field worker's phone registers itself when they sign in to the SpatialFlow app. This dialog is for a vehicle tracker, an API-driven device, or your own phone.

Registration Required

Register devices via POST /devices before sending location updates. Locations for unknown device_id values are rejected.

Sending Location Updates​

Single Location Update​

Send a location update for a specific device. Use the device UUID (id) returned from registration:

curl -X POST https://api.spatialflow.io/api/v1/devices/{device_uuid}/location \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"latitude": 37.7749,
"longitude": -122.4194,
"timestamp": "2025-11-10T14:30:00Z",
"accuracy": 10.5,
"speed": 15.2,
"heading": 180
}'

Response:

{
"success": true,
"device_id": "truck-005",
"events_triggered": 1,
"message": "Processed 1 locations"
}

Batch Updates​

Update multiple devices or multiple locations in one request:

Each device result includes location_results with the original request index, optional client_location_id, status (accepted, duplicate, or rejected), error_code, and retryable for each processed point. Send a stable client_location_id with each buffered row, keep it unchanged across retries, and make it unique within the submitted device batch. Validate both it and the index against the exact submitted device and point list before consuming that row. Chronological processing does not change the request index.

Delete accepted and duplicate rows. A duplicate means the same fix was already processed and is safe to acknowledge after a lost response. For rejected rows, retain the row when retryable is true and delete or surface it according to your terminal-error policy when false. A missing or malformed outcome must not acknowledge a row. Device-level success remains compatible with older clients and can be true for a partially accepted batch; it is not a per-point acknowledgement.

Deploy the API response contract before releasing clients that consume it. client_location_id correlates responses but does not replace fix identity: retry the same point payload and timestamp unchanged. Reusing an ID for changed coordinates or timestamps creates a distinct fix rather than deduplicating it. The server retains the processed fix fingerprint for at least 48 hours. Stop retrying or age out a buffered point before that window ends; a retry after 48 hours can be admitted as a new fix and charged again. SpatialFlow's mobile buffer ages out unsent locations after 24 hours, within this server window.

curl -X POST https://api.spatialflow.io/api/v1/devices/batch-update \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '[
{
"device_id": "truck-005",
"locations": [
{
"latitude": 37.7749,
"longitude": -122.4194,
"timestamp": "2025-11-10T14:30:00Z",
"client_location_id": "buffer-row-1042",
"accuracy": 10.5
},
{
"latitude": 37.7750,
"longitude": -122.4195,
"timestamp": "2025-11-10T14:31:00Z",
"client_location_id": "buffer-row-1043",
"accuracy": 12.0
}
]
},
{
"device_id": "truck-006",
"locations": [
{
"latitude": 37.7751,
"longitude": -122.4196,
"timestamp": "2025-11-10T14:30:00Z",
"client_location_id": "buffer-row-2088"
}
]
}
]'

Required Fields​

When sending location updates:

Required:

  • latitude: Decimal degrees (-90 to 90)
  • longitude: Decimal degrees (-180 to 180)

Optional but Recommended:

  • timestamp: ISO 8601 format (defaults to current time)
  • accuracy: GPS accuracy in meters (used for filtering)
  • speed: Speed in meters per second
  • heading: Direction in degrees (0-359)
  • altitude: Altitude in meters
  • metadata: Additional contextual data

Location Filtering and Hysteresis​

SpatialFlow implements intelligent filtering to reduce GPS jitter and prevent false triggers:

Accuracy Gate​

  • Rejects location updates with accuracy > 100 meters
  • Ensures only high-quality GPS fixes trigger events

Distance Hysteresis​

  • Ignores movements < 25 meters
  • Reduces noise from stationary devices with GPS drift

Time Hysteresis​

  • Requires 30 seconds between state transitions
  • Prevents rapid enter/exit events at geofence boundaries

Cooldown Period​

  • 60-second cooldown per device × geofence × event type
  • Prevents alert fatigue from repeated triggers
Smart Filtering

These filters are automatically applied to all location updates. You don't need to implement your own filtering logic - SpatialFlow handles it for you.

Device Metadata​

Use Cases​

Device metadata is a flexible JSON field for storing custom attributes:

Fleet Management:

{
"driver_name": "Alice Johnson",
"vehicle_type": "refrigerated_truck",
"license_plate": "ABC-1234",
"vin": "1HGBH41JXMN109186",
"capacity_kg": 5000
}

Field Service:

{
"technician_name": "Bob Smith",
"technician_id": "TECH-001",
"certification": "HVAC-Master",
"tools_equipped": ["multimeter", "leak_detector"],
"shift": "morning"
}

Asset Tracking:

{
"asset_type": "shipping_container",
"container_id": "CONT-98765",
"contents": "electronics",
"customer": "ACME Corp",
"value_usd": 50000
}

IoT Sensors:

{
"sensor_type": "temperature_humidity",
"firmware_version": "2.1.3",
"battery_level": 85,
"measurement_interval_seconds": 300
}

Best Practices​

Keep it flat:

// Good - easy to query and filter
{
"driver_name": "Alice",
"route_id": "R-001",
"priority": "high"
}

// Avoid - nested structures are harder to work with
{
"driver": {
"name": "Alice",
"info": {
"route": "R-001"
}
}
}

Use consistent naming:

// Good - consistent snake_case
{
"driver_name": "Alice",
"vehicle_type": "van",
"customer_id": "C123"
}

// Avoid - mixed conventions
{
"driverName": "Alice",
"vehicle-type": "van",
"CustomerID": "C123"
}

Don't store sensitive data:

  • Avoid storing passwords, API keys, or PII in metadata
  • Use references (IDs) instead of full data
  • Consider encryption for sensitive identifiers

Device Lifecycle​

Active Devices​

Active devices process location updates and trigger geofence events:

  • is_active = true
  • Location updates create events
  • Tracked in your active device count
  • Visible in the dashboard

Inactive Devices​

Deactivate devices to temporarily stop tracking:

curl -X POST https://api.spatialflow.io/api/v1/devices/{device_uuid}/deactivate \
-H "X-API-KEY: YOUR_API_KEY"

Inactive devices:

  • Don't process location updates
  • Don't trigger geofence events
  • Don't count toward active device limits
  • Retain historical data
  • Can be reactivated anytime

Reactivate when needed:

curl -X POST https://api.spatialflow.io/api/v1/devices/{device_uuid}/activate \
-H "X-API-KEY: YOUR_API_KEY"

Deleting Devices​

Permanently remove a device and all its data:

curl -X DELETE https://api.spatialflow.io/api/v1/devices/{device_uuid} \
-H "X-API-KEY: YOUR_API_KEY"
Data Loss

Deleting a device removes:

  • The device record
  • All location history
  • All geofence events
  • This action cannot be undone

Consider deactivating instead of deleting to preserve historical data.

Archiving Strategy​

For devices no longer in use but with valuable historical data:

  1. Deactivate the device to stop processing
  2. Export event data for archival (CSV/JSON)
  3. Update metadata with an archived: true flag
  4. Delete after your data retention period expires

Querying Devices​

An API key needs the devices:read permission to read devices, their events, sessions, and location tracks. A key without it gets a 403. See Authentication.

List All Devices​

Get all devices in your workspace:

curl https://api.spatialflow.io/api/v1/devices \
-H "X-API-KEY: YOUR_API_KEY"

Filter by status:

# Active devices only
curl https://api.spatialflow.io/api/v1/devices?is_active=true \
-H "X-API-KEY: YOUR_API_KEY"

# Inactive devices only
curl https://api.spatialflow.io/api/v1/devices?is_active=false \
-H "X-API-KEY: YOUR_API_KEY"

Get Device Details​

Retrieve a specific device by its UUID (id):

curl https://api.spatialflow.io/api/v1/devices/{device_uuid} \
-H "X-API-KEY: YOUR_API_KEY"

Response:

{
"id": "123e4567-e89b-12d3-a456-426614174000",
"device_id": "truck-005",
"name": "Delivery Truck 5",
"device_type": "vehicle",
"is_active": true,
"last_location": {
"latitude": 37.7749,
"longitude": -122.4194
},
"last_location_time": "2025-11-10T14:30:00Z",
"created_at": "2025-11-10T10:00:00Z",
"updated_at": "2025-11-10T14:30:00Z"
}

Device Events​

Get geofence events triggered by a device:

curl https://api.spatialflow.io/api/v1/devices/{device_uuid}/events?limit=20&offset=0 \
-H "X-API-KEY: YOUR_API_KEY"

Response: Returns a list of geofence events with nested device, geofence, and location objects.

[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "entry",
"device": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"device_id": "truck-005",
"name": "Delivery Truck 5",
"type": "vehicle"
},
"geofence": {
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "Downtown Delivery Zone",
"description": "Main downtown delivery area"
},
"location": {
"latitude": 37.7749,
"longitude": -122.4194,
"accuracy": 10.5,
"speed": 15.2
},
"timestamp": "2025-11-10T14:30:00Z",
"workflows_triggered": ["workflow-789"],
"created_at": "2025-11-10T14:30:01Z"
}
]

Recent Events​

Get recent events across all devices (role-scoped):

curl https://api.spatialflow.io/api/v1/devices/events/recent?limit=50&offset=0 \
-H "X-API-KEY: YOUR_API_KEY"

Returns the same response structure as device events, but filtered by role:

  • Field workers: See only their own device events
  • Managers/Owners: See events for all workspace devices

Bulk Location Import​

For historical data or customer migrations, import locations from CSV files:

CSV Format​

device_id,ts,lat,lon,accuracy_m,speed_mps,heading_deg,meta_driver
truck-005,2025-10-01T14:12:03Z,42.651,-73.756,9.2,12.4,180,alice
truck-006,2025-10-01T14:13:00Z,42.652,-73.757,8.5,15.0,175,bob
truck-005,2025-10-01T14:14:00Z,42.653,-73.758,10.1,13.2,178,alice

Required columns:

  • device_id: Device identifier
  • ts: ISO 8601 timestamp
  • lat: Latitude (-90 to 90)
  • lon: Longitude (-180 to 180)

Optional columns:

  • accuracy_m: GPS accuracy in meters
  • speed_mps: Speed in meters per second
  • heading_deg: Heading in degrees (0-359)
  • meta_*: Metadata columns (e.g., meta_driver, meta_cargo)

Upload CSV​

curl -X POST https://api.spatialflow.io/api/v1/devices/locations/import \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-F "file=@locations.csv"

Response:

{
"job_id": "import-123",
"status": "pending",
"filename": "locations.csv",
"created_at": "2025-11-10T15:00:00Z"
}

Check Import Status​

curl https://api.spatialflow.io/api/v1/devices/locations/import/import-123 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Response:

{
"job_id": "import-123",
"status": "completed",
"filename": "locations.csv",
"total_rows": 1000,
"valid_rows": 998,
"invalid_rows": 2,
"processed_rows": 1000,
"error_rate": 0.2,
"errors": [
{"line": 42, "error": "Invalid timestamp format"},
{"line": 157, "error": "Latitude out of range"}
],
"created_at": "2025-11-10T15:00:00Z",
"started_at": "2025-11-10T15:00:05Z",
"completed_at": "2025-11-10T15:02:15Z"
}
Import Validation
  • Maximum file size: 50 MB
  • Maximum rows: 500,000
  • Error threshold: 1% (jobs fail if >1% of rows are invalid)
  • Timestamps > 5 minutes in future are rejected
  • Timestamps > 30 days old generate warnings

Best Practices​

Naming Conventions​

Use clear, consistent device IDs:

Good examples:

  • truck-005, truck-006, truck-007
  • alice-phone, bob-phone, charlie-phone
  • warehouse-sensor-1, warehouse-sensor-2
  • container-CONT98765

Avoid:

  • device-1, device-2 (not descriptive)
  • my-test-device (use meaningful names)
  • Random UUIDs as device_ids (use UUIDs internally, but make device_id human-readable)

Update Frequency​

Balance real-time accuracy with costs and battery life:

Delivery vehicles (active):

  • 15-30 second intervals
  • Provides smooth tracking
  • Good for customer ETAs

Field service (working):

  • 1-5 minute intervals
  • Adequate for job site tracking
  • Better battery life

Fleet monitoring (idle):

  • 10-15 minute intervals
  • Reduces data costs
  • Sufficient for oversight

Asset tracking (slow-moving):

  • 30-60 minute intervals
  • Minimal battery drain
  • Suitable for long-term tracking
Adaptive Intervals

Consider implementing dynamic update intervals based on device state:

  • Faster when moving (30s)
  • Slower when stationary (5m)
  • Even slower when idle (15m)

Metadata Management​

Do:

  • Use metadata for filtering and routing logic
  • Keep metadata updated as device state changes
  • Use consistent keys across all devices
  • Document your metadata schema

Don't:

  • Store large objects (keep under 1 KB)
  • Use metadata for time-series data
  • Store duplicate information already in device properties
  • Change metadata structure frequently (breaks integrations)

Testing Location Updates​

Always test device tracking before production:

  1. Register a test device:
curl -X POST https://api.spatialflow.io/api/v1/devices/ \
-H "X-API-KEY: YOUR_API_KEY" \
-d '{
"device_id": "test-device-1",
"name": "Test Device",
"device_type": "mobile"
}'
  1. Send test location inside a geofence (use the device UUID from the response):
curl -X POST https://api.spatialflow.io/api/v1/devices/{device_uuid}/location \
-H "X-API-KEY: YOUR_API_KEY" \
-d '{
"latitude": 37.7749,
"longitude": -122.4194
}'
  1. Verify event was triggered:
curl https://api.spatialflow.io/api/v1/devices/{device_uuid}/events \
-H "X-API-KEY: YOUR_API_KEY"
  1. Check workflow execution:
  • View triggered workflows in the dashboard
  • Verify webhook deliveries
  • Confirm action outputs

Battery Optimization (Mobile Devices)​

For mobile apps tracking user location:

iOS:

  • Use allowsBackgroundLocationUpdates sparingly
  • Implement startMonitoringSignificantLocationChanges for low-power tracking
  • Request location updates only when needed

Android:

  • Use fused location provider for battery efficiency
  • Implement geofencing API for power-efficient boundary detection
  • Batch location updates when possible

Both platforms:

  • Only track when app is in use or user has opted in
  • Provide clear location permission explanations
  • Allow users to disable tracking
  • Show battery usage estimates

Common Use Cases​

1. Fleet Tracking​

Track delivery vehicles, service trucks, or company cars:

{
"device_id": "fleet-truck-42",
"name": "Delivery Truck 42",
"device_type": "vehicle",
"metadata": {
"driver_name": "Alice Johnson",
"driver_id": "DRV-001",
"vehicle_make": "Ford",
"vehicle_model": "Transit",
"license_plate": "ABC-1234",
"route": "downtown_deliveries",
"shift": "morning"
}
}

Geofence events:

  • Truck enters customer delivery zone → Send ETA notification
  • Truck exits warehouse → Start delivery timer
  • Truck enters restricted zone → Alert dispatcher

2. Field Service Management​

Monitor technicians and service personnel:

{
"device_id": "tech-alice-phone",
"name": "Alice's Work Phone",
"device_type": "mobile",
"metadata": {
"technician_name": "Alice Johnson",
"employee_id": "EMP-123",
"certification": "HVAC-Master",
"current_job": "JOB-5678",
"tools": ["multimeter", "manifold_gauge"],
"availability": "on_site"
}
}

Geofence events:

  • Technician enters job site → Clock in automatically
  • Technician exits job site → Clock out and request completion notes
  • Technician enters parts depot → Log parts pickup

3. Asset Monitoring​

Track shipping containers, equipment, or valuable assets:

{
"device_id": "container-CONT98765",
"name": "Shipping Container CONT98765",
"device_type": "tracker",
"metadata": {
"container_type": "40ft_refrigerated",
"contents": "pharmaceuticals",
"customer": "MedSupply Inc",
"shipment_id": "SHIP-2024-001",
"temperature_sensor": "enabled",
"value_usd": 250000
}
}

Geofence events:

  • Container enters port → Notify customs broker
  • Container exits approved route → Security alert
  • Container enters warehouse → Update inventory system

4. IoT Sensor Tracking​

Monitor environmental sensors or mobile IoT devices:

{
"device_id": "temp-sensor-warehouse-3",
"name": "Warehouse 3 Temperature Sensor",
"device_type": "iot",
"metadata": {
"sensor_type": "temperature_humidity",
"location": "warehouse_3",
"zone": "cold_storage",
"firmware_version": "2.1.3",
"battery_level": 85,
"last_calibration": "2025-10-15"
}
}

Geofence events:

  • Sensor moves outside warehouse → Theft alert
  • Sensor enters maintenance area → Pause monitoring
  • Sensor exits facility → Deactivate automatically

Signals​

As devices send location updates, SpatialFlow's anomaly detection layer analyzes their behavior and generates signals when it detects notable patterns:

  • Dwell signals fire when a device stays inside a geofence longer than a configured threshold
  • Policy violation signals fire when a device's location matches a policy rule (e.g., inside a restricted zone during off-hours)
  • Geofence signals provide unified enter/exit events alongside the legacy GeofenceEvent system

Signals include full explainability: each signal carries metadata, reason codes, and a structured explanation showing what thresholds were evaluated and why the signal fired.

Signal Filtering

SpatialFlow's GPS jitter filters (minimum 25m distance, 30s time gap, 100m accuracy gate) apply before signal evaluation, so signals are not triggered by noisy GPS data.

See Signals to learn about signal types and lifecycle.

Integration Examples​

Python SDK​

import requests

API_KEY = "your_api_key"
BASE_URL = "https://api.spatialflow.io/api/v1"

# Register device
def register_device(device_id, name, device_type="mobile"):
response = requests.post(
f"{BASE_URL}/devices",
headers={"X-API-KEY": API_KEY},
json={
"device_id": device_id,
"name": name,
"device_type": device_type
}
)
return response.json()

# Send location update (use device UUID from register response)
def send_location(device_uuid, lat, lon, accuracy=None):
response = requests.post(
f"{BASE_URL}/devices/{device_uuid}/location",
headers={"X-API-KEY": API_KEY},
json={
"latitude": lat,
"longitude": lon,
"accuracy": accuracy
}
)
return response.json()

# Usage
device = register_device("truck-001", "Delivery Truck 1", "vehicle")
result = send_location(device["id"], 37.7749, -122.4194, accuracy=10.5)
print(f"Events triggered: {result['events_triggered']}")

JavaScript/Node.js​

const axios = require('axios');

const API_KEY = 'your_api_key';
const BASE_URL = 'https://api.spatialflow.io/api/v1';

// Register device
async function registerDevice(deviceId, name, deviceType = 'mobile') {
const response = await axios.post(
`${BASE_URL}/devices`,
{
device_id: deviceId,
name: name,
device_type: deviceType
},
{
headers: { 'X-API-KEY': API_KEY }
}
);
return response.data;
}

// Send location update (use device UUID from register response)
async function sendLocation(deviceUuid, lat, lon, accuracy = null) {
const response = await axios.post(
`${BASE_URL}/devices/${deviceUuid}/location`,
{
latitude: lat,
longitude: lon,
accuracy: accuracy
},
{
headers: { 'X-API-KEY': API_KEY }
}
);
return response.data;
}

// Usage
(async () => {
const device = await registerDevice('truck-001', 'Delivery Truck 1', 'vehicle');
const result = await sendLocation(device.id, 37.7749, -122.4194, 10.5);
console.log(`Events triggered: ${result.events_triggered}`);
})();

Mobile App (React Native)​

import * as Location from 'expo-location';
import axios from 'axios';

const API_KEY = 'your_api_key';
const BASE_URL = 'https://api.spatialflow.io/api/v1';
const DEVICE_UUID = 'device-uuid-from-api';

// Request location permissions
async function requestLocationPermission() {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
console.error('Location permission denied');
return false;
}
return true;
}

// Start location tracking
async function startTracking() {
const hasPermission = await requestLocationPermission();
if (!hasPermission) return;

// Watch position with options
await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.High,
timeInterval: 30000, // 30 seconds
distanceInterval: 50 // 50 meters
},
async (location) => {
const { latitude, longitude, accuracy } = location.coords;

// Send to SpatialFlow
try {
const response = await axios.post(
`${BASE_URL}/devices/${DEVICE_UUID}/location`,
{
latitude,
longitude,
accuracy,
timestamp: new Date(location.timestamp).toISOString()
},
{
headers: { 'X-API-KEY': API_KEY }
}
);

console.log(`Location sent. Events: ${response.data.events_triggered}`);
} catch (error) {
console.error('Failed to send location:', error);
}
}
);
}

// Start tracking
startTracking();

Rate Limits​

Be aware of API rate limits when sending location updates:

  • Single updates: 600 requests per minute (10 per second)
  • Batch updates: 1,000 requests per minute
  • Device registration: 1,000 requests per hour

For high-frequency tracking:

  • Use batch updates for multiple devices
  • Implement exponential backoff on rate limit errors
  • Consider queuing updates client-side during network issues

Shift Management​

Shifts let you define working periods for devices, tracking when a field worker or vehicle is actively on duty. Shift data powers session analytics and time-on-site reporting.

Shift State Machine​

OFF → ACTIVE (start-shift)
ACTIVE → PAUSED (pause-shift)
PAUSED → ACTIVE (resume-shift)
ACTIVE → OFF (end-shift)
PAUSED → OFF (end-shift)

Shift Endpoints​

Start a shift:

curl -X POST https://api.spatialflow.io/api/v1/devices/{device_uuid}/start-shift \
-H "X-API-KEY: YOUR_API_KEY"

Pause a shift:

curl -X POST https://api.spatialflow.io/api/v1/devices/{device_uuid}/pause-shift \
-H "X-API-KEY: YOUR_API_KEY"

Resume a shift:

curl -X POST https://api.spatialflow.io/api/v1/devices/{device_uuid}/resume-shift \
-H "X-API-KEY: YOUR_API_KEY"

End a shift:

curl -X POST https://api.spatialflow.io/api/v1/devices/{device_uuid}/end-shift \
-H "X-API-KEY: YOUR_API_KEY"

Locations during a pause​

A paused shift shares no location, whichever route the location comes through: the device routes (/devices/{device_uuid}/location and /devices/batch-update) and the public ingest routes (/locations and /locations/batch). While the shift is paused, a location is rejected with error_code SHIFT_PAUSED. A location timestamped inside a pause is rejected with PAUSED_INTERVAL, also when it arrives after that shift ended or a newer shift started. SpatialFlow keeps only the latest resume of each shift, so everything in the shift before that resume counts as paused, and a shift that ends while paused counts as paused until it ends. A single-location request gets status 400; the batch routes reject such locations one by one with the same error_code and keep the rest. A rejected location is not stored, not counted toward your event quota and triggers no geofence events or workflows.

The public ingest routes queue locations for processing. If the shift pauses after a location was queued but before it was processed, the location is dropped the same way, and retrying it returns the same error. It still counts once toward your event quota.

Sessions​

Each shift creates a session record. List sessions for a device:

curl https://api.spatialflow.io/api/v1/devices/{device_uuid}/sessions \
-H "X-API-KEY: YOUR_API_KEY"

The list holds completed shifts. Add include_open=true to also get the shift in progress as open_session. Each session reports the photo_count and note_count of the evidence attached to it, and a device on shift carries the same counts in current_session. Reading sessions takes devices:read, and an API key also needs storage:read to get photo_count; without it, photo_count is null.

Use cases:

  • Field worker clock-in/clock-out tracking
  • Job site time-on-site reporting
  • Vehicle duty hours and break compliance
  • Shift-based analytics and billing

Next Steps​

Now that you understand devices, explore related features:

Route Params

Device routes use the server UUID (id). If you previously used device_id in route paths, update your client to pass the UUID.

API Endpoints​

Key device endpoints:

  • POST /api/v1/devices/ - Register a new device
  • GET /api/v1/devices - List all devices
  • GET /api/v1/devices/{uuid} - Get device details
  • POST /api/v1/devices/{uuid}/location - Update device location
  • POST /api/v1/devices/{uuid}/start-shift - Start a tracking shift
  • POST /api/v1/devices/{uuid}/pause-shift - Pause a tracking shift
  • POST /api/v1/devices/{uuid}/resume-shift - Resume a paused shift
  • POST /api/v1/devices/{uuid}/end-shift - End a tracking shift
  • GET /api/v1/devices/{uuid}/sessions - List device sessions
  • POST /api/v1/devices/batch-update - Batch location updates
  • DELETE /api/v1/devices/{uuid} - Delete a device
  • POST /api/v1/devices/{uuid}/activate - Activate a device
  • POST /api/v1/devices/{uuid}/deactivate - Deactivate a device
  • GET /api/v1/devices/{uuid}/events - Get device events
  • GET /api/v1/devices/events/recent - Get recent events across all devices
  • POST /api/v1/devices/locations/import - Import locations from CSV
  • GET /api/v1/devices/locations/import/{job_id} - Get import job status

See the API Reference for complete details and examples.