# Changelog
This changelog is generated automatically from [GitHub Releases](https://github.com/mikeckennedy/umami-python/releases).
# v1.0.0
*2026-06-11* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v1.0.0)
## \[1.0.0\]
First stable release. The package is now marked `Development Status :: 5 - Production/Stable` (it has been used in production at Talk Python Training for some time) and follows [SemVer](https://semver.org/) from here forward.
### Changed
- Promoted the trove classifier from `4 - Beta` to `5 - Production/Stable`, and added the `Typing :: Typed` classifier to advertise the bundled `py.typed` marker.
- Expanded the PyPI keyword set (added web-analytics, privacy, tracking, metrics, events, page-views, telemetry, statistics, self-hosted, async, httpx, pydantic, sdk, api-client) for better discoverability.
### Added
- `pydantic.Field(description=...)` metadata on every response-model field, plus a `griffe-pydantic` dev dependency, enriching the response models for IDEs, JSON schema, and future API-reference rendering. Note: the current Great Docs version (0.13.0) does not load griffe extensions, so the rendered field docs still come from the docstring `Attributes:` sections; the Field descriptions will surface on the docs site once the generator loads the extension.
### Changed
- Synchronous send functions now return the Umami API's JSON response as a dict (and `{}` when tracking is disabled), matching their async twins. Previously [new_event()](reference/new_event.html#umami.new_event), [new_revenue_event()](reference/new_revenue_event.html#umami.new_revenue_event), and [new_page_view()](reference/new_page_view.html#umami.new_page_view) returned `None`; [new_page_view_async()](reference/new_page_view_async.html#umami.new_page_view_async) also now returns the response dict instead of `None`.
- Missing or blank required event fields (hostname, website_id, event_name) now raise [umami.errors.ValidationError](reference/errors.ValidationError.html#umami.errors.ValidationError) instead of a bare `Exception`, so they can be caught alongside the SDK's other validation errors.
- `Website.shareId`, `Website.resetAt`, and `Website.deletedAt` are now optional (default `None`), so a websites response that omits those keys no longer raises `pydantic.ValidationError`.
- README now points to the new documentation site at https://mkennedy.codes/docs/umami-python/ (added status badges, a documentation pointer near the top, and a "Documentation" section linking the full API reference). Docs-only change -- no code or packaging changes.
ass="gdls-link gdls-code">new_page_view wording (visitor; the `ua` user-agent string); clarified that [disable()](reference/disable.html#umami.disable) still validates before skipping the HTTP request and that both affect only the send functions; documented all response models and exception classes; added a package module docstring; and added runnable usage examples. Docstring-only -- no code or behavior changes.
### Fixed
- `validate_event_data` no longer lets a whitespace-only `event_name` through (the check used `and`, so whitespace bypassed it; it now uses `or`).
# v0.6.25
*2026-06-07* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.6.25)
## \[0.6.25\] - 2026-06-07
### Added
- `distinct_id` support in [new_event](reference/new_event.html#umami.new_event), [new_page_view](reference/new_page_view.html#umami.new_page_view), and [new_revenue_event](reference/new_revenue_event.html#umami.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()](reference/clear_cloud_api_key.html#umami.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()](reference/set_url_base.html#umami.set_url_base) or [login()](reference/login.html#umami.login) required. Existing self-hosted ([login()](reference/login.html#umami.login) + token) usage is byte-for-byte unchanged. Added `urls.me` for Cloud key validation in [verify_token](reference/verify_token.html#umami.verify_token)/[verify_token_async](reference/verify_token_async.html#umami.verify_token_async). In Cloud mode, [heartbeat()](reference/heartbeat.html#umami.heartbeat) / [heartbeat_async()](reference/heartbeat_async.html#umami.heartbeat_async) check liveness via the authenticated `/me` endpoint (Cloud has no `/api/heartbeat`), and [login()](reference/login.html#umami.login) / [login_async()](reference/login_async.html#umami.login_async) raise [OperationNotAllowedError](reference/errors.OperationNotAllowedError.html#umami.errors.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()](reference/set_cloud_api_key.html#umami.set_cloud_api_key) alongside [set_url_base()](reference/set_url_base.html#umami.set_url_base) / [login()](reference/login.html#umami.login). Same exception type ([OperationNotAllowedError](reference/errors.OperationNotAllowedError.html#umami.errors.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](reference/models.Website.html#umami.models.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](reference/set_url_base.html#umami.set_url_base) validation, the `validate_state` guards, the [login](reference/login.html#umami.login)/[login_async](reference/login_async.html#umami.login_async) happy path and response-model deserialization ([LoginResponse](reference/models.LoginResponse.html#umami.models.LoginResponse), [WebsitesResponse](reference/models.WebsitesResponse.html#umami.models.WebsitesResponse)), [websites](reference/websites.html#umami.websites)/[websites_async](reference/websites_async.html#umami.websites_async), the [disable()](reference/disable.html#umami.disable) no-op (asserting no HTTP request is made), `ip_address` payload inclusion/omission, explicit-arg-overrides-defaults precedence, [heartbeat_async](reference/heartbeat_async.html#umami.heartbeat_async) self-hosted, and the [verify_token](reference/verify_token.html#umami.verify_token)/[heartbeat](reference/heartbeat.html#umami.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()](reference/website_stats_async.html#umami.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()](reference/website_stats.html#umami.website_stats). ([\#18](https://github.com/mikeckennedy/umami-python/issues/18))
- [active_users()](reference/active_users.html#umami.active_users) and [active_users_async()](reference/active_users_async.html#umami.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](https://github.com/mikeckennedy/umami-python/issues/19))
- [website_stats()](reference/website_stats.html#umami.website_stats) / [website_stats_async()](reference/website_stats_async.html#umami.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](https://github.com/mikeckennedy/umami-python/issues/20))
- [heartbeat()](reference/heartbeat.html#umami.heartbeat) / [heartbeat_async()](reference/heartbeat_async.html#umami.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()](reference/new_event.html#umami.new_event) and [new_revenue_event()](reference/new_revenue_event.html#umami.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.
# v0.5.24
*2026-06-04* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.5.24)
## \[0.5.24\] - 2026-06-04
### Changed
- Migrated HTTP backend from `httpx` to `httpx2`, imported as `import httpx2 as httpx` so all call sites remain unchanged
- Files: `umami/umami/impl/__init__.py`, `umami/requirements.txt`, `umami/pyproject.toml`, `pyrefly.toml`, `README.md`, `umami/README.md`
# v0.4.23
*2026-03-13* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.4.23)
## \[0.4.23\]
### Added
- [new_revenue_event()](reference/new_revenue_event.html#umami.new_revenue_event) and [new_revenue_event_async()](reference/new_revenue_event_async.html#umami.new_revenue_event_async) for tracking revenue with Umami's new revenue feature
- Revenue events automatically inject `revenue` (float) and `currency` (ISO 4217 string) into event custom data
- Input validation: revenue must be a number \>= 0, currency must be a non-empty string
- Defaults to `event_name='revenue'` and `currency='USD'` for simple usage
- 15 new tests covering sync/async revenue events, validation, and edge cases
# v0.3.22
*2026-02-03* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.3.22)
## \[0.3.22\] - 2026-02-02
### Fixed
- [new_event_async](reference/new_event_async.html#umami.new_event_async) crashed with `binascii.Error` due to erroneous `base64.b64decode` / `json.loads` calls on the plain-JSON API response; now returns `resp.json()` directly, matching the other event and page-view methods
# v0.3.21
*2025-12-12* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.3.21)
### Added
- Change-log preservation section for tracking historical release details
- Pyrefly configuration file treating `umami/umami` package as type-resolution root
### Changed
- Centralized all version information into a single location for easier maintenance
- Updated Python requirement to 3.10+ (dropped support for Python 3.8 and 3.9)
- Refactored [WebsiteStats](reference/models.WebsiteStats.html#umami.models.WebsiteStats) to use plain integers instead of custom `Metric` type - Thanks [@rubitcat](https://github.com/rubitcat) ([\#15](https://github.com/mikeckennedy/umami-python/issues/15))
- Updated `ruff.toml` to enforce consistent import ordering
### Fixed
- Suppressed and cleaned up unnecessary type-checking warnings
# v0.2.20
*2025-05-31* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.2.20)
Umami-Python Release Notes
**Since 5fa35183 (7 commits)**
## 🚀 New Features
- **Development and Testing Tracking**: Added options to disable tracking for development and testing environments. This allows users to control data collection based on their needs.
When tracking is disabled:
- ✅ **No HTTP requests** are made to your Umami server
- ✅ **API calls still validate** parameters (helps catch configuration issues)
- ✅ **All other functions work normally** (login, websites, stats, etc.)
- ✅ **Functions return appropriate values** for compatibility
## 📚 Documentation
- Documented the heartbeat check feature for monitoring system health.
- Added documentation for new methods in the Umami API: `get_heartbeat` and `enabled/disable`.
## 🧹 Maintenance
- Sorted some imports in various files to improve code organization and readability.
- Fixed a minor typo in the documentation, ensuring consistency across the project.
# v0.2.19
*2025-05-31* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.2.19)
Umami-Python Release Notes
**Since 1d17c014 (16 commits)**
## 🚀 New Features
- **Heartbeat Functions**: Added asynchronous ([heartbeat_async](reference/heartbeat_async.html#umami.heartbeat_async)) and synchronous ([heartbeat](reference/heartbeat.html#umami.heartbeat)) versions of the existing heartbeat function for improved import flexibility.
- Added website stats and active users functions ([website_stats](reference/website_stats.html#umami.website_stats), [active_users](reference/active_users.html#umami.active_users)) Thanks [@orangethewell](https://github.com/orangethewell)
## 🐛 Bug Fixes
- **Login Fix**: Corrected the API data handling from `data` to `json` in the login process. Originally, this was not a problem with the server, but it has started failing. If you have encountered login errors, this may fix it.
## ⚡ Performance
- **Heartbeat Function Optimization**: Improved the performance of the heartbeat function by optimizing its execution flow.
# v0.2.18
*2025-05-31* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.2.18)
Umami-Python Release Notes
**Since 3a08f77a (2 commits)**
## 🚀 New Features
- **Python Version Update**: Upgraded to Python 3.13 and 3.14, providing better support for modern applications.
## 🐛 Bug Fixes
- **API Call Method Fix**: Corrected the use of `data` instead of `json` in login API calls, ensuring proper data transmission.
Originally, this was not a problem with the server, but it has started failing. If you have encountered login errors, this may fix it.
# v0.2.17
*2024-04-19* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.2.17)
Adds `is_logged_in() -> bool` method to check whether login has been called and was successful.
# v0.2.15
*2024-03-01* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.2.15)
- Fixes schema change from Umami 2.9 to 2.10 on websites response
- Includes new heartbeat function from 0.1.14 release
# v0.1.14
*2024-03-01* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.1.14)
Add heartbeat API endpoint.
# v0.1.13
*2024-02-13* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.1.13)
Support IP address in new_event and new_page_view payloads, see issue [\#2](https://github.com/mikeckennedy/umami-python/issues/2).
It looks like Umami added the IP data field in the payload (see https://github.com/umami-software/umami/pull/2479 ). It's not clear when the feature will be released in a new build ( https://github.com/umami-software/umami/releases ). But in preparation, I added the id_address parameter to both new_event and new_page_view as well as their async twins.
This is out in version 0.1.13 and on PyPI at https://pypi.org/project/umami-analytics/0.1.13/ Thanks for the idea [@ddxv](https://github.com/ddxv)
# v0.1.12
*2024-01-27* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.1.12)
Add ability to pass alternative user agent to events (but be careful, umami blocks what it perceives as bots).
# v0.1.11
*2024-01-24* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.1.11)
- Added method: [new_page_view()](reference/new_page_view.html#umami.new_page_view) and [new_page_view_async()](reference/new_page_view_async.html#umami.new_page_view_async).
- Dropped auth requirement for new event, it's not needed (see [\#1](https://github.com/mikeckennedy/umami-python/issues/1) )
# v0.1.10
*2024-01-22* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.1.10)
- Added platform type (e.g. Windows) to user agent.
- Added doc strings to most methods.
- Bump the version for next release.
- Added better validation for some functions.
- Added custom error types for validation.
# v0.1.9
*2024-01-21* · [GitHub](https://github.com/mikeckennedy/umami-python/releases/tag/v0.1.9)
First real release outside our own apps. Enjoy!