Authentication
Here is the part that surprises people: you can send events to Umami all day without logging in. The ingestion endpoint is public, because a browser tracker on a public web page cannot keep a secret. Credentials only enter the picture when you want to read data back out.
When you do, there are two doors and you walk through exactly one of them. A self-hosted instance takes a username and password and hands you a token. Umami Cloud takes an API key. The package routes requests and sets headers differently behind each door, so the choice matters even though the rest of your code stays identical.
What needs a credential
| Call | Needs | Why |
|---|---|---|
| new_event(), new_page_view(), new_revenue_event() | set_url_base() or a Cloud key | Umami’s /api/send endpoint is unauthenticated. |
| heartbeat() | set_url_base() or a Cloud key | Self-hosted pings /api/heartbeat. Cloud calls /me with the key. |
| websites(), website_stats(), active_users() | login() or a Cloud key | These read data and require a token or key. |
| verify_token() | login() or a Cloud key | Returns False rather than raising when nothing is set. |
Call a query function before you have a credential and you get umami.errors.OperationNotAllowedError with the message Call login() or set_cloud_api_key() before proceeding. No request is made; the package checks before it dials.
Self-hosted: set_url_base and login
import umami
umami.set_url_base('https://umami.example.com')
login = umami.login('admin', 'super-secret')
print(login.user.username, login.user.isAdmin)login() posts to /api/auth/login, stores the returned token in the module, and returns a LoginResponse with the token and a User model. You can drop the return value on the floor. Every later query call sends the stored token as Authorization: Bearer <token> without you touching it.
Both arguments must be non-empty, and whitespace does not count, or ValidationError is raised before any request. Wrong credentials come back from the server as a 401 and raise httpx2.HTTPStatusError.
Calling login() again replaces the stored token. There is no logout function; the token lives until the process exits or you log in again.
Tokens expire
Umami login tokens are temporary, and they expire on Umami’s schedule, not yours. A long-running process should assume that one day a query call will start failing with a 401, probably during a demo. verify_token() asks the server whether the stored token is still good and never raises:
if not umami.verify_token():
umami.login('admin', 'super-secret')verify_token(check_server=False) skips the network and only reports whether a credential is present, which is the same answer is_logged_in() gives.
Umami Cloud: set_cloud_api_key
import umami
umami.set_cloud_api_key('your-cloud-api-key', region='eu') # region is optional
umami.set_website_id('978435e2-7ba1-4337-9860-ec31ece2db60')
umami.set_hostname('example.com')Create the key in the Umami Cloud dashboard under the API keys settings. region accepts 'us', 'eu', or None; when omitted, Cloud uses the region of the account that owns the key. Any other value raises ValidationError, as does a blank key.
Setting a key flips three switches at once:
- Query and verification calls go to
https://api.umami.is/v1, with/euor/usinserted afterv1when you pin a region, and carry anx-umami-api-keyheader. - Events go to
https://cloud.umami.is/api/sendwith no credential, because the Cloud ingestion endpoint is public too. The region does not affect this host. - login() and login_async() raise OperationNotAllowedError. The key is the credential, and Cloud has no username and password login to call.
You do not call set_url_base() in Cloud mode. If you do, the value is stored and politely ignored while the key is set.
Switching modes
clear_cloud_api_key() removes the key and the region, and everything reverts to self-hosted behaviour: events use url_base, and query calls need a login token. A token from a login() made before you switched to Cloud is still sitting there, so you do not have to log in again.
is_logged_in() returns True when either a login token or a Cloud key is set. It does not contact the server; use verify_token() for that.
Keep credentials out of code
Read the password or key from an environment variable or a secrets store at startup, then hand it to login() or set_cloud_api_key(). The package does not read any environment variables itself, so there is no magic name to discover and no surprise when a variable is set on the wrong machine.
import os
import umami
umami.set_url_base(os.environ['UMAMI_URL'])
umami.login(os.environ['UMAMI_USER'], os.environ['UMAMI_PASSWORD'])If a login fails, Troubleshooting lists the causes.