> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livry.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Caching: latest vs Pinned Versions

> How long the edge reuses an answer, why a pinned version is cached for a year, and how to choose between latest and a pinned URL in production.

Every serving URL ends in either `latest` or a version number, and the two are cached completely
differently.

## Pinned versions are immutable

```text theme={null}
/{environmentID}/{themeID}/{variantID}/7/tokens.css
```

Version 7's bytes never change — nothing in Livry can write an existing version. So it is served as
**immutable and cached for a year**. A browser that has it will not ask again.

This is the fastest thing Livry can give you, and it behaves like any other hashed build asset.

## `latest` moves

```text theme={null}
/{environmentID}/{themeID}/{variantID}/latest/tokens.css
```

`latest` points at whatever was published most recently, so it has to expire. How fast is **an
Environment setting**:

| | |
| - | - |
| **Default** | 60 seconds |
| **Range** | 0 – 3600 seconds |
| **0** | Every reuse revalidates first — cheap, because the answer is usually a `304` |

A public `latest` answer is also served **stale-while-revalidate for 5 minutes**, so a reader past the
expiry gets the old file immediately while a fresh one is fetched behind them. It costs at most one
reload of a slightly old file, never a blocked request.

<Note>
  **The Environment owns this, not the Theme.** The right answer depends on what the Environment is
  *for* rather than on the product: a sandbox wants a publish on screen at the next reload;
  production wants fewer requests and can wait a minute. Every Theme in one Environment shares the
  value.

  Set it on the Environment's Settings page. Changing it rewrites every Theme's serving settings.
</Note>

## Signed answers are capped at 5 minutes

<Warning>
  A signed answer is never cached for more than **300 seconds**, pinned or latest, whatever the
  Environment's lifetime says.

  A signature has no expiry, so **deleting its key is the only revocation** — and this cap is how
  long a revoked URL can keep working from a cache that already held it. It is not the Environment's
  to widen.
</Warning>

So a signed pinned URL is cached for 5 minutes, not a year. That is the price of revocability.

## Restricted Themes are not shared-cached at all

A Theme with an origin or IP allowlist is served with directives that forbid a shared cache from
storing it — a proxy holding a copy would answer the next caller without running the checks. See
[Access control](/serving/access-control).

## Choosing, in production

<CardGroup cols={2}>
  <Card title="Pin the version" icon="thumbtack">
    A theme change ships like any other change: publish, then deploy the new version number.

    **Best cache behaviour**, and a publish can never surprise a running app. Costs you a deploy per
    theme change.
  </Card>

  <Card title="Use latest" icon="arrows-rotate">
    Publishing reaches users without a deploy — which is usually the point of buying a theming
    service.

    Costs a revalidation per cache lifetime, and means a publish is a production change.
  </Card>
</CardGroup>

A middle path that works well: **`latest` in sandbox, pinned in production**, with your release
process bumping the number. You get fast iteration where you are working and no surprises where it
matters.

<Note>
  Your app can read the current version from the API — a Theme's `servingUrls.version` — so "pin to
  whatever is newest at build time" is a one-line build step rather than a manual edit. See
  [Themes](/api/themes).
</Note>

## How long a publish takes to appear

<Steps>
  <Step title="The publish returns">
    Immediately. The version is written and the draft is gone.
  </Step>

  <Step title="Files are materialised">
    A few seconds. Rendering happens asynchronously, off a queue.
  </Step>

  <Step title="latest reaches a given reader">
    Up to the Environment's cache lifetime after that — plus up to 5 minutes more if they are served
    stale while it revalidates.
  </Step>
</Steps>

If you need to confirm a publish landed, poll the pinned URL for the new version number rather than
watching `latest`. The pinned URL appears as soon as materialisation finishes and is never served
stale.

## Conditional requests

Every answer carries an `ETag`, and `If-None-Match` is honoured — a revalidation that finds nothing
changed is a `304` with no body. That is what makes a zero-second cache lifetime affordable if you
want one.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.