You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
distinct_id support in new_event, new_page_view, and new_revenue_event payloads
(sync and async), sent to Umami as payload field id
Umami Cloud API-key authentication via set_cloud_api_key(key, region=None) and clear_cloud_api_key(). In Cloud mode, data/management calls route to https://api.umami.is/v1[/region] with an x-umami-api-key header, and events are sent to https://cloud.umami.is/api/send — no set_url_base() or login() required. Existing
self-hosted (login() + token) usage is byte-for-byte unchanged. Added urls.me for Cloud
key validation in verify_token/verify_token_async. In Cloud mode, heartbeat() / heartbeat_async() check liveness via the authenticated /me endpoint (Cloud has no /api/heartbeat), and login() / login_async() raise OperationNotAllowedError to fail
fast (the API key is the credential — there is no username/password login on Cloud).
Changed
validate_state error messages now mention set_cloud_api_key() alongside set_url_base() / login(). Same exception type (OperationNotAllowedError) and trigger conditions; text only.
Response models are more tolerant of variant/partial responses: WebsiteStats.comparison and Website.user are now optional, and Website accepts createUser (returned by team-website
listings, where userId is null). Successful responses today are unaffected.
Internal: expanded the unit test suite (now 128 tests) following a suite review — added
behavioral coverage for set_url_base validation, the validate_state guards, the login/login_async happy path and response-model deserialization (LoginResponse, WebsitesResponse), websites/websites_async, the disable() no-op (asserting no HTTP
request is made), ip_address payload inclusion/omission, explicit-arg-overrides-defaults
precedence, heartbeat_async self-hosted, and the verify_token/heartbeat failure branches.
Added a sync/async parity harness and consolidated the per-file HTTP mock builders into a shared tests/_mocks.py (net ~210 fewer lines). No production behavior changed.
Fixed
website_stats_async() sent its date range as snake_case start_at/end_at query
params, which the Umami API ignores — it silently returned all-time stats regardless
of the requested window. Now sends camelCase startAt/endAt, matching the sync website_stats(). (#18)
active_users() and active_users_async() read the active-visitor count from a
non-existent x key, so they always returned 0. They now read Umami's current visitors key (falling back to the legacy x key for compatibility). (#19)
website_stats() / website_stats_async() sent their url and host filters under
the old query-param names, which Umami renamed on 2025-10-07 (url → path, host → hostname) — so filtering stats by URL or hostname was a silent no-op. The
public url/host keyword arguments are unchanged; they now map to the current path/hostname wire names. (#20)
heartbeat() / heartbeat_async() issued a POST to /api/heartbeat, which current Umami
answers with 405 Method Not Allowed; the broad exception handler swallowed the error so the
call always returned False even against a healthy server. They now issue a GET (the endpoint
returns {"ok": true}).
new_event() and new_revenue_event() (sync) defaulted url to '/event-api-endpoint' while
their async twins used '/'. All four now default to '/', so the sync/async twins agree and no
placeholder path appears in dashboards.