Skip to main content
Work down from what you are seeing.

The serving URL returns 404

By far the most common cause. Nothing is served until a first publish. A Theme with a draft and no published version has a serving URL that exists and answers 404.Fix: publish the Theme. See Versioning.
A Theme builds only the files its integrations require. A Theme whose only integration is Build-time tokens does not serve tokens.css at all.Fix: check the Theme’s Integration tab, which lists the URLs that actually exist. Add the pair you need and republish. See What gets served.
A Theme with an empty integration list serves nothing. Themes created before integrations existed are in that state.Fix: add a pair on the Integration tab, then publish.
/7/ only exists if version 7 exists. Versions start at 1 and never skip, but a Theme with 3 versions has no /7/.Fix: read servingUrls.version from the API, or use latest.
Sandbox and production are different ids and completely separate entities. A Theme created in sandbox does not exist in production, even with the same slug.Fix: check the {environmentID} segment.
Serving URLs are built from ids, not slugs. /acme/sandbox/… is a portal URL, not a serving URL.Fix: copy the URL from the Theme’s Integration tab.

The serving URL returns 403

A 403 never tells you which check refused — deliberately, because naming the failing check tells an attacker which header to forge. So work through all three:
A signed Theme answers 403 for a missing signature, a malformed one, an unknown kid, and a wrong MAC — one answer for all four.Check, in order:
  • Is the canonical string exactly path + "?kid=" + kid, with the path from the leading slash and no host?
  • Is the digest base64url with padding stripped, not standard base64?
  • Is the kid in the URL the same one you signed with?
  • Was the key deleted?
See Signed URLs for the worked examples.
The two cases that surprise people:
  • A <link rel=\"stylesheet\"> sends no Origin header unless you add crossorigin. The only signal is Referer, and a page with Referrer-Policy: no-referrer withholds that too — so a browser request with an allowlist set can be refused even from a listed origin.
  • A sandboxed iframe sends Origin: null, which is refused rather than treated as missing.
Fix: review the Theme’s missing-origin policy, or add crossorigin to the link. See Access control.
If the Theme lists IP ranges and your request’s address is not in one — or could not be determined at all — it is refused. Null never matches an allowlist.This is the usual cause when browsers are refused: an IP allowlist on a Theme fetched by end users refuses all of them, because their addresses are not knowable.

A publish is not showing up

The files are rendered asynchronously, after the publish returns. latest moves when materialisation finishes, not when the button goes green.
latest is cached for your Environment’s lifetime — 60 seconds by default — and served stale-while-revalidate for up to 5 minutes past that.To confirm a publish landed, poll the pinned URL for the new version number. It appears as soon as materialisation finishes and is never served stale. See Caching.
A pinned URL never changes. If your app has /7/ in it, publishing version 8 changes nothing for that app — which is exactly why you pinned it.Fix: bump the number, or move to latest.

A token is missing from the output

Silently ignored. A Variant cannot introduce a token — there is nothing for the override to apply to.Fix: add the token to the Theme, then override it. See Variants.
tokens.css is a lossy projection. A token CSS cannot express becomes a comment naming its path rather than being dropped silently.Fix: search the stylesheet for the path. If it is commented, read tokens.json instead.
Both are preserved but never applied. A document relying on either imports with tokens missing, and nothing warns you.Fix: replace them with aliases — {color.brand} — which are fully supported.
When a Theme publishes, every Variant is migrated: keys the Theme renamed are renamed, keys it removed or retyped past fitting are dropped.A rename expressed as remove-and-add rather than move has to be inferred, and the inference can lose an override when the rename also changed the value.Fix: re-set the override on the Variant, and use move for renames in future.

Publishing a Variant returns 409

A Variant can be published only once per Theme version. Its version number is the Theme version its overrides answer, so the second publish finds that file already written. Re-reading and retrying will not clear it. Publish the Theme first, then the Variant.

A Variant has unpublished changes nobody made

Expected after a Theme publish. The automatic migration writes each Variant’s draft and does not publish it. Review the draft and publish.

The API returns 403 and the person is a Team Admin

Correct behaviour. A Team role grants nothing inside an Environment. A Team Admin with no grant on that Environment is refused, by design. Fix: grant them the Environment. See Roles.

The API returns 401

The token is missing, expired, or minted for the wrong audience. The Public API accepts only https://api.livry.dev/public/v1, and a developer-portal token is refused. Note also that machine credentials are not issuable yet — the API is reachable today only with a user-backed token. See Authentication.

The API returns 402

A plan ceiling, not a rate limit. Creating a Variant past your Theme’s allowance. Retrying will not help. See Plans and limits.

Colours look wrong after switching brand

Your app is holding values rather than var() references. Swapping the stylesheet only rebrands what references custom properties. Check your theme configuration for literal hex values, and see Choosing an approach.
A library that computes shades in JavaScript — MUI, Vuetify, PrimeVue, PrimeNG — genuinely cannot take a var(), which is why those read tokens.values.json and must re-fetch on a brand change.

Still stuck

Which check refused a request is recorded in Livry’s telemetry even though it is not in the response. Send us the URL, the approximate time, and what you expected — support@livry.dev.