Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ricardomb-tech/surqo/llms.txt

Use this file to discover all available pages before exploring further.

The Sensors API bridges your ESP32 hardware and the Surqo platform. Devices push readings over HTTP, and the server automatically computes the Vapour Pressure Deficit (VPD) from air temperature and humidity using the Magnus equation before persisting the record. Every saved reading is immediately broadcast to all WebSocket clients subscribed to that farm, enabling live dashboard updates without polling. Historical data is queryable as a time series for any metric over a rolling window of up to one year, and a 24-hour stats endpoint returns per-metric min / max / avg / trend summaries.

Ingest a sensor reading

POST /api/v1/sensors/reading — Auth required — Rate limit: 120 requests / minute Accepts a reading from an ESP32 (or any HTTP client) and saves it to the database. If both air_temp_c and air_humidity_pct are provided, vpd_kpa is calculated automatically. When farm_id is supplied, the reading is broadcast to the WebSocket live feed for that farm.

Request body

device_id
string
required
Unique identifier of the physical device. Maximum 100 characters (e.g. "ESP32-CAMPO-001").
farm_id
uuid
UUID of the farm this reading belongs to. The farm must be owned by the authenticated user. When omitted the reading is stored without a farm association and WebSocket broadcast is skipped.
soil_moisture_pct
float
Volumetric soil moisture as a percentage. Valid range: 0100.
soil_temp_c
float
Soil temperature in degrees Celsius. Valid range: -1080.
air_temp_c
float
Air temperature in degrees Celsius. Valid range: -1060. Used together with air_humidity_pct to compute vpd_kpa.
air_humidity_pct
float
Relative air humidity as a percentage. Valid range: 0100. Used together with air_temp_c to compute vpd_kpa.
uv_index
float
UV radiation index. Valid range: 020.
battery_mv
integer
Battery voltage in millivolts (e.g. 3820).
rssi_dbm
integer
Wi-Fi signal strength in dBm (e.g. -62).
firmware_version
string
Firmware version string reported by the device.
source
string
Ingestion channel. Defaults to "http". The system also accepts "mqtt" internally.

Response 201

Returns a SensorReadingResponse object.
id
uuid
Unique reading identifier.
device_id
string
Device identifier as submitted.
farm_id
uuid | null
Associated farm UUID, or null if no farm was specified.
vpd_kpa
float | null
Computed Vapour Pressure Deficit in kilopascals. null if air_temp_c or air_humidity_pct were not provided.
soil_moisture_pct
float | null
Stored soil moisture value.
soil_temp_c
float | null
Stored soil temperature.
air_temp_c
float | null
Stored air temperature.
air_humidity_pct
float | null
Stored air humidity.
uv_index
float | null
Stored UV index.
battery_mv
integer | null
Stored battery voltage.
rssi_dbm
integer | null
Stored signal strength.
firmware_version
string | null
Stored firmware version.
source
string
Ingestion channel as persisted (always "http" for this endpoint).
created_at
datetime
ISO 8601 timestamp of when the reading was recorded.

Example

curl -X POST https://surqo-api.fly.dev/api/v1/sensors/reading \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "ESP32-CAMPO-001",
    "farm_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "soil_moisture_pct": 44.5,
    "soil_temp_c": 27.8,
    "air_temp_c": 31.2,
    "air_humidity_pct": 70.1,
    "uv_index": 7.4,
    "battery_mv": 3820,
    "rssi_dbm": -62
  }'
Response 201
{
  "id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
  "device_id": "ESP32-CAMPO-001",
  "farm_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "soil_moisture_pct": 44.5,
  "soil_temp_c": 27.8,
  "air_temp_c": 31.2,
  "air_humidity_pct": 70.1,
  "uv_index": 7.4,
  "battery_mv": 3820,
  "rssi_dbm": -62,
  "vpd_kpa": 1.38,
  "firmware_version": null,
  "source": "http",
  "created_at": "2026-06-28T14:35:00Z"
}

Get time series

GET /api/v1/sensors/timeseries/{farm_id} — Auth required Returns a list of {timestamp, value} data points for a single metric over a rolling time window. Ideal for charting historical trends.
farm_id
uuid
required
UUID of the farm to query.
hours
integer
Number of hours of history to return. Range: 18760 (up to one year). Defaults to 24.
metric
string
Sensor metric to return. Defaults to "soil_moisture_pct".Accepted values:
  • soil_moisture_pct
  • soil_temp_c
  • air_temp_c
  • air_humidity_pct
  • uv_index
  • vpd_kpa

Response 200

Returns an array of TimeseriesPoint objects in ascending chronological order.
timestamp
datetime
ISO 8601 timestamp of the reading.
value
float | null
Metric value at that timestamp. null if the sensor did not report this field in that reading.
curl "https://surqo-api.fly.dev/api/v1/sensors/timeseries/a1b2c3d4-e5f6-7890-abcd-ef1234567890?hours=48&metric=vpd_kpa" \
  -H "Authorization: Bearer <token>"
Response 200
[
  { "timestamp": "2026-06-26T14:00:00Z", "value": 1.21 },
  { "timestamp": "2026-06-26T14:15:00Z", "value": 1.25 },
  { "timestamp": "2026-06-26T14:30:00Z", "value": 1.38 }
]

Get latest reading for a device

GET /api/v1/sensors/latest/{device_id} — Auth required Returns the most recent reading recorded for a specific device. If the reading is linked to a farm, the farm must belong to the authenticated user.
device_id
string
required
The device identifier string (e.g. "ESP32-CAMPO-001").
Returns 404 if no readings exist for the device. Returns 403 if the associated farm belongs to a different user.
curl https://surqo-api.fly.dev/api/v1/sensors/latest/ESP32-CAMPO-001 \
  -H "Authorization: Bearer <token>"

Get 24h stats

GET /api/v1/sensors/stats/{farm_id} — Auth required Returns aggregate statistics computed from the last 24 hours of sensor data for a farm. Each metric includes its minimum, maximum, average, and a directional trend comparing the first and last reading in the window.
farm_id
uuid
required
UUID of the farm to query.

Response 200

farm_id
string
Farm UUID as a string.
hours
integer
Stats window in hours (always 24).
stats
object
Metrics always included when data is available: soil_moisture_pct, air_temp_c, air_humidity_pct, vpd_kpa.
curl https://surqo-api.fly.dev/api/v1/sensors/stats/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer <token>"
Response 200
{
  "farm_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "hours": 24,
  "stats": {
    "soil_moisture_pct": { "min": 38.2, "max": 51.7, "avg": 44.1, "trend": "down" },
    "air_temp_c":        { "min": 22.1, "max": 34.5, "avg": 29.5, "trend": "stable" },
    "air_humidity_pct":  { "min": 58.0, "max": 79.3, "avg": 68.3, "trend": "up" },
    "vpd_kpa":           { "min": 0.91, "max": 1.87, "avg": 1.42, "trend": "stable" }
  }
}
If there are no sensor readings in the last 24 hours the response body is {"error": "Sin datos en las últimas 24h"} with status 200.

Live WebSocket feed

WS /api/v1/sensors/ws/live/{farm_id} Subscribe to a real-time stream of sensor readings for a farm. The connection is authenticated via a JWT passed as a query parameter — the WebSocket protocol does not support Authorization headers during the handshake.
farm_id
string
required
UUID string of the farm to subscribe to.
token
string
required
A valid JWT token for the authenticated user. The server validates this before accepting the connection.

Connection flow

  1. Client opens the WebSocket with ?token=<jwt>.
  2. Server validates the token and verifies the farm is owned by the user.
  3. On successful connection, the server immediately sends the last recorded reading as an initial message.
  4. Every subsequent sensor reading ingested via POST /api/v1/sensors/reading for this farm is broadcast as a sensor_reading message.

Message types

Sent once immediately after the connection is accepted. Contains the full SensorReadingResponse payload of the most recent reading.
{
  "type": "initial",
  "data": {
    "id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
    "device_id": "ESP32-CAMPO-001",
    "soil_moisture_pct": 44.5,
    "air_temp_c": 31.2,
    "air_humidity_pct": 70.1,
    "vpd_kpa": 1.38,
    "created_at": "2026-06-28T14:35:00Z"
  }
}

Close codes

CodeMeaning
4001Authentication failed — token missing, invalid, or expired.
4002Invalid farm UUID — the farm_id path parameter is not a valid UUID.
4003Access denied — the farm does not belong to the authenticated user.

JavaScript example

const farmId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
const token  = 'your-jwt-token'

const ws = new WebSocket(
  `wss://surqo-api.fly.dev/api/v1/sensors/ws/live/${farmId}?token=${token}`
)

ws.onopen = () => console.log('Connected to live feed')

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data)

  if (msg.type === 'initial') {
    console.log('Last reading on connect:', msg.data)
  } else if (msg.type === 'sensor_reading') {
    console.log('New reading:', msg.data)
  }
}

ws.onclose = (event) => {
  console.warn(`WebSocket closed — code ${event.code}`)
}
Reconnect automatically on close codes that are not 4001, 4002, or 4003. Those three codes indicate a permanent error (bad credentials or bad farm ID) that will not resolve on retry.

Build docs developers (and LLMs) love