πŸš‚ Avea Backend API

Comprehensive API documentation for frontend and mobile developers

Base URL: http://localhost:3000
πŸ” Authentication

Authentication Method: WorkOS session-based authentication with secure cookies

CORS: Configured by the CORS_ALLOWED_ORIGINS allowlist with credentials support

Frontend Auth Flow:
  • Send credentials: 'include' on authenticated requests.
  • Fetch a CSRF token after login: GET /auth/csrf (store and reuse it).
  • Send X-CSRF-Token on all state-changing requests (POST/PUT/PATCH/DELETE).
  • Auto-refresh happens on authenticated requests; you can also call POST /auth/refresh.
  • On 401, redirect to login; on 403, show "forbidden".
  • Login: POST /auth/login β†’ if 409, call POST /auth/select-organization.
  • Logout: POST /auth/logout clears the session cookie.
POST /auth/login
Authenticate user with email and password
Request Body:
{
  "email": "user@example.com",
  "password": "password123"
}
Response (200):
(empty body)
Response (409):
{
  "success": false,
  "code": "organization_selection_required",
  "pendingAuthenticationToken": "pat_123",
  "organizations": [
    { "id": "org_123", "name": "Acme Co" }
  ],
  "error": "The user must choose an organization to finish their authentication."
}

Sets Cookie: wos-session (httpOnly, secure), wos-csrf

POST /auth/select-organization
Complete login after user selects an organization
Request Body:
{
  "pendingAuthenticationToken": "pat_123",
  "organizationId": "org_123"
}
Response (200):
(empty body)

Sets Cookie: wos-session (httpOnly, secure), wos-csrf

POST /auth/refresh
Refresh the current WorkOS session
Headers:
X-CSRF-Token: <token>
Response (200):
{
  "success": true,
  "csrfToken": "token"
}

Sets Cookie: wos-session (httpOnly, secure), wos-csrf

GET /auth/csrf
Issue a CSRF token for the current session
Response (200):
{
  "csrfToken": "token"
}

Sets Cookie: wos-csrf

POST /auth/logout
Log out current user and clear session
Headers:
X-CSRF-Token: <token>
Response:
{
  "success": false,
  "error": null
}

Clears Cookie: wos-session, wos-csrf

GET /auth/callback
OAuth callback for WorkOS authentication flow
Query Parameters:

code (required) - Authorization code from WorkOS

Response: Redirect to / with session cookie

πŸ‘€ User Management
πŸ”’ Authentication Required: All endpoints require valid session cookie
GET /user/profile
Get current user's profile information
Response:
{
  "success": true,
  "data": {
    "id": "user_123",
    "email": "user@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "organizationId": "org_456"
  }
}
πŸ“± Device Management
πŸ”’ Authentication Required: All endpoints require valid session cookie
POST /device/avea
Create a new Avea device in the database
Request Body:
{
  "device": {
    "device_eui": "1234567890ABCDEF",
    "railroad_id": 1,
    "device_make_id": 2,
    "firmware_version": "1.0.0",
    "end_device_id": "device-001"
  }
}
Required Parameters:
  • device_eui (string) - 16 character hexadecimal device identifier
  • railroad_id (integer) - ID of the associated railroad
  • device_make_id (integer) - ID of the device make/model
  • firmware_version (string) - Firmware version of the device
  • end_device_id (string) - Unique end device identifier
Success Response:
{
  "success": true
}
POST /device/ttn
Create device in The Things Network and register to all servers
Request Body:
{
  "device": {
    "device_eui": "1234567890ABCDEF",
    "join_eui": "FEDCBA0987654321",
    "app_key": "00112233445566778899AABBCCDDEEFF",
    "end_device_id": "sm-01-1234567890abcdef",
    "firmware_version": "1.0.0",
    "frequency_plan_id": "US_902_928_FSB_2",
    "lorawan_version": "1.0.3",
    "regional_parameters_version": "1.0.3-a"
  }
}
Required Parameters:
  • device_eui (string) - 16 character hexadecimal device identifier
  • join_eui (string) - 16 character hexadecimal join identifier
  • app_key (string) - 32 character hexadecimal application key
  • end_device_id (string) - Device identifier in format: {device_make}-{device_eui} (e.g., "sm-01-1234567890abcdef")
  • firmware_version (string) - Firmware version in format #.#.# (e.g., "1.0.0")
Optional Parameters:
  • frequency_plan_id (string) - LoRaWAN frequency plan
  • lorawan_version (string) - LoRaWAN specification version
  • regional_parameters_version (string) - Regional parameters version
πŸ“Š Device Status
πŸ”’ Authentication Required: All endpoints require valid session cookie
GET /device-status
Get status of all devices with latest readings and active alerts
Query Parameters:

company_id (optional) - Filter by company ID

railroad_id (optional) - Filter by railroad ID

Response:
{
  "success": true,
  "data": [
    {
      "device_eui": "1234567890ABCDEF",
      "device_id": 1,
      "railroad_name": "BNSF Railway",
      "railroad_id": 1,
      "company_name": "Acme Corp",
      "company_id": 1,
      "has_active_alerts": true,
      "active_alerts": [
        {
          "incident_id": 123,
          "sensor_type_id": 1,
          "sensor_type_name": "Temperature",
          "sensor_type_unit": "Β°F",
          "sensor_channel": 1,
          "alert_status": "HIGH",
          "reading_value": 85.5,
          "start_time": "2024-01-15T10:30:00Z"
        }
      ],
      "latest_reading_time": "2024-01-15T12:00:00Z"
    }
  ],
  "count": 1
}
πŸ—ΊοΈ Map
πŸ”’ Authentication Required: All endpoints require valid session cookie
GET /map/buckets
Get playback metadata for the map time scrubber
Response:
{
  "success": true,
  "data": {
    "earliest_time_utc": "2025-01-01T00:00:00Z",
    "latest_time_utc": "2026-01-29T12:34:56Z",
    "allowed_bucket_sizes_seconds": [60, 300, 900, 3600],
    "step_rules": [
      { "range": "last_24h", "bucket_size_seconds": 60 },
      { "range": "last_7d", "bucket_size_seconds": 300 },
      { "range": "last_30d", "bucket_size_seconds": 900 },
      { "range": "older", "bucket_size_seconds": 3600 }
    ]
  }
}
GET /map/points
Get playback data for a viewport at a bucket-aligned time
Query Parameters:

bbox (required) - west,south,east,north

zoom (required) - map zoom level (0-20)

as_of (required) - ISO 8601 time with timezone (Z or Β±HH:MM); server rounds down to bucket

bucket_size_seconds (required) - 60, 300, 900, or 3600

offline_threshold_seconds (optional) - staleness threshold for Offline

Note: global tiles are aggregated at zoom 5 (tile_id z may differ from requested zoom)

Response:
{
  "success": true,
  "count": 2,
  "data": {
    "tier": "high",
    "as_of_utc": "2026-01-28T18:00:00Z",
    "items": [
      {
        "device_eui": "1234567890ABCDEF",
        "lat": 39.74,
        "lon": -104.99,
        "timestamp_utc": "2026-01-28T18:12:00Z",
        "status": "Normal",
        "value": null
      }
    ]
  }
}
GET /map/points/live
Get live map data for a viewport and zoom
Query Parameters:

bbox (required) - west,south,east,north

zoom (required) - map zoom level (0-20)

since (optional) - ISO 8601 timestamp with timezone (Z or Β±HH:MM) from prior response

offline_threshold_seconds (optional) - staleness threshold for Offline

Note: global tiles are aggregated at zoom 5 (tile_id z may differ from requested zoom)

Response:
{
  "success": true,
  "data": {
    "tier": "global",
    "items": [
      {
        "tile_id": "2/1/1",
        "centroid_lat": 40.0,
        "centroid_lon": -100.0,
        "bbox_south": 39.0,
        "bbox_west": -101.0,
        "bbox_north": 41.0,
        "bbox_east": -99.0,
        "count_total": 120,
        "count_alarm": 3,
        "count_warning": 10,
        "count_offline": 5,
        "count_normal": 102
      }
    ],
    "removed_ids": [],
    "server_time_utc": "2026-01-28T18:12:10Z",
    "recommended_poll_seconds": 5,
    "since_ignored": true
  }
}
GET /map/points/:device_eui
Get detail for a selected device
Path Parameters:

device_eui (required) - 16-char hex string

Response:
{
  "success": true,
  "data": {
    "device_eui": "1234567890ABCDEF",
    "device_id": 1,
    "railroad_id": 1,
    "railroad_name": "BNSF Railway",
    "company_id": 1,
    "company_name": "Acme Corp",
    "has_active_alerts": true,
    "active_alerts": [
      {
        "incident_id": 123,
        "sensor_type_id": 1,
        "sensor_type_name": "Temperature",
        "sensor_type_unit": "Β°F",
        "sensor_channel": 1,
        "alert_status": "HIGH",
        "reading_value": 85.5,
        "start_time": "2026-01-28T18:10:00Z"
      }
    ],
    "latest_reading_time": "2026-01-28T18:12:00Z"
  }
}
⚠️ Sensor Reading Alerts
πŸ”’ Authentication Required: All endpoints require valid session cookie
GET /sensor-reading-alerts
Get sensor reading alerts with filtering options
Query Parameters:

company_id (optional) - Filter by company ID

railroad_id (optional) - Filter by railroad ID

device_eui (optional) - Filter by specific device

start_date (optional) - Filter from date (ISO 8601)

end_date (optional) - Filter until date (ISO 8601)

onlyShowActiveAlerts (optional) - "true" for active only

Response:
{
  "success": true,
  "data": [
    {
      "device_eui": "1234567890ABCDEF",
      "alerts": [
        {
          "alert_id": 123,
          "sensor_type_id": 1,
          "sensor_type_name": "Temperature",
          "sensor_type_unit": "Β°F",
          "sensor_channel": 1,
          "alert_status": "HIGH",
          "time": null,
          "start_time": "2024-01-15T10:30:00Z",
          "end_time": null
        }
      ]
    }
  ],
  "count": 1
}
GET /sensor-reading-alerts/device/:device_eui
Get active and historical alerts for specific device
Path Parameters:

device_eui (required) - Device EUI (16 char hex)

Query Parameters:

since (optional) - Include historical alerts since date

Response:
{
  "success": true,
  "data": {
    "device_eui": "1234567890ABCDEF",
    "active_alerts": [
      {
        "alert_id": 123,
        "sensor_type_id": 1,
        "sensor_type_name": "Temperature",
        "sensor_type_unit": "Β°F",
        "sensor_channel": 1,
        "alert_status": "HIGH",
        "start_time": "2024-01-15T10:30:00Z",
        "end_time": null
      }
    ],
    "historical_alerts": [
      {
        "alert_id": 122,
        "sensor_type_id": 1,
        "sensor_type_name": "Temperature",
        "sensor_type_unit": "Β°F",
        "sensor_channel": 1,
        "alert_status": "HIGH",
        "start_time": "2024-01-14T10:30:00Z",
        "end_time": "2024-01-14T11:00:00Z"
      }
    ]
  },
  "active_count": 1,
  "historical_count": 1,
  "total_count": 2
}
πŸ“ˆ Sensor Readings
πŸ”’ Authentication Required: All endpoints require valid session cookie
GET /most-recent-sensor-reading/:device_eui
Get most recent sensor readings for a device
Path Parameters:

device_eui (required) - Device EUI (16 char hex)

Response:
{
  "success": true,
  "data": [
    {
      "sensor_reading_log_id": 456,
      "device_eui": "1234567890ABCDEF",
      "sensor_type": "Temperature",
      "sensor_channel": 1,
      "value": 23.5,
      "time": "2024-01-15T12:00:00Z"
    }
  ]
}
GET /sensor-readings-aggregate
Get aggregated sensor readings with time bucketing
Query Parameters:

start_time (required) - Start time (ISO 8601)

end_time (required) - End time (ISO 8601)

device_eui (optional) - Filter by device

interval (optional) - Time bucket: 1 minute, 5 minutes, 15 minutes, 30 minutes, 1 hour, 6 hours, 12 hours, 1 day, 1 week (default: 1 hour)

aggregation (optional) - Function: avg, min, max, sum, count (default: avg)

Response:
{
  "success": true,
  "data": [
    {
      "device_eui": "1234567890ABCDEF",
      "sensor_type_id": 1,
      "sensor_channel": 1,
      "bucket": "2024-01-15T12:00:00Z",
      "value": 23.5
    }
  ]
}
πŸ“Ά Uptime
πŸ”’ Authentication Required: All endpoints require valid session cookie
GET /device-uptime-aggregate
Get historical uptime/reporting coverage by bucket
Definition:

eligible means the device has reported at least once before the bucket time and is not retired/decommissioned at that bucket time.

online means an eligible device reported within the offline threshold before the bucket time.

Query Parameters:

start_time (required) - Start time (ISO 8601)

end_time (required) - End time (ISO 8601)

interval (optional) - Time bucket: 1 minute, 5 minutes, 15 minutes, 30 minutes, 1 hour, 6 hours, 12 hours, 1 day, 1 week (default: 1 hour)

offline_threshold_seconds (optional) - Seconds without readings before a device is offline (default: 21600)

railroad_id (optional) - Filter by railroad

Response:
{
  "success": true,
  "data": [
    {
      "bucket": "2026-05-01T12:00:00.000Z",
      "eligible_count": 100,
      "online_count": 97,
      "offline_count": 3,
      "uptime_percent": 97
    }
  ]
}
πŸ“š Reference Data
πŸ”’ Authentication Required: All endpoints require valid session cookie

Companies

  • GET /company - List all companies (AVEA Group org only; role org-admin or org-ops) β€” for device provisioning
  • GET /company/current - Get current tenant company name
  • GET /company/id/:company_id - Get company by ID
  • GET /company/name/:name - Get company by name
Sample Response (current):
{
  "success": true,
  "data": {
    "name": "Acme Corp"
  }
}
Sample Response (by id/name):
{
  "success": true,
  "data": {
    "company_id": 1,
    "name": "Acme Corp",
    "created_at": "2024-01-01T00:00:00Z"
  }
}

Railroads

  • GET /railroad - Get all railroads (optional query: company_id)
  • GET /railroad/id/:railroad_id - Get railroad by ID
  • GET /railroad/name/:name - Get railroad by name
Sample Response:
{
  "success": true,
  "data": [
    {
      "railroad_id": 1,
      "name": "BNSF Railway",
      "company_id": 1,
      "created_at": "2024-01-01T00:00:00Z"
    }
  ]
}

Device Makes

  • GET /device-make - Get all device makes
  • GET /device-make/id/:device_make_id - Get device make by ID
  • GET /device-make/name/:name - Search device make by name (case-insensitive partial match)
Sample Response:
{
  "success": true,
  "data": [
    {
      "device_make_id": 1,
      "name": "Avea Sensor Pro",
      "model": "ASP-001",
      "manufacturer": "Avea Technologies"
    }
  ]
}

Sensor Types

  • GET /sensor-type - Get all sensor types
  • GET /sensor-type/id/:sensor_type_id - Get sensor type by ID
  • GET /sensor-type/name/:name - Search sensor types by name (partial match, returns array)
Sample Response:
{
  "success": true,
  "data": [
    {
      "sensor_type_id": 1,
      "name": "Temperature",
      "unit": "Β°C",
      "description": "Temperature sensor"
    }
  ]
}
πŸ”‘ Key Management
πŸ”’ Special Authorization Required: KeyMaster role (org-keymaster)
GET /key/lorawan/credentials
Get LoRaWAN credentials (KeyMaster role required)
Response:
{
  "success": true,
  "data": {
    "join_eui": "0000000000000000",
    "app_key": "00000000000000000000000000000000"
  }
}
Error Codes:
  • 401 - Unauthorized (not authenticated)
  • 403 - Forbidden (KeyMaster role required)
  • 500 - Server configuration error (missing TTN_JOIN_EUI or TTN_APP_KEY)
πŸ’» Example Usage

JavaScript/Frontend:

// Login
const loginResponse = await fetch('http://localhost:3000/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include', // Important for cookies
  body: JSON.stringify({
    email: 'user@example.com',
    password: 'password123'
  })
});

// Get CSRF token
const csrfResponse = await fetch('http://localhost:3000/auth/csrf', {
  credentials: 'include'
});
const { csrfToken } = await csrfResponse.json();

// Get user profile
const profileResponse = await fetch('http://localhost:3000/user/profile', {
  credentials: 'include' // Include session cookie
});

// Create device (state-changing request)
const createDeviceResponse = await fetch('http://localhost:3000/device/avea', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken
  },
  credentials: 'include',
  body: JSON.stringify({
    device: {
      device_eui: '1234567890ABCDEF',
      railroad_id: 1,
      device_make_id: 2,
      firmware_version: '1.0.0',
      end_device_id: 'device-001'
    }
  })
});

// Get device status
const devicesResponse = await fetch('http://localhost:3000/device-status?company_id=1', {
  credentials: 'include'
});

// Get aggregated readings
const readingsResponse = await fetch(
  'http://localhost:3000/sensor-readings-aggregate?' +
  'start_time=2024-01-01T00:00:00Z&' +
  'end_time=2024-01-02T00:00:00Z&' +
  'interval=1 hour&' +
  'aggregation=avg',
  { credentials: 'include' }
);

React Native Example:

// Note: React Native handles cookies automatically
const loginResponse = await fetch('http://localhost:3000/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    email: 'user@example.com',
    password: 'password123'
  })
});

// Get CSRF token
const csrfResponse = await fetch('http://localhost:3000/auth/csrf');
const { csrfToken } = await csrfResponse.json();

// Get alerts for specific device
const alertsResponse = await fetch(
  'http://localhost:3000/sensor-reading-alerts/device/1234567890ABCDEF?since=2024-01-01T00:00:00Z'
);

Common Data Types:

  • Device EUI: 16 character hex string (e.g., "1234567890ABCDEF")
  • Date/Time: ISO 8601 format (e.g., "2024-01-15T12:00:00Z")
  • Alert Status: "HIGH", "LOW", "CRITICAL"
  • Sensor Channel: Integer (1-16)
Common HTTP Status Codes:
  • 200: Success
  • 400: Bad Request (validation error)
  • 401: Unauthorized (login required)
  • 403: Forbidden (insufficient permissions)
  • 500: Internal Server Error