Skip to content

OlivierZal/melcloud-api

Repository files navigation

@olivierzal/melcloud-api

A typed Node.js client for the MELCloud and MELCloud Home APIs, providing access to Mitsubishi Electric air-to-air (Ata), air-to-water (Atw) and energy recovery ventilation (Erv) devices.

License Node GitHub release CI CodeQL

Quality Gate Test coverage Docs coverage

Features

  • Strongly typed — full TypeScript types for both MELCloud Classic and MELCloud Home APIs, with 100% TSDoc coverage on the public surface.
  • Two APIs, one client — Classic and Home behind consistent ergonomics; pick what your account uses.
  • Ata / Atw / Erv support — air conditioners, heat pumps with hot water, and energy-recovery ventilation units.
  • Resilient by default — auto-retry on transient failures, rate-limit awareness, pre-emptive session refresh.
  • Typed failures — telemetry, reports and error-log getters return Result<T> so callers branch on network / unauthorized / rate-limited / validation / server instead of catching generic exceptions.
  • Tree-shakablesideEffects: false plus /classic, /home and /constants subpath exports for namespace-style imports.

Requirements

  • Node.js >= 22
  • A valid MELCloud or MELCloud Home account
  • For installing the package: a GitHub personal access token with the read:packages scope

Installation

Important

This package is published to GitHub Packages, not the public npm registry.

Configure your project so npm fetches the @olivierzal scope from GitHub:

//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
@olivierzal:registry=https://npm.pkg.github.com

NODE_AUTH_TOKEN must be a GitHub personal access token with the read:packages scope (export it in your shell or set it in your CI environment). Then:

npm install @olivierzal/melcloud-api

Usage

MELCloud (classic)

import { ClassicAPI, ClassicFacadeManager } from '@olivierzal/melcloud-api'

const api = await ClassicAPI.create({
  username: 'user@example.com',
  password: 'password',
})

const manager = new ClassicFacadeManager(api, api.registry)

// Browse the device hierarchy
for (const zone of manager.getZones()) {
  console.log(zone.name)
}

// Interact with a device through its facade
const [device] = api.registry.getDevices()
if (device !== undefined) {
  const facade = manager.get(device)
  await facade.updateValues({ Power: true })
}

MELCloud Home

import { HomeAPI, HomeFacadeManager } from '@olivierzal/melcloud-api'

const api = await HomeAPI.create({
  username: 'user@example.com',
  password: 'password',
})

await api.list()
const manager = new HomeFacadeManager(api)

// Interact with a device through its facade
const [device] = api.registry.getAll()
if (device !== undefined) {
  const facade = manager.get(device)
  console.log(facade.name, facade.operationMode, facade.setTemperature)
  await facade.updateValues({ setTemperature: 21 })
}

Error handling

Best-effort getters (telemetry, reports, error logs, settings reads) return a Result<T> so callers can branch on the failure class — network, unauthorized, rate-limited, validation, or server. Mutations (update*, updatePower) keep their throw-on-failure contract.

const result = await facade.getEnergy({ from: '2024-01-01', to: '2024-12-31' })
if (!result.ok) {
  switch (result.error.kind) {
    case 'rate-limited':
      // result.error.retryAfterMs
      break
    case 'unauthorized':
      // re-authenticate before retrying
      break
    default:
      // network / server / validation — log and skip
  }
  return
}
const energy = result.value

Session management

Both clients keep their session alive without caller involvement:

  • Pre-emptive refresh — sessions are renewed when they come within 5 minutes of expiry, so no request pays the re-authentication round-trip on its critical path.
  • Reactive 401 recovery — when the server rejects a credential mid-flight, the SDK re-authenticates once (Home tries the cheap refresh-token exchange first, then falls back to a full sign-in) and replays the original request. A cooldown guard prevents retry loops.
  • Persistence — pass a settingManager (a simple get/set string store) to persist tokens and credentials across restarts; without one, everything stays in memory and a new instance signs in from scratch.

Important

The SettingManager receives the access/refresh tokens and the account password as plain strings. You are responsible for backing it with secure storage (encrypted settings store, OS keychain, …) — do not write it to a world-readable file. The SDK redacts credentials, tokens and cookies from its own log output.

const settings = new Map<string, string>()
const api = await HomeAPI.create({
  settingManager: {
    get: (key) => settings.get(key) ?? null,
    set: (key, value) => settings.set(key, value),
  },
})

Resilience & retries

The request pipeline applies three policies, outermost first:

  1. Rate-limit gate (HTTP 429) — a 429 response pauses all requests for the Retry-After window (delta-seconds and HTTP-date forms are both honored), or 2 hours when the header is absent. While paused, calls fail fast with RateLimitError (retryAfter, unblockAt) instead of hammering the server; check api.isRateLimited before issuing bursts.
  2. Auth retry (HTTP 401) — a single re-authentication + replay, as described above.
  3. Transient retry (GET only) — 502/503/504 responses are retried up to 4 times with exponential backoff and jitter (1 s initial delay, 16 s cap). Mutations (POST/PUT) are never retried automatically: a write that may have landed server-side must not be silently duplicated. Each retry is observable through the onRequestRetry lifecycle event.

Imports

Exports follow a Classic* / Home* prefix convention so the target API is obvious at the call site. The ./classic and ./home subpaths re-export everything with the prefix stripped — a modern alternative to TypeScript's deprecated namespace keyword.

// From the root — prefixed names, works in mixed Classic/Home contexts
import { ClassicAPI, HomeAPI, type ClassicGetDeviceData } from '@olivierzal/melcloud-api'

// From the Classic-scoped subpath — short names, no ambiguity
import { API, FanSpeed, type GetDeviceData } from '@olivierzal/melcloud-api/classic'

// From the Home-scoped subpath — short names
import { API, type AtaValues, type DeviceCapabilities } from '@olivierzal/melcloud-api/home'

// Namespace-like usage (recommended for mixed Classic + Home code)
import type * as Classic from '@olivierzal/melcloud-api/classic'
import type * as Home from '@olivierzal/melcloud-api/home'
const a: Classic.GetDeviceData<0> = ...
const b: Home.AtaValues = ...

// Shared enum-like constants (e.g. CLASSIC_FLAG_UNCHANGED, HomeDeviceType)
import { ClassicDeviceType, HomeDeviceType } from '@olivierzal/melcloud-api/constants'

Available subpaths: /classic, /home, /constants.

Documentation

Full API reference at https://olivierzal.github.io/melcloud-api/.

Contributing

See CONTRIBUTING.md.

Security

For vulnerability reports, see SECURITY.md.

Changelog

See CHANGELOG.md.

Caution

This API is not endorsed, verified or approved by Mitsubishi Electric Corporation. Mitsubishi cannot be held liable for any claims or damages that may occur when using this client to control MELCloud devices.

License

MIT © Olivier Zalmanski

About

MELCloud API for Node.js

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors