IGH developershome API v1 · 1.1.0

Hoppscotch

The Hoppscotch package

To try the API in the Hoppscotch desktop app, import the Hoppscotch package from the public repository's hoppscotch/ folder (or download it below): the collection with every operation, the authorization flows and the refusals, scripted with Hoppscotch's own hopp API; an environment each for the home network and the cloud; and a guide for the WebSocket in the Realtime tab, which imports nothing, with the ClientMessage messages to send. It holds the same requests as the Postman package and is generated with these pages, so it always matches the reference. On the home network, pin the gateway's CA for its address first (GET /v1/gateway/tls, TLS and CA pinning).

IGH home API in Hoppscotch

Version 1.1.0. Generated from the API's OpenAPI and AsyncAPI documents with the rest of https://developers.igh.app, from the same requests as the Postman package: nothing here is edited by hand.

File What
igh-api.hoppscotch.json The collection (Hoppscotch collection format v12, requests v17): every operation, the authorization flows, errors, a read-only smoke run.
igh-local.hoppscotch_environment.json Environment: IGH local (home network).
igh-staging-cloud.hoppscotch_environment.json Environment: IGH staging cloud.
igh-cloud.hoppscotch_environment.json Environment: IGH cloud.
WEBSOCKETS.md The WebSocket in Hoppscotch's Realtime tab: the URLs, the messages to send, the events and close codes.

Made for the Hoppscotch desktop app 26.9 (its data formats: collection v12, request v17, environment v2). The scripts use Hoppscotch's own hopp API, which runs in the experimental scripting sandbox: Settings → Experiments → Experimental scripting sandbox stays on (the default).

0. Install and start

Install the desktop app from https://hoppscotch.com/download (Linux: AppImage, .deb or .rpm) and start it from the applications menu, or with hoppscotch where the package puts that command on your PATH. Requests go through the desktop app's Native interceptor (Settings → Interceptor), the default: it reaches the home network and holds the CA of step 1. The web app at hoppscotch.io can't reach the gateway on the home network.

1. Trust the gateway's CA (local environment)

On the home network the gateway answers on port 8490 with HTTPS signed by its own CA. Pin that CA for the gateway's address only; never turn off Verify Host or Verify Peer.

  1. Get ca.pem, either:
    • in the IGH app: Settings → Integrations → the API integration → Download CA (igh-<serial>-ca.pem; it shows the SHA-256 fingerprint too);
    • or through the cloud: select a cloud environment, set token (step 4), send TLS → Gateway CA (to pin): the response pane's Console prints ca; save it as ca.pem. Compare fingerprint with openssl x509 -in ca.pem -noout -fingerprint -sha256.
  2. Settings → Interceptor → Native → the gear button (Manage Domains Overrides) → add the gateway's address exactly as the requests use it, host and port: igh-<serial>.local:8490 (or 192.168.1.20:8490 when you use the LAN address), then select it.
  3. For that domain: Verify Host and Verify Peer on; CA Certificates → Add Certificate File → ca.pem, turned on.
  4. Not under Global Defaults: a CA certificate there is used for every address (the native relay hands it to curl as the CA bundle, in place of the default one), so the cloud's certificates would no longer verify.
  5. Select the IGH local environment and set serial (the gateway id, lower case). Where igh-<serial>.local doesn't resolve, put the gateway's LAN address in baseUrl (https://192.168.1.20:8490/v1): the certificate names it too.
  6. After the CA is regenerated in the app, replace the file.

2. Import

  1. Collections (left sidebar) → Import / Export → Import from Hoppscotch → Import from File → igh-api.hoppscotch.json → Import.
  2. Environments → Import / Export → Import from Hoppscotch → select the three environment files.
  3. Pick an environment in the environment selector (top right).
  4. The WebSocket is not part of the import: see WEBSOCKETS.md.

3. The environments

Switching the environment is the only change between the home network and the cloud: every request uses <<baseUrl>> and <<token>>. The device flow, OAuth and MCP always go to a cloud (pairBase, recordUrl).

File Name baseUrl Notes
igh-local.hoppscotch_environment.json IGH local (home network) https://igh-<<serial>>.local:8490/v1 The gateway on the home network, port 8490, HTTPS with the gateway's own CA (pin it for this address: Settings → Interceptor → Native → Manage Domains Overrides, README step 1). serial is the gateway id in lower case. Without mDNS, put the gateway's LAN address in baseUrl instead (https://192.168.1.20:8490/v1): the certificate names the LAN addresses too. Pairing, MCP and OAuth go to the production cloud; for a gateway on staging change pairBase to https://api.staging.igh.app/v1 and mcpUrl to https://mcp.staging.igh.app.
igh-staging-cloud.hoppscotch_environment.json IGH staging cloud https://api.staging.igh.app/v1 The staging cloud's relay (api.staging.igh.app), MCP server (mcp.staging.igh.app) and OAuth server (cloud.staging.igh.app). The home's gateway must use the staging cloud, and the integration needs Cloud access on.
igh-cloud.hoppscotch_environment.json IGH cloud https://api.igh.app/v1 The production cloud's relay (api.igh.app), MCP server (mcp.igh.app) and OAuth server (cloud.igh.app). The integration needs Cloud access on.

Every variable is in the environment, the ids and the scripts' state too: Hoppscotch's scripts write only environments, and a collection variable would outrank an environment's value.

Variable What
transport local or cloud: requests marked (local only) or (cloud only) aren't sent in the other.
baseUrl The API, ending in /v1.
wsUrl The WebSocket /v1/stream.
serial The gateway id, lower case (on the integration's page and in mDNS _igh-api._tcp). The local environment's addresses use it.
recordUrl The integration's own MCP address https://mcp.igh.app/r/<id>, from its page in the app (Agent access on): the device flow, OAuth and MCP use it.
pairBase The cloud API that runs the device flow (/v1/pair).
mcpUrl The cloud's MCP server; an integration's own address (recordUrl) is <mcpUrl>/r/<id>.
token (secret) The credential every request sends (Authorization: Bearer <<token>>). The Authorization folders set it.
apiKey (secret) An API key to paste (folder (a)).
oauthRefreshToken (secret) OAuth's refresh token: folder (d) request 5 sets it.
revokedToken (secret) A revoked credential, for a negative case.
otherRecordToken (secret) Another integration's credential, for a negative case.
deviceName Its current name (for the rename request).
unitRef deviceId's unit: the third part of the device id.
dimmerId The first dimmer.
shutterId The first shutter.
acId The first air conditioner.
irId The first IR remote.
irKey A key the IR remote learned.
lockId The first lock.
gateId The first gate.
roomName Its current name.
createdSceneId The test scene.
sceneRevision Its revision.
automationRevision Its revision.
scheduleRevision Its revision.
intentValue Its value, as JSON.
caFingerprint The gateway CA's SHA-256 fingerprint.
tokenKind key, agent, session or oauth.
pairCode The device flow's device code.
pairUserCode Its matching code.
pairInterval Seconds between polls.
pairPolls Polls so far.
mcpCode check_access's code.
oauthResourceMetadata The protected-resource metadata URL.
oauthIssuer The authorization server.
oauthAuthUrl Set by the scripts.
oauthTokenUrl Set by the scripts.
oauthRegisterUrl Set by the scripts.
oauthRevokeUrl Set by the scripts.
oauthScope Set by the scripts.
oauthClientId Hoppscotch's registered client id.
oauthRedirectUri Where the browser lands after OAuth approval (a loopback address: nothing answers, the code is in the address bar).
oauthAuthorizeUrl The authorization address request 4 makes, to open in a browser.
oauthState Its state, checked when the code comes back.
oauthVerifier (secret) Its PKCE code verifier.
oauthCode (secret) The address the browser landed on (or only its code), pasted for request 5.
deviceId, lightId, sceneId, automationId, scheduleId, roomId, intentId, sensorId An id, empty: Getting started → House and the list requests fill it (only while empty); set it to choose.

Secret variables are masked and kept on this device only (Hoppscotch never syncs or exports their values). No environment holds a credential or an id: fill them in Hoppscotch.

4. Get a credential

Each folder under Authorization goes top to bottom and ends with token set.

Use Folder Credential
Scripts, your own programs (a) API key: an administrator creates it on the integration's page (Settings → Integrations → API integration → Create key); paste it into apiKey igh_key_… until deleted
Agent-like tools, terminals, anything without a browser (b) Device flow: POST /v1/pair with recordUrl, the administrator compares the matching code and accepts in the IGH app, POST /v1/pair/token until approved igh_agent_… until revoked
The same, for one session (c) Device flow, the administrator picks Accept for this session igh_session_… (ends with the session)
Hosted platforms (Claude, ChatGPT) (d) OAuth 2.1 with PKCE: discovery, dynamic registration, the authorization address in a browser, the code exchange an access token of about an hour, and a refresh token
MCP clients (e) MCP over JSON-RPC: request_access, then check_access until accepted igh_agent_… or igh_session_…

(f) holds the refusals: no token, a bad or revoked one, Agent access or Cloud access off, another integration's credential on its address.

Polling (b, c, e). Hoppscotch can't send a request again from a script, so the poll is one request you send again until it answers with the credential; from the second poll on it first waits pairInterval seconds (and 5 s longer after each slow_down). Hands-free: the folder → Run Collection → select only the poll request, Iterations 120, Delay 0, Keep variable values on, Stop run if an error occurs on. Once the request is answered (approved, denied or expired) the next poll stops before it is sent, which ends the run.

OAuth (d). Hoppscotch's own OAuth 2.0 authorization (Authorization Code with PKCE) sends the browser back to the app's page: https://hoppscotch.io/oauth in the web app, which IGH accepts, but app://hoppscotch/oauth in the desktop app, which IGH refuses (redirects are https, or http on a loopback address). In the desktop app the folder does the same flow in requests: 4 makes the authorization address with a fresh PKCE verifier (S256) and prints it; open it in a browser and approve; the browser lands on http://127.0.0.1/igh-callback?code=… (nothing answers there); copy that whole address into the environment's oauthCode and send 5, which sets token and oauthRefreshToken. The folder's last request is the same flow with Hoppscotch's OAuth 2.0 authorization, for the web app.

5. Run

  1. Getting started → House fills the id variables (deviceId, lightId, sceneId, roomId, …) with the house's first of each kind, only while they are empty: clear one to pick again, or set it to the device you want.
  2. Smoke run (read-only) → Run Collection (with Stop run if an error occurs off): every read, nothing switched or created.
  3. The other folders top to bottom. Requests named [switches a device] operate real units. Scenes, Automations and Schedules create one test object each (a scene of one delay, a disabled automation, a paused schedule): the API never deletes, so remove them in the app.
  4. Errors and edge cases provokes each refusal; some need a setting of the integration changed first (the request says which).

Every request checks its status (its Variables tab holds what the collection's scripts check: igh.expect, igh.code, …). A refusal is checked for { error: { code, message } }, and the response pane's Console prints its appAction (the app screen that does what the API won't) and retryAfter. A request that can't work in the selected environment, or needs an empty variable, stops before it is sent and says why. Through the cloud, 50 requests a second and 600 a minute per home: past that, 429 with retryAfter.

Guides: https://developers.igh.app · Reference: https://developers.igh.app/reference.html · The same requests for Postman: https://developers.igh.app/postman.html

The WebSocket in Hoppscotch

/v1/stream carries requests as messages, subscriptions and pushed events (AsyncAPI: https://developers.igh.app/asyncapi.html).

Hoppscotch has no WebSocket requests to import: a collection holds HTTP and GraphQL requests only, and the Realtime → WebSocket tab keeps just one URL and its protocols, with no saved messages, no headers and no environment variables. So the WebSocket is set up by hand there, once, from this page.

The URLs

Environment wsUrl
IGH local (home network) wss://igh-<<serial>>.local:8490/v1/stream
IGH staging cloud wss://api.staging.igh.app/v1/stream
IGH cloud wss://api.igh.app/v1/stream

The Realtime tab can't set an Authorization header (it uses the app window's own WebSocket), so the token goes in the query: ?token=<the token>. The gateway and the cloud both accept the query token on the upgrade (the cloud strips it before relaying). The tab doesn't fill <<…>> variables either: paste the token itself, copied from the environment (the eye button shows a secret's value).

Connect

  1. Realtime (left bar) → WebSocket.
  2. URL: the environment's wsUrl with the token, for example wss://api.igh.app/v1/stream?token=igh_agent_…. Leave Protocols empty.
  3. Connect. The log shows the connection, then every frame.
  4. In the message box, pick JSON, paste a message below, replace each <<…>> with its value (the Realtime tab sends the text as it is), Send.

On the home network (wss://igh-<serial>.local:8490/v1/stream) the WebSocket is opened by the app window, not by the Native interceptor, so the CA certificate of README step 1 doesn't apply to it: it trusts the system's CAs. Either add the gateway's CA to the system's trust (Fedora: sudo trust anchor ca.pem; every program on the computer then trusts it), or use a cloud URL. Never accept an untrusted certificate instead.

The messages to send

A request message { id, method, path, query?, body? } (path with /v1, as on HTTPS) is answered { id, status, body }; { id, subscribe: [...] } chooses the topics (state, sensor, scene, exposure; all four until a client subscribes).

Subscribe: state

Only state events from now on (answered { id, status: 200, body: { subscribed } }).

{
  "id": "subscribe-state",
  "subscribe": [
    "state"
  ]
}

Subscribe: sensors

Only sensor events from now on (answered { id, status: 200, body: { subscribed } }).

{
  "id": "subscribe-sensor",
  "subscribe": [
    "sensor"
  ]
}

Subscribe: scenes

Only scene events from now on (answered { id, status: 200, body: { subscribed } }).

{
  "id": "subscribe-scene",
  "subscribe": [
    "scene"
  ]
}

Subscribe: exposure

Only exposure events from now on (answered { id, status: 200, body: { subscribed } }).

{
  "id": "subscribe-exposure",
  "subscribe": [
    "exposure"
  ]
}

Subscribe: everything

All four topics (what a new socket gets until it subscribes).

{
  "id": "subscribe-all",
  "subscribe": [
    "state",
    "sensor",
    "scene",
    "exposure"
  ]
}

Request: state now

GET /v1/state over the socket: read it once after (re)connecting to catch up.

{
  "id": "state",
  "method": "GET",
  "path": "/v1/state"
}

Command: light on [switches a device]

A request over the socket, answered { id, status, body } as over HTTPS. Switches a real device.

{
  "id": "light-on",
  "method": "POST",
  "path": "/v1/devices/<<lightId>>/control",
  "body": {
    "op": "set_power",
    "args": {
      "on": true
    }
  }
}

Command: light off [switches a device]

A request over the socket, answered { id, status, body } as over HTTPS. Switches a real device.

{
  "id": "light-off",
  "method": "POST",
  "path": "/v1/devices/<<lightId>>/control",
  "body": {
    "op": "set_power",
    "args": {
      "on": false
    }
  }
}

Keep-alive (no ping message: a cheap request)

The stream has no ping message, and a text that is not JSON (ping) closes it (4400 invalid_request). WebSocket ping frames are answered by the server; to see traffic, send this cheap request.

{
  "id": "keep-alive",
  "method": "GET",
  "path": "/v1/about"
}

What arrives

state

A unit's or gang's new state.

{
  "event": "state",
  "house": "Cohen Home",
  "device": "8F3K2M9Q4T7W:5b0e6c1a-2f4d-4c3b-9a8e-7d6f5e4c3b2a:u-kitchen-light",
  "class": "light_dimmer",
  "state": {
    "on": true,
    "bri": 40
  },
  "at": 1760780000000
}

sensor

A sensor reading or detection.

{
  "event": "sensor",
  "house": "Cohen Home",
  "device": "8F3K2M9Q4T7W:5b0e6c1a-2f4d-4c3b-9a8e-7d6f5e4c3b2a:u-hall-sensor",
  "class": "sensor",
  "state": {
    "motion": true,
    "lux": 120
  },
  "at": 1760780000000
}

scene

A scene runs (running true, then false).

{
  "event": "scene",
  "house": "Cohen Home",
  "device": "8F3K2M9Q4T7W:5b0e6c1a-2f4d-4c3b-9a8e-7d6f5e4c3b2a:scene:s-evening",
  "class": "scene",
  "state": {
    "running": true
  },
  "at": 1760780000000
}

exposure

What the integration shows changed: read GET /v1/house again.

{
  "event": "exposure",
  "house": "Cohen Home",
  "at": 1760780000000
}

answer

The answer to a request message.

{
  "id": "light-on",
  "status": 200,
  "body": {
    "house": "Cohen Home",
    "device": "8F3K2M9Q4T7W:5b0e6c1a-2f4d-4c3b-9a8e-7d6f5e4c3b2a:u-kitchen-light",
    "state": {
      "on": true,
      "bri": 40
    }
  }
}

error (rate limited, the socket stays open)

Through the cloud, a message past the limit.

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests for this home. Wait and try again.",
    "retryAfter": 1
  }
}

error (before a close)

A credential that stopped passing: this frame, then close 4403 agent_access_disabled.

{
  "error": {
    "code": "agent_access_disabled",
    "message": "Agent access is off for this API integration.",
    "appAction": "integrations.record"
  }
}

Errors and close codes


IGH home API 1.1.0. Generated from the doc source; do not edit.