Keys & domains
Embeds run in your visitors’ browsers, so their API key is visible to anyone who views source. That is expected and safe — provided you use a publishable key and restrict it to your domains.
Never put a secret key in an embed. Secret keys carry full permissions and are meant for servers you control. If you have pasted one into a web page, revoke it in the dashboard now and create a publishable key instead.
Creating the key
In the Ullr dashboard: Integrations → API keys → Create key.
- Type: Publishable
- Scopes: see the table below
- Allowed domains: your site’s hostnames
You can also create a key from any embed’s configurator page, which pre-selects
the “All embeds” preset — trails:read, lifts:read, features:read,
resorts:read and updates:read. That preset deliberately contains live-status
scopes only: no history, no snapshots.
Publishable keys remain visible in the dashboard after creation, so you can re-copy a snippet any time.
Scopes each embed needs
| Embed | Scopes |
|---|---|
| Trail list | trails:read |
| Lift list | lifts:read |
| Interactive map | resorts:read + one per layer you turn on: trails:read, lifts:read, features:read |
| Conditions widget | resorts:read, trails:read, features:read, updates:read, plus lifts:read when showLifts is on |
The two list embeds need only their own scope — each reads a single endpoint and
never asks for the area itself. The map and the widget both load the area, so
they also need resorts:read.
One key can serve every embed on your site. Give it the union of what those embeds need and nothing more.
Leave the history scopes (trails:readHistory, lifts:readHistory,
features:readHistory) and the snapshot and summary scopes off an embed key
unless you are deliberately using the timestamp attribute. They expose your
operational record over time to anyone who copies the key out of your page —
a live-only key leaks today’s status, a history key leaks the whole season.
The one case that needs them is a historical view: setting timestamp on an
embed switches it onto the history endpoints, so that key needs
readHistory for each resource the embed reads instead of the live scope.
Give that its own key, restricted to the page that uses it, rather than
widening the key serving your public status boards.
Restricting domains
An unrestricted publishable key works from anywhere — including someone else’s website, on your data and your quota. Set the allowed domains.
Enter bare hostnames, one per entry:
example.com
*.example.com- No scheme, port or path.
https://example.com/is invalid; useexample.com. - One leading wildcard label is allowed.
*.example.commatcheswww.example.comandconditions.staging.example.com, but not the bareexample.com. List both when you need both. - Localhost is always allowed —
localhost,127.0.0.1and[::1]on any port — so local development needs no extra entry. - An empty list means no restriction. Fine while prototyping; fix it before you ship.
Matching is against the browser’s Origin header. A request with a restricted
key and no Origin at all — plain curl, most server-side code — is denied.
That is deliberate: publishable keys are for browsers.
Rotating a key
- Create the new key with the same scopes and domains.
- Update your pages to the new key and deploy.
- Watch Last used on the old key in the dashboard — it stops advancing once nothing is using it. (It updates at most once an hour, so give it time.)
- Delete the old key.
If a key has leaked and you need it dead immediately, revoke it — that takes effect at once, and you can create the replacement afterwards.
What a rejected embed looks like
An embed whose key is refused renders nothing rather than an error box, so check the browser’s network tab:
| Status | Cause |
|---|---|
401 | Key missing, misspelled, revoked, or expired |
403 | Key lacks the scope for that data, or your domain is not on its allowed list |
Troubleshooting walks through each case.