# Mobile App API (iOS — MySOLEM & MyIndygo apps) **Base URL**: `https://qualif.mysolem.com` **Prefix**: `/api/` (distinct from the web platform's unprefixed routes) **Auth**: OAuth2 Bearer token (not session cookie) **Source**: mitmproxy capture of both iOS apps (`inputs/apps_captured_all.jsonl`) The mobile apps use a cleaner, more structured REST API than the web platform. Key differences: - Bearer token instead of cookies - `/api/` prefix on all endpoints - Gateway-routed module access: `GET /api/module/{gatewaySerial}/{action}/{childSerial6}` - High-level composite endpoints (`getPoolStatus`, `getUserWithHisModules`) --- ## Authentication — OAuth2 ``` POST /oauth2/token Content-Type: application/json {"scope": "*", "grant_type": "client_credentials"} ``` Response: ```json { "access_token": "6wtff8dVlhjn7y...", "expires_in": 5184000, "token_type": "Bearer" } ``` `expires_in` = 5,184,000 seconds = **60 days**. The `client_id` and `client_secret` are embedded in the app binary. The server accepts `client_credentials` without explicit client authentication headers visible in the capture — likely validated by the app's TLS client certificate or embedded credentials. All subsequent requests: ``` Authorization: Bearer {access_token} ``` ```python import requests BASE = "https://qualif.mysolem.com" r = requests.post(f"{BASE}/oauth2/token", json={"scope": "*", "grant_type": "client_credentials"}) token = r.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} # Then use headers= on every request resp = requests.get(f"{BASE}/api/getUser", headers=headers) ``` --- ## User ``` GET /api/getUser ``` Returns current authenticated user with `agency`, `distributor`, `manufacturer` fields not present in the web API. ``` GET /api/getUserWithHisModules ``` Returns user object with full `modules` array embedded. ``` GET /api/getUserPools ``` Returns array of pool configuration records. Each pool has: ```json { "id": "5de53c1c7ed4c45c2cfe0688", "owner": "5de53bca7ed4c45c2cfe067f", "adminUsers": ["5de53bca7ed4c45c2cfe067f"], "address": "30 Rue de Florette, 30250 Villevieille, France", "latitude": 43.79417821332963, "longitude": 4.095361891348177, "volume": 25, "installationType": "outdoor", "filteringType": "filterGlass", "waterTreatmentType": "chlorineStabilizedTablet", "phRegulationMode": "automatic", "chlorineRegulationMode": "manual", "heatingSystemType": "none", "coverType": "none", "cyanuricAcidRate": 0, "model": "Autre" } ``` > This endpoint reveals the pool ID `5de53c1c7ed4c45c2cfe0688` for the Domicile pool — previously only accessible via the main `anelissen@solem.fr` account on the web. Via the mobile API + OAuth2 it is readable. ``` GET /api/getUserWateringModuleGroups → {"wateringModuleGroups": []} GET /api/getUserAgriculturalModuleGroups → {"agriculturalModuleGroups": []} GET /api/getCustomers → {"err": {"code": "user_is_not_professional"}} (pro-only) GET /api/users/{userId}/canopy-sites GET /api/users/{userId}/access-requests GET /api/users/{userId}/module-access-requests ``` --- ## Pool ``` GET /api/getPoolStatus?attributesToPopulate[]=modules&poolIdentifier={poolId} ``` Returns pool status including all associated modules with full config. Pool ID for Domicile: `5de53c1c7ed4c45c2cfe0688`. ``` GET /api/getUserPools (see above) ``` --- ## Modules ``` GET /api/getModuleWithHisUsers?module={moduleId} ``` Returns module object with full `users` array. ``` GET /api/getModuleInventory ?module={gatewaySerial} &startDate={iso8601} &endDate={iso8601} &data=1 ``` Returns `{children: [...]}` — all child modules registered under this gateway with full config, battery, connectivity state. Most efficient way to enumerate a site's devices. ``` GET /api/getModuleBackups?module={moduleId} ``` Returns array of saved program backups (automatic + manual snapshots). ``` PUT /api/updateModule Content-Type: application/json {"module": {"id": "moduleId", "batteryVoltage": 77, "powerState": 0, "status": {...}, ...}} ``` Response: full module object including `inputs` and `outputs` arrays. Used by the app to sync device status back to the server after a LoRa radio round-trip. The `status` field contains the device's reported state (e.g. `ag.outputs`, `watering`). --- ## Gateway-routed module access All real-time commands go through the gateway module (LRMB10). The URL encodes: - `{gatewaySerial}` = full gateway serial, e.g. `1000000705320001` - `{childSerial6}` = **last 6 hex chars** of the child module's serial **Domicile module routing table:** | Child module | childSerial6 | Full serial | |---|---|---| | LRPC-F1DA27 (pool controller) | `F1DA27` | `150302F1DA270001` | | LR6AG-070917 (valve) | `070917` | `6606010709170001` | | LRMS-0721DC (sensor station) | `0721DC` | `7400040721DC0001` | | LR2IPECO-0C9282 (IP ECO) | `0C9282` | `3802020C92820001` | | LRLEVEL-0D2ED9 (fill valve) | `0D2ED9` | `3A01020D2ED90001` | | LRPR-0D6800 (pressure) | `0D6800` | (lr-pr type) | ### Read endpoints ``` GET /api/module/{gatewaySerial}/information ← gateway itself GET /api/module/{gatewaySerial}/information/{child6} ← child module real-time info GET /api/module/{gatewaySerial}/status/{child6} ← current output/input states GET /api/module/{gatewaySerial}/configuration/{child6} ← device configuration GET /api/module/{gatewaySerial}/programs/{child6} ← programs stored on device GET /api/module/{gatewaySerial}/inventory ← all children (same as getModuleInventory) ``` Gateway information response (`GET /api/module/1000000705320001/information`): ```json { "mtype": "10", "msn": "1000000705320001", "name": "LRMB10-070532 (Perso)", "vsoft": "6.5.0", "ble": "1.0.13", "ssid": "Freebox-2A5E5D", "bluetoothLink": true, "wifiLink": true, "ethernetLink": false, "modemLink": false, "up": 782800, "cdate": "2026-06-05T00:11:38+02:00", "dmn": "qualif.mysolem.com" } ``` ### Write endpoints ``` POST /api/module/{gatewaySerial}/status/{child6} Content-Type: application/json {"ag": [{"index": 0, "rainDelay": 0}, {"index": 1, "rainDelay": 0}, ...]} ``` Sets rain delay for each output. Response: full device status including `dialogTimeStamp`, `ackTimestamp`, `temperature`, `battery`, `ag.sensor`, `ag.outputs`. ``` POST /api/module/{gatewaySerial}/programs/{child6} Content-Type: application/json {programs: [...], programmationType: 1} ``` Sends programs directly to device over LoRa. ``` POST /api/module/{gatewaySerial}/manual/{child6} Content-Type: application/json {...command body...} ``` Sends a manual command to the device over LoRa. --- ## Sensor data & chart ``` GET /api/getInputs?id={moduleId} ``` Returns all inputs for a module with full `expression`, `expressionInverse`, alert config, `pushAlert`, `mailAlert`. ``` GET /api/getComputedChartData ?inputId={inputId} &startDate={iso8601} &endDate={iso8601} &numberOfBar={N} &locale=fr ``` Returns input data bucketed into N bars over the date range. Response includes `expression`, `expressionInverse`, and `computedSensorData` array. ``` GET /api/getComputedOutputActivationHistory ?moduleId={moduleId} &outputIndex={0|1|2...} &startDate={iso8601} &endDate={iso8601} &numberOfBar={N} ``` Returns per-period activation state: `[{currentPeriodStartDate, currentPeriodEndDate, value}]`. With `numberOfBar=1440` over 24h = 1 bar per minute. --- ## Programs ``` GET /api/getPrograms?id={moduleId} ``` Full program objects including `startTimesCycles`, `stationsDuration` arrays not present in the web API. ``` PUT /api/updatePrograms Content-Type: application/json { "programmationType": 1, "programs": [{ "id": "...", "name": "...", "waterBudget": 100, "windows": [...], "programCharacteristics": {...} }] } ``` Response: `{updatedPrograms: [...]}`. ``` POST /api/library/program-templates {"limit": 10, "moduleId": "moduleId", "offset": 0} → {"programTemplates": []} ``` --- ## Documents & metadata ``` GET /api/getDocument?documentType=installation-guide&product-type={moduleType} → {"url": "https://qualif.mysolem.com/files/notices/{type}/solem/fr-installation-guide.pdf"} ``` ``` GET /api/getAdvertisingProfilePresets → [{name, ble: {windows: [...]}, loRa: {interval}}] ``` BLE advertising profiles: `default`, `ecoMode`, etc. ``` GET /api/getAllBanishedSerialNumbers → ["120C0200750001", ...] ``` Blacklisted/revoked device serial numbers. --- ## Push notifications ``` POST /api/addApnsTokenToUser {"isSandbox": false, "topic": "com.mysolem.qualif", "user": "{userId}", "token": "{apnsToken}"} → {"code": "ok"} ``` --- ## Reporting (app telemetry) These are called by the app after sending data to devices — informational only. ``` POST /api/reportModuleDatasSent {"module": "moduleId"} POST /api/reportProgramsDatasSent {"programs": ["programId", ...], "module": "moduleId"} POST /api/reportManualCommandSent {"route": "http", "command": {...}, "id": "moduleId"} ``` --- ## Key differences vs web API | | Web (cookie auth) | Mobile (Bearer token) | |---|---|---| | Auth | Session cookie | OAuth2 Bearer | | Module routing | Direct by ID | Via gateway serial | | Pool access | Admin-only / main account | `GET /api/getUserPools` | | Sensor data | `getComputedSensorData` (ticks) | `getComputedChartData` (bars) | | Output history | `output/events` | `getComputedOutputActivationHistory` | | Programs | `programs/modules` | `getPrograms` (richer fields) | | Module inventory | `modules/search` | `getModuleInventory` via gateway | | Backups | — | `getModuleBackups` |