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.2.tar.gz (29.6 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.2-py3-none-any.whl (19.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: catgenie-0.1.2.tar.gz
  • Upload date:
  • Size: 29.6 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.2.tar.gz
Algorithm Hash digest
SHA256 e4a39d035f5d41eb916e6f1cf8f1921af8ccd0a2b0df5e7975213876ec915145
MD5 69b045c784b44cc3a9629c6e24ad6c02
BLAKE2b-256 f0fafba85681d43ec07d2dcece367e6a370563866bf5c1f35cdd7362898ae8f7

See more details on using hashes here.

Provenance

The following attestation bundles were made for catgenie-0.1.2.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.2-py3-none-any.whl.

File metadata

  • Download URL: catgenie-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 19.0 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 5323ccb2847a46e393ef900c2b152b7a6fb6d5cf4ae0753ec71608541b04f2dc
MD5 136c55e84a38e524304dcb4806b47b99
BLAKE2b-256 d51f52ccc3dcefe8a25eb0c31140bf175e3b7255d80225e52763b24ed30cad5e

See more details on using hashes here.

Provenance

The following attestation bundles were made for catgenie-0.1.2-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