Skip to main content

Querying stats

Sending events is the daily work. Reading them back is for the Monday morning question: how did the weekend go? Three functions answer it. websites() tells you what Umami is tracking, website_stats() summarises a date range, and active_users() counts who is on the site this minute. All three need a login token or a Cloud API key, all three raise httpx2.HTTPStatusError on a non-2xx response, and each has an _async twin.

Your websites

for site in umami.websites():
    print(site.id, site.name, site.domain)

websites() returns a list of Website models, and site.id is the value you hand to set_website_id(). Umami pages this endpoint, and for a long time the package quietly returned only the first page, which was fine until the first account with more websites than fit on one. Since 1.1.0 it asks for page after page until it holds the total the server reports, and returns the complete list. One request is still enough for most accounts.

user is set for websites you own, and createUser is set instead for team websites. Both are WebsiteUser models with id and username, and either can be None.

Stats for a date range

from datetime import datetime, timedelta

end = datetime.now()
start = end - timedelta(days=7)

stats = umami.website_stats(start_at=start, end_at=end)
print(stats.pageviews, stats.visitors, stats.visits, stats.bounces, stats.totaltime)

website_stats() returns a WebsiteStats model with page views, unique visitors, visits (sessions), bounces (single-page sessions), and total engagement time in seconds. When Umami includes a comparison block for the preceding period, it is available as stats.comparison with the same five fields, so “up on last week” is a subtraction away. Otherwise it is None.

Dates

start_at and end_at are datetime objects. The package converts them to epoch milliseconds with timestamp(), and here is the trap: a naive datetime is interpreted in the local timezone of the process. Your laptop in Portland and your server in Frankfurt will ask Umami about different seven-day windows from the same code. Pass timezone-aware datetimes and the ambiguity disappears.

Filters

Every optional keyword narrows the stats to matching traffic:

stats = umami.website_stats(start_at=start, end_at=end, url='/pricing', country='US', device='Mobile')

The filters are url, referrer, title, query, event, host, os, browser, device, country, region, and city. Two of them are renamed on the wire: url is sent as path and host is sent as hostname, which is what current Umami expects. Filters you leave as None are not sent.

Active visitors right now

count = umami.active_users()

active_users() returns an int: the number of visitors active on the website in the last few minutes, as Umami defines it. It is the number you keep refreshing during a launch. Pass website_id to query a site other than the default.

Overriding the default website

Every query function accepts website_id. Pass it when you want numbers for a site other than the one set with set_website_id():

stats = umami.website_stats(start_at=start, end_at=end, website_id=other_site.id)

Umami Cloud limits

Umami documents a rate limit of 50 calls per 15 seconds per API key on Cloud. The package does not throttle or retry, so a loop that polls active_users() for forty websites every second meets a 429 in under two seconds. Pace it yourself.

Beyond the three queries

The package stops at websites, summary stats, and active users. Reports, sessions, event data, time series, and realtime data are all in the Umami HTTP API, and the credential you configured here works for them too. How it works shows the call.

Errors on query calls are covered in Troubleshooting.