# MySolem API — Endpoints All paths relative to `https://qualif.mysolem.com`. All require session cookie unless noted. > **Mobile API**: the iOS apps use a separate `/api/` prefix layer with OAuth2 Bearer auth. See `api-docs/mobile-api.md`. --- ## Feature flags ``` GET /features ``` Returns flat JSON object of boolean flags (`dashboard`, `enableHydraulicSupervision`, …). --- ## Sites ``` GET /canopy-sites?userId={userId} ``` Returns `{ items: [...], total: N }`. Each item includes full `modules` array and `fencing` config. ``` GET /canopy-sites/{siteId}/users GET /canopy-sites/{siteId}/modules/indicators GET /canopy-sites/{siteId}/informations GET /canopy-sites/{siteId}/rules GET /canopy-sites/{siteId}/map/modules GET /canopy-sites/{siteId}/modules/eco-mode-compatible GET /canopy-sites/{siteId}/site-notes GET /canopy-sites/{siteId}/notifications GET /canopy/{siteId}/waterBudget ``` --- ## Modules **Search / list:** ``` POST /modules/search Content-Type: application/json { "query": { "id": { "$in": ["moduleId1", "moduleId2"] } }, "projection": { "name": 1, "type": 1, "serialNumber": 1 } } ``` Response: `{ result: { results: [...], totalCount: null } }` ``` GET /modules/available?userId={userId} ``` **Paginated modules for a site:** ``` GET /canopy-sites/{siteId}/modules?limit=20&skip=0&includeTotalCount=true ``` Response: `{items: [...], total: N}`. Note: items use `_id` (MongoDB style), not `id`. **Dashboard chart data:** ``` GET /modules/echarts-data?includeCustomers=true&includeAgency=false → {chartModuleTypes: [{name, value}], chartModuleConnectivities: [...]} ``` **SIM card payment check:** ``` GET /sim-cards/needsPayment?moduleIds={id1}&moduleIds={id2} → {result: {"moduleId": false, ...}} ``` **Remote state:** ``` GET /remote/module/state?moduleId={moduleId} GET /remote/module/configuration?moduleId={moduleId} ``` **Module data model (key fields):** ```json { "id": "...", "name": "...", "type": "lr-ag", "serialNumber": "6606010709170001", "macAddress": "C8:B9:61:...", "softwareVersion": "7.2.0", "batteryVoltage": 81, "battery": 5, "rssi": 1, "isOnline": true, "relay": "relayModuleId", "latitude": 43.79417, "longitude": 4.09520, "lastRadioCommunication": "2026-06-04T17:59:29.000Z" } ``` --- ## Sensor / Input data **Batch input config (most efficient way to get all input metadata):** ``` GET /inputs?inputId=id1%3Bid2%3Bid3 ``` Semicolons (`%3B`) separate multiple IDs. Returns array of full input objects with `expression`, `type`, `unit`, `module`, `index`. Much lighter than querying each individually. > **Note**: sensor data can be queried per-input or per-module (all inputs at once). **All inputs for a module — preferred for dashboard-style loads:** ``` GET /remote/module/getComputedSensorsData ?moduleId={moduleId} &startDate={iso8601} &endDate={iso8601} &forceRawOrComputed=computed ``` Returns an **array** of input objects, each with a `computedSensorData` field containing ticks. More efficient than querying each input individually. Pass `forceRawOrComputed=raw` for raw ADC values. **Single input telemetry:** ``` GET /remote/input/getComputedSensorData ?inputId={inputId} &startDate={iso8601} &endDate={iso8601} &ensurePrevTickForContinuity=true ``` Response: ```json { "id": "60ca42adad69dac1558f7c66", "name": "Plumbago", "module": "60ca42adad69dac1558f7c64", "type": 12, "unit": 10, "interval": 60, "typeIsMoistureSensor": true, "computedSensorData": [ { "tickTimestamp": "2026-06-01T00:42:00.000Z", "value": 54.1875 } ] } ``` **Last tick only (no date range — fastest for live dashboards):** ``` GET /input/getLastTickFromInput?inputId={inputId} ``` Returns `{ "tick": { "input": "...", "value": , "timestamp": "...", "id": "..." } }`. Value is **raw** (before expression). Divide by 100 for pH/temp from LRPS. **Averages:** ``` GET /inputs/average?inputIds={id1},{id2}&startDate={iso8601}&endDate={iso8601} ``` **Update input config:** ``` POST /input/update ``` **CSV export:** ``` GET /input/getComputedInputIpxDataBetweenDatesAsCsv ?inputId={inputId}&startDate={iso8601}&endDate={iso8601} ``` **Legacy socket.io call** (same route, used from browser JS): ``` socket.request({ method: "GET", url: "/remote/module/getComputedSensorsData", data: { moduleId, startDate, endDate } }, callback) ``` --- ## Outputs ``` GET /outputs/modules?moduleIds={moduleId} POST /outputs/search { "query": { "module": { "$in": ["moduleId"] } } } POST /output/update ``` Response: `{ outputs: [{ id, module, index, name, flowRate: { value, unit } }] }` Flow rate unit: `1` = L/h, `2` = m³/h. **Output event history:** ``` GET /output/events?moduleId={moduleId} GET /output/events?outputId={outputId}&startDate={iso8601}&endDate={iso8601} ``` **Watering forecast:** ``` GET /output/forecast?moduleId={moduleId} GET /output/forecast?outputId={outputId} ``` --- ## Programs ``` GET /programs/modules?moduleIds={moduleId} PUT /programs { "programs": [...], "module": "moduleId" } GET /programs/reset?moduleId={moduleId} POST /programs/search { "query": { "id": { "$in": [...] } } } ``` Program object: ```json { "id": "...", "name": "Pelouse Terrasse", "module": "...", "index": 0, "programmingType": "temporal", "weekDays": 127, "waterBudget": 100, "windows": [{ "start": 420, "end": 422, "on": 120, "off": 0 }] } ``` --- ## Daily watering ``` GET /garden/dailyData/{moduleId} GET /modules/heatmap-data?moduleIds={ids}&startDate=X&endDate=Y ``` --- ## Users ``` GET /users-info/{userId}?includeUserGroups=true GET /users/{userId}/modules GET /users/{userId}/sites GET /users/{userId}/module-groups GET /users/{userId}/subscriptionModules POST /users/search { "query": {...}, "projection": {...} } ``` **Flexible module list (POST — supports field projection + filtering):** ``` POST /users/{userId}/modules Content-Type: application/json { "fields": {"name": 1, "type": 1, "serialNumber": 1, "displayType": 1}, "filters": {}, "addOwnershipFlag": true, "addOwnerIds": false, "includeCustomers": true, "includeAgency": false, "includeTotalCount": true, "limit": 20, "skip": 0 } ``` Response: `{modules: [...], totalCount: N}`. Supported filter fields: `search`, `tagIds`, `displayTypes`, `connectivities`, `powerTypes`. **Similar pattern for grouped resources:** ``` POST /users/{userId}/module-groups → {moduleGroups, totalCount} POST /users/{userId}/agricultural-module-groups → {agriculturalModuleGroups, totalCount} POST /users/{userId}/watering-module-groups → {wateringModuleGroups, totalCount} POST /users/{userId}/customers → {customers, totalCount} POST /users/{userId}/associated → {users, totalCount} (associated users) POST /users/{userId}/tags → {tags} ``` **Activity log:** ``` POST /users/{userId}/records { "limit": 10, "skip": 0, "filters": {"startDate": "2026-05-28T21:51:48.266Z", "showAllEvents": true}, "includeTotalCount": true, "includeCustomers": true, "sortColumn": "timestamp", "sortOrder": -1, "fields": {"category": 1, "label": 1, "module": 1, "timestamp": 1} } → {records: [...], totalCount: N} ``` **Activity log categories:** ``` POST /users/{userId}/records/categories {"filters": {"startDate": "2026-05-28T21:51:48.266Z"}, "includeCustomers": true} → [{category, displayCategory}] ``` --- ## Notifications / journal ``` GET /journal/notifications GET /journal/counts GET /journal/warnings/accepted POST /journal/acknowledge/all POST /canopy-sites/{siteId}/notifications/acknowledge-all ``` **Notifications (site-level, POST — supports filtering):** ``` POST /canopy-sites/{siteId}/notifications {"limit": 1, "includeTotalCount": true, "filters": {"state": {"$in": ["unread", "process"]}}} → {records: [...]} ``` **Journal notifications (cross-site, filtered):** ``` POST /journal/notifications { "canopyIds": ["siteId"], "limit": 100, "skip": 0, "filters": {"priority": {"$in": ["critical", "high"]}, "state": {"$in": ["unread", "process"]}}, "projection": {...} } → {records: [...], total: N} ``` --- ## Weather ``` GET /moduleGroups/getCanopyWeather ?location[latitude]={lat}&location[longitude]={lng} ``` Returns 3-day AccuWeather-style forecast. --- ## Clusters (watering module groups) ``` GET /clusters/{clusterId}/data GET /clusters/{clusterId}/modules GET /clusters/{clusterId}/programs PUT /clusters/{clusterId}/programs PATCH /clusters/{clusterId}/name PATCH /clusters/{clusterId}/flowrate DELETE /clusters/{clusterId} ``` --- ## Favorites & tags ``` GET /favorites/{userId} POST /favorites DELETE /favorites/{userId} GET /modules/{moduleId}/tags POST /module/tags { "module": "moduleId", "tags": [...] } ``` --- ## Module notebook (event log) Full chronological history of manual commands, alerts, firmware updates, pool events: ``` GET /modules/{moduleId}/notebook?startDate={unix_epoch_seconds} ``` > `startDate` is a **Unix timestamp in seconds**, not ISO 8601. Response: `{ "records": [...] }` sorted oldest-first. Record structure: ```json { "id": "...", "module": "moduleId", "category": "manual_command", "label": "pHmètre - Seuil bas dépassé", "businessLabel": "Le pH est trop bas", "priority": "low", "state": "archived", "timestamp": "2026-06-04T21:00:00.000Z", "params": { "action": 4, "index": 1 }, "input": "inputId", "notifications": [], "comments": [] } ``` Known `category` values: `manual_command`, `pool`, `platform_alert`, `firmware_update` The `params` field structure varies by category: for `manual_command` it mirrors the linesControl action/index that triggered the event; for `pool` it contains poolAlertType (e.g. `backwashReminder`, `poolLevelSensorCleaningReminder`). See `api-docs/control.md` for action code definitions. --- ## Module users ``` GET /modules/{moduleId}/users ``` Returns array of full user objects with access to this module (email, push tokens, preferences). --- ## Linked inputs ``` GET /input/getInputsLinkedModule/{moduleId} ``` Returns inputs linked to a module. Returns `[]` for standalone LoRa modules. --- ## User avatar ``` GET /user/avatar GET /user/avatar?t={unix_ms} ← cache-bust variant ``` Returns current authenticated user's avatar as `image/png`. Never cached (`no-cache, no-store`). --- ## SIM subscription ``` GET /users/{userId}/checkSubscriptionSimcardStatus ``` Returns ~39-byte JSON — whether the user has an active cellular SIM subscription. --- ## Field supervision (watering events over time range) ``` GET /canopy/{siteId}/field-supervisions/modules → {canopyModules: [{type, modules: [{serialNumber, name, outputs: [{id, index, name}]}]}]} GET /canopy/{siteId}/field-supervision/{supervisionId}/resume → {allPrograms: [...]} GET /moduleGroups/{siteId}/field-supervision/{supervisionId}/events/{startISO}/{endISO}?timezone=Europe/Paris → {events: [{eventId, moduleId, moduleName, identifier, name, start, end, flowRate, index}]} ``` Field supervision ID for Domicile: `643c5134347b27edbf38b173` --- ## Satellite / NDVI imagery Host: **`sentinel.mysolem.com`** (separate from the main API host). **Check image availability:** ``` GET /moduleGroups/{siteId}/fencing/{timestampMs}/images-check → {availableImages: {available: true, reason: "ok"}} ``` **List images:** ``` GET /moduleGroups/{siteId}/fencing/{timestampMs}/images → {imagesList: {images: [{name, urlRgb, urlSavi}]}} ``` **Fetch satellite images directly:** ``` GET https://sentinel.mysolem.com/api/plot/{plotId}/image/{timestamp}/savi → PNG (SAVI vegetation index) GET https://sentinel.mysolem.com/api/plot/{plotId}/image/{timestamp}/rgb → PNG (true color) ``` Timestamp format: `YYYYMMDDTHHmmss`, e.g. `20220417T103631`. Domicile plot ID: `62f4efcb4a6dd563203558c3`, fencing timestamp: `1656939129360`