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.4.tar.gz (32.7 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.4-py3-none-any.whl (20.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: catgenie-0.1.4.tar.gz
  • Upload date:
  • Size: 32.7 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.4.tar.gz
Algorithm Hash digest
SHA256 b81fdb28c9e0c6a6490d0f2a3d8a9dda5e825478c2b19cb0a9fa44d0eb62109d
MD5 19c0cf0dfcc35de1eadd5a4687ddeb5f
BLAKE2b-256 b7470555f275d04925cab11a6cfc03cfcb9d05890800324857f34e20e3973a28

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: catgenie-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 20.8 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.4-py3-none-any.whl
Algorithm Hash digest
SHA256 111484ef007fa799db132bee05915d12650f79e8fd0a535838802dc0488ca8ae
MD5 794143e9f9d6d632857d2ce571ea8238
BLAKE2b-256 ecc6d08f2c8da5553a7124ca829f3182c8566965cc32a172e54b47a7ecf8bd71

See more details on using hashes here.

Provenance

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