API Reference

Agrogat REST and ingest endpoints — sensor data, sync, geotechnical modules, and dashboard API.

X-API-Key (ingest) X-Sync-Key (sync) Session (dashboard)
Base URL https://agrogat.com

Base URL

Replace with your actual host (local or production), for example:

CODE
http://localhost

or

CODE
https://agrogat.com

See sensors-dragino-lse01.md for the LSE01 field contract, LoRa→HTTP forwarding, and the difference between simulator and live mode.


POST/ingest.php

Receives and stores sensor readings. Does not require dashboard login, but a valid X-API-Key is mandatory.

Rows are linked to a gateway via GATEWAY_CODE in .env (resolved against gateways.id). If the database is missing the gateway_id column, ingest.php falls back to inserting without that column.

Headers

Header Value Required
Content-Type application/json Yes
X-API-Key Your API key (INGEST_API_KEY) Yes

Request body

JSON
{
  "sensor_id": "string (required)",
  "payload": { }
}
Field Type Required Description
sensor_id string Yes Unique sensor identifier
payload object/any No Measurement data — any JSON object. If payload is omitted, the full request object is stored (subject to sensor_id handling in code)

Maximum request body size: 1 MB

Success response — 200 OK

JSON
{
  "status": "success",
  "id": "42"
}

Error responses

HTTP code Cause
400 Bad Request Missing sensor_id or invalid JSON
401 Unauthorized Missing or invalid X-API-Key
415 Unsupported Media Type Content-Type is not application/json
500 Internal Server Error Internal server error (see server log)

Example 401 response:

JSON
{
  "status": "error",
  "message": "Unauthorized"
}
  • The dashboard API and sync_receive are protected by separate keys (SYNC_API_KEY must be set on the central server to accept push).

POST/api/sync_receive.php

Used by gateway instances (via scripts/sync_push_to_central.php) to send batches of readings to a central server. Does not require a dashboard session.

Header Value
Content-Type application/json
X-Sync-Key Same string as SYNC_API_KEY in the central server's .env

Central server: SYNC_API_KEY must be set (non-empty). Otherwise the endpoint responds with 503 and sync.not_configured.

Body (summary):

JSON
{
  "cluster": "TRAT",
  "source_gateway_code": "TRAT-GATEWAY-001",
  "readings": [
    {
      "id": 1,
      "sensor_id": "sensor-a",
      "data": { },
      "received_at": "2026-05-03T12:00:00+00:00",
      "gateway_code": "TRAT-GATEWAY-001"
    }
  ]
}

source_gateway_code is always the source that owns the numeric id values in the batch. Each row must have a matching active gateways record on the central server for gateway_code (used for sensor_data.gateway_id).

Success: {"success":true,"inserted":N,"skipped":M,"errors":E,...}


POST/api/sync_entities_receive.php

Used by scripts/sync_push_entities_to_central.php when SYNC_ENTITIES_ENABLE is enabled on the gateway. Same authentication as sync_receive (X-Sync-Key = SYNC_API_KEY). The receiver upserts clients (by email) and sensors (unique sensor_id). The sender uses the same base URLs as for readings; only the filename changes from sync_receive.php to sync_entities_receive.php.

Header Value
Content-Type application/json
X-Sync-Key Same string as SYNC_API_KEY on the receiver

Body (summary):

JSON
{
  "source_gateway_code": "TRAT-GATEWAY-001",
  "clients": [
    {
      "company_name": "Example Ltd",
      "email": "contact@example.com",
      "country_code": "NO",
      "currency_code": "NOK",
      "active": true,
      "default_gateway_code": "TRAT-GATEWAY-001"
    }
  ],
  "sensors": [
    {
      "sensor_id": "sensor-a",
      "client_email": "contact@example.com",
      "country_code": "NO",
      "active": true,
      "gateway_code": "TRAT-GATEWAY-001"
    }
  ]
}

country_code must be exactly two characters (ISO 3166-1 alpha-2). For sensors, client_email must exist on the receiver after the client list is applied (clients are processed first).

Success: {"success":true,"clients_upserted":N,"sensors_upserted":M,"errors":E}. If errors > 0, the sender does not advance the watermark (watermark_ts, channel entities_push).


Dashboard API (/api/*.php)

These endpoints are used by dashboard.php (JavaScript) and require an authenticated session (cookie after successful login via login.php / api/auth.php). Unauthenticated calls typically receive 401 with a JSON error.

Overview (all under /api/):

File Method / purpose
auth.php POST — login / logout (JSON action, email, password)
me.php GET — current user and role
locale.php POST — update language preference (en, no, th)
clients.php CRUD for clients (role restrictions in code)
sensors.php CRUD for sensors
gateways.php CRUD for gateways incl. optional latitude / longitude
users.php User administration
data.php GET — sensor data with joins (sensor, client, gateway code)
gateway_map.php GET — gateway (from GATEWAY_CODE in .env) and sensors with coordinates linked to that gateway
simulator.php Simulator / LIVE mode and data generation
reset.php Controlled table reset (after UI confirmation)
sync_receive.php POST — receive sensor_data batch from gateway (X-Sync-Key) — see dedicated section
sync_entities_receive.php POST — receive clients + sensors batch (X-Sync-Key) — see dedicated section
geo_status.php GET — ground stability last 24h (pore pressure, temperature, geo_status)
permafrost_depth.php GET — thaw depth from thermistor JSONB (`?sensor_id=&at=latest\ history`)
geo_trends.php GET — monthly averages Jan–May + linear forecast toward 0°C
runway_status.php GET — runway VWC, estimated penetration, and status per section

Detailed JSON contracts follow the implementation in each PHP file; the common pattern is application/json and error objects with success: false where applicable.


Geotechnical module (JSONB in sensor_data.data)

All geotechnical measurements are stored in the existing sensor_data table via ingest.php. Set type in payload and use the fields below. Thresholds are configured in lib/operations.phpgeo (and evaluated in lib/geo_payload.php).

Ingest — Dragino RS485 (LoRaWAN, hex → pore pressure)

When the network server sends raw hex or base64 from a Dragino RS485 node, ingest.php can decode water column height (metres) and store it under pore_pressure in JSONB.

JSON
{
  "sensor_id": "JM-PIEZO-01",
  "payload_hex": "0B45014041A00000"
}

Alternatively: hex, data (base64), or payload.payload_hex. Without the Dragino header (Modbus data only): set RS485_HEADER_SKIP_BYTES=0 in .env.

.env Default Description
RS485_HEADER_SKIP_BYTES 3 Skip battery (2) + payload version (1) on FPort 2
RS485_DATA_OFFSET 1 Extra offset before float (often 1 Modbus byte after header)
RS485_DECODE_FORMAT float32_be float32_be, float32_le, uint16_mm, int16_mm, uint32_mm

Stored payload (excerpt):

JSON
{
  "type": "piezometer",
  "pore_pressure": 20.0,
  "pore_pressure_unit": "m",
  "pore_pressure_kpa": 196.133,
  "source": "dragino_rs485_lorawan",
  "raw_hex": "0b45014041a00000"
}

Alarms use pore_pressure_kpa (calculated from metres × 9.80665).

Ingest — example payloads

Permafrost (thermistor string):

JSON
{
  "sensor_id": "JM-PERM-01",
  "payload": {
    "type": "permafrost_thermistor",
    "depths_m": { "1": -1.2, "3": -0.6, "5": 0.2, "10": -3.1 }
  }
}

Piezometer (pore pressure):

JSON
{
  "sensor_id": "JM-PIEZO-01",
  "payload": {
    "type": "piezometer",
    "pore_pressure_kpa": 95.5
  }
}

When pore_pressure_kpapore_pressure_critical_kpa (default 120), geo_status is set to CRITICAL on the row. When temperature exceeds 0°C for more than 72 hours (all permafrost rows in the window), thermal_erosion: true and CRITICAL are set.

GET/api/geo_status.php

Optional sensor_id. Returns overall (STABLE / WARNING / CRITICAL) and per sensor: min/max temperature and pore pressure over the last 24h, latest geo_status.

GET/api/permafrost_depth.php

Required sensor_id. at=latest (default) or at=history (last 50). Returns thaw_depth_m (shallowest depth ≥ 0°C), depths_m, and fully_frozen.

GET/api/geo_trends.php

Required sensor_id. Optional depth_m, years_back (default 10). Returns monthly_averages (Jan–May per year), daily_series, and forecast (linear regression, forecast_zero_crossing_at).

Simulator (geo)

In the dashboard Simulator tab (staff only): sensor IDs containing PERM, PIEZ, or INCL receive geo payloads instead of LSE01.

Action Description
?action=generate All active sensors (LSE01 + geo)
?action=generate_geo Geo sensors only
?action=seed_geo_history Weekly permafrost history (~3 years) for trend charts

The dashboard Geotechnical tab displays status, thaw depth, and Chart.js graphs from the APIs above.

GET/api/runway_status.php

Runway monitoring (Jan Mayensfield et al.). ?group=jan_mayensfield (default).

Returns sections[] with location, vwc_pct, penetration_cm, status_label (Optimal/Caution/Critical), status_color, received_at.

Thresholds are configured in lib/runway.php (runway_config()) and can be overridden per client/sensor via lib/operations.phprunway or later via the database.

Defaults for Jan Mayensfield:

  • Optimal: est. penetration < 5 cm
  • Caution: 5–15 cm
  • Critical: > 15 cm

Seed demo data: php scripts/seed_runway_enja.php


Web UI

Path Description
/ Welcome page (guests); dashboard (signed-in users)
/login.php Sign in
/ Welcome page (public, SEO)
/dashboard.php Dashboard (requires session)
/docs/api.php API reference (HTML)

Security

  • Requests to /ingest.php must include a valid X-API-Key; comparison uses timing-safe hash_equals.
  • sync_receive.php requires X-Sync-Key matching SYNC_API_KEY. The endpoint is a no-op (503) when SYNC_API_KEY is not set.
  • sync_entities_receive.php follows the same pattern.
  • Dashboard API is session-protected; do not strip the Cookie header in a reverse proxy for /api/.
  • For 500 responses, user-facing messages are intentionally generic; details are logged on the server.