> ## 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.

# Editing Variant Overrides in the Livry Public API

> Read a Variant's overrides, set and unset them by path, publish them against a Theme version, or discard the draft.

A Variant's tokens are **not** a DTCG document. They are a flat map of dotted path to value — the
values this Variant changes, and nothing else.

| | | Needs |
| - | - | - |
| `GET` | `…/variants/{variantID}/tokens` | `viewer` |
| `PATCH` | `…/variants/{variantID}/tokens` | `editor` |
| `POST` | `…/variants/{variantID}/tokens/publish` | `editor` |
| `DELETE` | `…/variants/{variantID}/tokens` | `editor` |

All paths are under `/public/v1/environments/{environmentID}/themes/{themeID}`.

## Why the shape differs from a Theme's

The Theme owns the structure: the tree, the names, `$type`, `$description`, `$extensions`. A Variant
owns `$value` and nothing else.

That is not a simplification of the API — it is the model. A Variant cannot add a token, cannot
rename one, cannot retype one, and cannot describe one. Overriding a path the Theme does not have is
silently ignored, because there is nothing for the override to apply to. See
[Variants](/theming/variants).

<Info>
  If you want a token's type or description, ask the **Theme**. It is the only place that can answer
  without two sources being able to disagree.
</Info>

## Read the overrides

```http theme={null}
GET …/variants/{variantID}/tokens
```

<ParamField query="version" type="integer">
  Read a specific published version. Remember that a Variant version number is a **Theme** version
  number.
</ParamField>

<ParamField query="includeVersions" default="false" type="boolean" />

```json Response 200 theme={null}
{
  "resource": {
    "version": null,
    "latestVersion": 7,
    "publishedAt": "2026-09-17T22:11:02Z",
    "etag": "0x8DC1F2AA07B1D93",
    "hasUnpublishedChanges": true,
    "versions": [],
    "tokens": [
      {
        "path": "color.brand.primary",
        "type": null,
        "resolvedType": null,
        "inferred": false,
        "value": {
          "colorSpace": "srgb",
          "components": [0.078, 0.682, 0.349],
          "hex": "#14AE59"
        },
        "description": null,
        "extensions": null
      }
    ],
    "groups": []
  }
}
```

The envelope is deliberately identical to a Theme's, so a client reading both surfaces reads one
shape. What differs is that **`groups` is always empty** and a token carries only `path` and `value`.
`type`, `resolvedType` and `description` are always `null`, and `inferred` is always `false` — there
is no type here that could have been inferred.

## Edit the overrides

```http theme={null}
PATCH …/variants/{variantID}/tokens?etag={etag}
```

**Two operations over a dotted path**, where a Theme takes five over a JSON Pointer. There is no tree
to address into, nothing to escape, and no `move` to express.

<CodeGroup>
  ```json Request theme={null}
  {
    "operations": [
      {
        "operation": "set",
        "path": "color.brand.primary",
        "value": "#14AE59"
      },
      {
        "operation": "unset",
        "path": "color.brand.accent"
      }
    ]
  }
  ```

  ```bash curl theme={null}
  curl -X PATCH "$BASE/environments/$ENV/themes/$THEME/variants/$VARIANT/tokens?etag=$ETAG" \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"operations":[{"operation":"set","path":"color.brand.primary","value":"#14AE59"}]}'
  ```
</CodeGroup>

| `operation` | Carries | Means |
| - | - | - |
| `set` | `path`, `value` | Override this path with this value. Covers RFC 6902's `add`/`replace` pair — whether the path was already overridden is not a distinction a client can meaningfully make. |
| `unset` | `path` | Stop overriding this path. **Not the same as setting it to nothing**: the path falls back to the Theme's own value. |

The path is the token path as the Theme spells it — `color.brand.primary`, not
`/color/brand/primary`.

Returns the whole override set, with the new `etag`.

## Publish the overrides

```http theme={null}
POST …/variants/{variantID}/tokens/publish
```

No body. Returns the Variant.

<Warning>
  **A Variant can be published only once per Theme version.**

  A Variant version number *is* the Theme version its overrides are addressed against. The publish
  writes a version file numbered with the Theme's current version; publishing again against the same
  Theme version finds that file already written and answers `409 conflict`.

  Re-reading and retrying will not clear it. Publish the Theme first, then the Variant.
</Warning>

## Discard the draft

```http theme={null}
DELETE …/variants/{variantID}/tokens?etag={etag}
```

Throws the draft away and returns the Variant. Published versions are untouched.

## After the Theme publishes

When its Theme publishes a version, every Variant beneath it is **automatically migrated**: keys the
Theme renamed are renamed here, and keys it removed — or retyped past fitting — are dropped.

That migration writes each Variant's **draft**. It does not publish. So after a Theme publish, expect
`hasUnpublishedChanges: true` on Variants nobody touched; read the draft, check it, and publish.

<Note>
  A rename is only reliably tracked when the Theme's edit used a `move` operation. A
  remove-and-add pair has to be inferred by matching removed content against added content, which can
  lose an override when the rename also changed the value, or when two identically valued tokens are
  renamed in the same publish.
</Note>


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