Skip to main content

Async Python client for the CatGenie AI smart litter box API by PetNovations

Project description

catgenie-api

A reverse-engineered async Python library for the CatGenie AI smart litter box by PetNovations. Provides full authentication, device status, and cleaning cycle control — designed as the foundation for a Home Assistant integration.

Features

  • Phone + SMS authentication — full login flow using the same OTP mechanism as the official app
  • Automatic token refresh — access tokens (~30 min) are transparently refreshed using the long-lived refresh token (~10 years)
  • Device status & control — start/stop cleaning cycles, read configuration, operation state, and sani-solution level
  • Fully typed Pydantic v2 models — every API response is validated into typed Python objects; no raw dicts
  • PEP 561 compliant — ships py.typed, zero mypy errors with strict settings
  • Home Assistant ready — async context managers, shared session injection, and a clean model hierarchy that maps directly to HA entities

How It Works

The CatGenie app communicates with iot.petnovations.com (CloudFront-fronted AWS API Gateway). Every request requires three custom headers derived from an AES-CBC encrypted timestamp and HMAC-SHA256 signatures. The phone number itself is AES-encrypted in the auth request body.

There is one significant non-obvious requirement: PetNovations' edge silently discards requests from Python's standard SSL stack, returning 200 OK with an empty body instead of sending the SMS or returning data. This library uses curl_cffi to impersonate a Chrome Android TLS fingerprint, which is the only way to reliably trigger the SMS and receive API responses.

Installation

pip install catgenie

Or for development:

git clone https://github.com/kclif9/catgenieapi
cd catgenie-api
pip install -e ".[dev]"

Usage

Authentication

import asyncio
from catgenie import CatGenieAuth, CatGenieClient

async def main():
    async with CatGenieAuth() as auth:
        # Step 1 — trigger SMS to the phone number
        await auth.request_login_code(country_code=61, phone="499999999")

        # Step 2 — exchange the SMS code for tokens
        code = input("Enter SMS code: ")
        credentials = await auth.login(country_code=61, phone="499999999", code=code)

    # Step 3 — use the client
    async with CatGenieClient(credentials) as client:
        devices = await client.get_devices()
        for device in devices:
            print(f"{device.name}: {'online' if device.is_online else 'offline'}")
            print(f"  Sani-solution remaining: {device.remaining_sani_solution}%")
            print(f"  Lifetime cycles: {device.configuration.total_cycles}")

asyncio.run(main())

Controlling a Device

async with CatGenieClient(credentials) as client:
    devices = await client.get_devices()
    device = devices[0]

    # Start a cleaning cycle
    await client.start_cleaning(device.manufacturer_id)

    # Stop a cleaning cycle
    await client.stop_cleaning(device.manufacturer_id)

Token Persistence

import json, dataclasses
from catgenie import Credentials

# Save after login
with open("credentials.json", "w") as f:
    json.dump(dataclasses.asdict(credentials), f)

# Restore on next run
with open("credentials.json") as f:
    credentials = Credentials(**json.load(f))

Shared Session (Home Assistant pattern)

from curl_cffi.requests import AsyncSession, Response
from catgenie import CatGenieAuth, CatGenieClient
from catgenie.const import TLS_IMPERSONATE

async with AsyncSession(impersonate=TLS_IMPERSONATE) as session:
    async with CatGenieAuth(session=session) as auth:
        credentials = await auth.login(...)

    async with CatGenieClient(credentials, session=session) as client:
        devices = await client.get_devices()

API Reference

CatGenieAuth

Method Description
get_base_url(country_code, phone) Preflight config call (mirrors app behaviour)
request_login_code(country_code, phone) Triggers SMS OTP to the phone number
login(country_code, phone, code) Exchanges OTP for Credentials
refresh() Refreshes the access token using the refresh token

CatGenieClient

Method Returns Description
get_devices() list[Device] All devices on the account
get_device_status(device_id) dict Raw operation status
start_cleaning(device_id) dict Start a cleaning cycle
stop_cleaning(device_id) dict Stop a cleaning cycle
get_account() dict User profile
get_pets() list[dict] Pet profiles
get_pet_statistics() dict Per-pet usage statistics
get_notifications() NotificationList Push notification history
get_notification_settings() dict Push notification preferences
get_firmware_info(manufacturer_id) dict Available firmware update
get_mainboard(manufacturer_id) dict Mainboard hardware info

Key Models

Model Notable properties
Device unique_id, name, is_online, is_cleaning, remaining_sani_solution, last_clean, fw_version
DeviceConfiguration total_cycles, mode (CleaningMode), cat_delay, schedule, binary_elements
OperationStatus is_cleaning, clean_progress_pct (0–100 while running, None when idle)
ActivationInfo count (lifetime cleaning cycles), date (first activation)
Notification / NotificationData parsed_data property auto-parses the nested JSON payload
Credentials access_token, refresh_token, is_token_expired

Project Structure

src/catgenie/
├── __init__.py     # Public API exports
├── py.typed        # PEP 561 marker
├── auth.py         # Phone + SMS authentication (CatGenieAuth, Credentials)
├── client.py       # High-level async client (CatGenieClient)
├── const.py        # Constants and all endpoint paths
├── models.py       # Pydantic v2 models for every API response
└── signing.py      # AES-CBC encryption + HMAC-SHA256 request signing

Prior Art & Credits

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

catgenie-0.1.5.tar.gz (32.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

catgenie-0.1.5-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

File details

Details for the file catgenie-0.1.5.tar.gz.

File metadata

  • Download URL: catgenie-0.1.5.tar.gz
  • Upload date:
  • Size: 32.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for catgenie-0.1.5.tar.gz
Algorithm Hash digest
SHA256 54bb265553cd652b3ab5266c2b8c5bcc2795414c469956c7ccd99a7acf21a5d5
MD5 216198a0efe7da383bf56ad958fe611b
BLAKE2b-256 ac1a203398759822c0f9278283f53030c8dc10b0a24a8011523c8cadb29aed74

See more details on using hashes here.

Provenance

The following attestation bundles were made for catgenie-0.1.5.tar.gz:

Publisher: publish.yml on kclif9/catgenie-api

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file catgenie-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: catgenie-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 20.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for catgenie-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 0cd873af3aed4e32ed2c1112d51bfd01611893a7d895e7be167ba78e8f56dd7c
MD5 ab183aa2f87ac0cbd973f376ebe2020e
BLAKE2b-256 271d434741567f45b3ecac9a444ab1acaf0a34786927b2ebbabeb292de9d679e

See more details on using hashes here.

Provenance

The following attestation bundles were made for catgenie-0.1.5-py3-none-any.whl:

Publisher: publish.yml on kclif9/catgenie-api

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page