nodepetkit
Node.js / TypeScript client for PetKit smart pet devices over the PetKit cloud (REST).
Spiritual successor to pypetkitapi — same wire protocol, same cloud endpoints, TypeScript-native. If you can control your device in the PetKit mobile app, you can control it with this library.
Node only. Uses crypto and
fetchprimitives available in Node ≥ 20. It does not run in browsers.
Install
npm i @apocaliss92/nodepetkit
Requires Node 20 or later.
Quick start
import { Nodepetkit, FeederDevice } from '@apocaliss92/nodepetkit';
const petkit = new Nodepetkit(
{ email: '[email protected]', password: 'secret' },
{ region: 'us' }, // region name or ISO id — resolved to a gateway at login
);
// 1. Authenticate (resolves your region gateway, then logs in)
await petkit.login();
// 2. Discover devices registered to your account
const devices = await petkit.discoverDevices();
// 3. Create a typed device handle (fetches its initial state)
const feeder = (await petkit.createDevice(devices[0])) as FeederDevice;
// 4. React to polled state changes
feeder.on('stateChanged', ({ changed }) => console.log('changed:', changed));
// 5. Send commands
await feeder.feed(20); // dispense 20g now
await feeder.setChildLock(true);
// 6. Poll again whenever you like (PetKit has no realtime push)
await feeder.refresh();
Token-based authentication
If you already have a session token:
const petkit = new Nodepetkit({ token: 'your-session-id', region: 'us' });
await petkit.login(); // resolves the gateway; pass `gateway` too to skip that call
⚠️ PetKit only allows one active session per account. Logging in here can log out your phone app (and vice-versa). Consider a dedicated shared "family member" account, as recommended by the Home Assistant PetKit community.
Supported devices
| Device class | Series | Status |
|---|---|---|
FeederDevice | Fresh Element / Mini / D3 / D4 / D4S (Gemini) / D4H / D4SH | Core commands |
LitterDevice | Pura X (T3), Pura MAX (T4), T5 / T6 / T7 | Core commands |
PurifierDevice | K2 / K3 deodorizers | Core commands |
FountainDevice | Eversweet W4 / W5 / CTW2 / CTW3 | State + settings; BLE control @experimental |
UnknownDevice | Any unrecognised type | Fallback (raw state + passthrough commands) |
State field names and command payloads are transcribed from pypetkitapi. They are
not yet hardware-verified in this port — open an issue if a field or command is wrong.
How it works
- Region —
login()callsGET v1/regionserversonpassport.petkt.comto map your region to a gateway (api.eu-pet.com,api.petkt.com,api.petktasia.com, …). - Auth — it POSTs
user/loginwith an MD5-hashed password; the returned session id is sent asX-Session/F-Sessionon every later call. - Discovery —
discoverDevices()readsgroup/family/list(plus the shared-device roster). - State —
createDevice()POSTs{deviceType}/device_detail(ordeviceData) and stores it. - Commands —
updateSettingsfor persistent options,controlDevicefor transient actions, and per-device endpoints (saveDailyFeed,deodorantReset, …) for the rest.
PetKit's classic REST API has no realtime push channel, so state is refreshed by polling
(device.refresh()); there is no WebSocket to keep open.
All internals (PetkitHttp, CloudDeviceGateway, login, crypto helpers) are kept out of
the public surface to preserve API stability.
API reference
Nodepetkit
new Nodepetkit(
creds: { email: string; password: string } | { token: string; region: string; gateway?: string },
opts?: { region?: string; timezone?: string },
)
| Method | Returns | Description |
|---|---|---|
login() | Promise<void> | Resolve the region gateway and authenticate |
discoverDevices() | Promise<PetkitDeviceInfo[]> | List devices on the account |
createDevice(info) | Promise<BaseDevice> | Build a typed handle and fetch its state |
getSession() | PetkitSession | null | The active token + resolved gateway |
BaseDevice
All device classes extend BaseDevice, a typed event emitter.
| Member | Type | Description |
|---|---|---|
id | string | PetKit device id |
deviceType | string | Raw type (d4s, t4, w5, …) |
type | DeviceType | Family: feeder / litter / fountain / purifier / unknown |
name | string | Friendly name |
online | boolean | Best-effort online flag |
state | Readonly<DeviceState> | Latest polled detail |
refresh() | Promise<void> | Poll the cloud and merge state |
event stateChanged | { changed } | Emitted when polled state changes |
FeederDevice
food · food1 · food2 · batteryPower · desiccantLeftDays · feeding · errorCode
· feed(grams | { hopper1, hopper2 }) · cancelFeed() · resetDesiccant()
· markFoodReplenished() · callPet() (D3) · playSound(id) (D3/D4H/D4SH)
· setChildLock() · setIndicatorLight() · setFeedSound() · setVolume() · setSetting(kv)
LitterDevice
boxFull · sandPercent · sandLack · liquidLack · deodorantLeftDays · errorCode · battery · usedTimes
· startCleaning() · dumpLitter() · removeOdor() · levelLitter() · resetN60Deodorizer()
· resetN50Deodorant() · setLight() · setMaintenance() · setPower() · stop(code)
· setAutoClean() · setChildLock() · setSandType(1|2|3) · setAutoInterval(seconds) · setSetting(kv)
PurifierDevice
mode · power · liquid · liquidLack · humidity · temperature
· setPower() · setMode(PUR_MODE) · setAutoWork() · setChildLock() · setSetting(kv)
FountainDevice
mode · powered · running · filterPercent · filterWarning · lackWarning · batteryPercent
· cloud: setLightRing() · setLightBrightness() · setDoNotDisturb() · setSetting(kv)
· BLE (@experimental): powerOn() · powerOff() · pause() · resume() · setNormalMode() · setSmartMode() · resetFilter()
Fountains have no direct cloud control — commands are relayed over BLE through a nearby PetKit device, so BLE methods only work when such a relay is on your account/network.
Diagnostics dumper
import { createDumper } from '@apocaliss92/nodepetkit';
const snapshot = createDumper(feeder, { redact: true }).dump();
// { id: '******1234', deviceType: 'd4', type: 'feeder', name: '***chen', state: {…} }
redact: true (default) masks the device id and name so dumps can be shared safely.
Error types
| Class | Thrown when |
|---|---|
PetkitError | Base class for all library errors |
PetkitAuthError | Login / region-resolution / token failure |
PetkitApiError | PetKit application error (.code) or non-2xx HTTP |
PetkitTransportError | Network error reaching the REST API |
PetkitDeviceOfflineError | Reserved — not yet thrown |
Credits & scope
Reverse-engineered from pypetkitapi (the library
behind the Jezza34000 Home Assistant PetKit integration).
See docs/petkit-cloud-api-spec.md for the full protocol
notes this port is built from. Not affiliated with or endorsed by PetKit.
License
MIT — see LICENSE.