# QiPort > QiPort is a European (EU-hosted) IoT device cloud. Zigbee devices — including Tuya > devices joined through a SMLIGHT SLZB-06P10 coordinator — are read and written through > an HTTPS REST API using stable, vendor-neutral capability keys such as > `environment.temperature` and `actuator.valve`. Energy equipment reached through a > vendor cloud (inverter, wallbox, vehicle) is modelled the same way, under the same > keys. Applications never see Zigbee clusters, Tuya datapoints, or model numbers, and > never connect to the MQTT broker. This file is the machine-readable index for coding > agents. Base URL: https://api.qiport.eu Docs: https://docs.qiport.eu MQTT: mqtt.qiport.eu:8883 — devices and the Edge Agent only. Applications MUST NOT connect. ## Read before generating code - [AI integration guide](https://docs.qiport.eu/ai-integration-guide.md): the full agent-facing guide. Read top to bottom before writing a client. - [OpenAPI 3.1 spec](https://docs.qiport.eu/openapi.yaml): LIVE endpoints only. BUILDING endpoints are held outside `paths` under `x-qiport-building` so generated clients cannot call them. - [Live gateway walkthrough](https://docs.qiport.eu/demo-gateway.md): a real installation. Hub as gateway, Zigbee devices as first-class devices, and a photovoltaic system — inverter, house battery, wallbox, car, air conditioning — as cloud-connected devices on the same API. Real IDs and real responses. Read this for a concrete target to write code against. - [Example TypeScript app](https://docs.qiport.eu/example-app/): runnable Node 22 client, no dependencies. ## Three rules that must not be violated 1. Applications use HTTPS + OAuth2 only. No MQTT broker credentials in an application. 2. The SLZB-06P10 coordinator is never exposed to the internet. It reaches a local QiPort Edge Agent; the agent dials out over mTLS MQTT. 3. Never assume an actuator is controllable. A write grant must exist. Treat HTTP 403 with a `reasons[]` array as a normal, expected outcome. ## Two kinds of device, one API A device is either behind a QiPort gateway (Zigbee, reported by a hub over mTLS MQTT) or cloud-connected (a connector polls a vendor cloud — Victron, go-e, Ford — and publishes under the device's own identity). Both appear in `/v1/devices`, both have `/state` and `/telemetry`, both use catalogue keys. A client MUST NOT branch on which kind a device is; the transport is a platform detail. Connectors do not republish a stale reading, and the limit follows the source: five minutes for an inverter polled every 30 s, ninety minutes for a Daikin unit polled every 20-60 minutes under a vendor call quota. A vendor cloud that stops answering makes its device go `offline` with its last values and their original timestamps — never a flat line of repeated numbers. Two sensors in two places are two devices, both reporting `environment.temperature`. There is no `environment.temperature_outdoor`; group readings by device, not by key. ## Capability families `environment.*` air temperature, humidity. `electrical.*` power and current of sockets and meters. `binary.switch` on/off. `system.*` battery, link quality, signal, uptime. `energy.*` pv_power, load_power, grid_power, battery_power, produced_today, consumed_today. `evse.*` power, current, session_energy, total_energy, state. `vehicle.*` battery_percent, range. `hvac.*` setpoint_temperature, mode. Two traps for generated code: 1. `energy.grid_power` and `energy.battery_power` are SIGNED. Positive grid power is import, negative is export; positive battery power is charging. Taking the absolute value inverts the meaning of the reading. 2. `energy.produced_today`, `energy.consumed_today` and `evse.total_energy` are counters, not instantaneous values. Aggregate them with `max` or `last`, never `mean`. The daily ones reset at midnight; `evse.total_energy` never resets. Do not derive one reading from another. `pv − load` is not the grid flow. Read the measured key. ## Status of every endpoint Endpoints carry one of three statuses. Generate code only against LIVE. - LIVE — implemented, deployed, tested. - BUILDING — code written, not deployed. Calling it today fails. - NOT BUILT — roadmap only. No shape exists. Do not generate code that calls it. Nothing is BUILDING today. Everything listed below is deployed. ## LIVE endpoints - `POST /v1/oauth/token` — OAuth2 client_credentials, bearer token, `expires_in` 3600. - `GET /v1/devices` — list devices; cursor pagination; filters `status`, `model`, `site_id`. - `GET /v1/devices/{publicId}` — one device. - `GET /v1/devices/{publicId}/state` — latest value per capability. - `GET /v1/devices/{publicId}/telemetry` — history; `from`, `to`, `capabilities`, `resolution=raw|1m|5m|1h|1d`, `aggregation`. - `POST /v1/devices/{publicId}/commands` — write a value to a writable capability. Refusal is the normal case: 403 with `reasons[]`. - `PUT /v1/devices/{publicId}/capability-grants` — enable or revoke a write grant. - `GET /v1/live` — Server-Sent Events. Types: `telemetry`, `device_status`, `command_update`, `pairing_update`. Filter with `?types=`. - `GET /v1/catalog/capabilities` — canonical capability catalogue, units, safety classes. - `GET /v1/sites` — sites. - `POST /v1/bindings`, `GET /v1/bindings` — bind a device to an application object. - `POST /v1/pairing/sessions` — open a permit-join window on one hub. It closes itself. - `GET /v1/pairing/sessions/{id}` — the window and every candidate that joined it. - `POST /v1/pairing/sessions/{id}/adopt` — turn a candidate into a device. - `POST /v1/pairing/sessions/{id}/reject` — refuse a candidate, keep the record. - `DELETE /v1/pairing/sessions/{id}` — shut the window now. - `GET /health` — unauthenticated liveness. ## Two things about pairing that generated code gets wrong 1. A candidate is NOT a device. It has no `public_id`, no telemetry, no commands. It becomes a device only when a human calls adopt. Do not model it as a device with missing fields, and do not adopt automatically. 2. Adoption is NOT permission. The adopt response carries `write_blocked[]` — every writable capability on a draft profile is created as an explicit denial. Control requires granting each one through `PUT /v1/devices/{publicId}/capability-grants`. ## Safety classes S0/S1 execute immediately. S2 requires `confirm: true` in the body. S3 requires a signed single-use challenge plus the `commands:high_risk` scope. A challenge is a permission to do exactly one thing once: device, capability, value, project and client are all bound into it, and replaying it is refused. ## Not built There is no `/v1` endpoint for provisioning, firmware, webhooks, GraphQL or WebSocket. Hubs do update themselves over the air, but that runs between the device and the firmware host — it is not an application-facing API. Do not invent URLs for any of it.