---
title: "Cosmetics CDN"
description: "Cosmetic catalogs, image atlases, and individual icons."
canonical: "https://developers.fnlb.net/docs/cdn/cosmetics"
markdown: "/docs/cdn/cosmetics.md"
---

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

# Cosmetics CDN

Cosmetic catalogs tell you which cosmetics exist, what they are called, and how they are grouped. Icons and atlases tell you what they look like. Everything on this page is read-only and needs no API token.

A typical client picks a locale, loads a `lite.json` catalog for the item list, and renders each item from an atlas page and the individual icon as a fallback.

## Catalogs

Catalogs are regenerated about once an hour. Every catalog comes in two forms:

- **`full.json`** keeps every field, including variants, flags, and asset paths. Use it when you need the whole record.
- **`lite.json`** drops fields that are not needed to render or select an item. Use it for grids and pickers.

Each catalog lives under a locale. These locales are available:

```text
ar  de  en  es  es-419  fr  id  it  ja  ko  pl
pt-BR  ru  th  tr  vi  zh-Hans  zh-Hant
```

The examples use `en`. Replace it with any locale from the list. The locale controls the localized `name` fields; ids are the same in every catalog.

### Endpoints

All paths below are relative to `https://cdn.fnlb.net`, and every one also has a `lite.json` variant.

| Path                                          | `full.json` contents                           |
| :-------------------------------------------- | :--------------------------------------------- |
| `/cosmetics/{locale}/full.json`               | Equippable cosmetics and tracks.               |
| `/cosmetics/{locale}/{type}/full.json`        | One cosmetic type.                             |
| `/cosmetics/{locale}/backbling/full.json`     | Backpacks and pet carriers.                    |
| `/cosmetics/{locale}/pickable/full.json`      | Emotes, emojis, and tracks.                    |
| `/cosmetics/{locale}/track/full.json`         | Tracks only.                                   |
| `/cosmetics/{locale}/shop/full.json`          | Every equippable cosmetic in the current shop. |
| `/cosmetics/{locale}/shop/{type}/full.json`   | Shop items of one equippable type.             |
| `/cosmetics/{locale}/new/full.json`           | Every new equippable cosmetic.                 |
| `/cosmetics/{locale}/new/{type}/full.json`    | New items of one equippable type.              |
| `/cosmetics/{locale}/banners/full.json`       | Banners.                                       |
| `/cosmetics/{locale}/banner-colors/full.json` | Banner colors.                                 |
| `/cosmetics/{locale}/playlists/full.json`     | Playlists.                                     |

The shape of a `lite.json` file depends on the catalog:

- The root `/{locale}/lite.json` is an array of `{ id, type, name, variants? }`. Tracks appear with `type: "track"` but without `albumArt`.
- Per-type and group lite catalogs are arrays of `{ id, name, variants? }`. The `type` is omitted because the path already identifies it.
- Shop and new lite catalogs are arrays of cosmetic id strings.
- `banners/lite.json` contains `{ id, name }`.
- `banner-colors/lite.json` contains `{ id, color, hex }`.
- `playlists/lite.json` contains `{ id, name, subName, isDefault }`.

### Which types appear where

The root catalog and the shop/new catalogs only include equippable cosmetics:

```text
backpack  emoji  emote  outfit  petcarrier  pickaxe  shoe  sidekick  track
```

The per-type catalogs cover every Battle Royale cosmetic type:

```text
aura  backpack  contrail  emoji  emote  glider  loadingscreen
music  outfit  petcarrier  pickaxe  shoe  sidekick  spray  toy  wrap
```

## Cosmetic objects

A full cosmetic looks like this:

```json
{
  "id": "CID_029_Athena_Commando_F_Halloween",
  "type": "outfit",
  "name": "Ghoul Trooper",
  "variants": [
    {
      "t": "ClothingColor",
      "n": "Clothing Color",
      "o": [{ "t": "Mat0", "n": "Default" }]
    }
  ],
  "path": "/Game/Athena/Items/Cosmetics/Characters/CID_029_Athena_Commando_F_Halloween/"
}
```

- `id` is the cosmetic id.
- `type` is one of the types listed above.
- `name` is localized for the catalog locale.
- `variants` is optional. `t` is the channel, `n` is its display name, and each option uses `t` for the tag and `n` for its display name.
- `flags` is optional and is a bitfield used by the API.
- `path` is optional and points at the game asset.

A full track uses a different shape:

```json
{
  "id": "SparksSong_...",
  "type": "track",
  "name": "Track name",
  "path": "/SparksSongTemplates/Items/SongParts/",
  "albumArt": "https://...",
  "added": "2026-01-01T00:00:00.000Z"
}
```

Banners have their own shape: `full.json` returns `{ id, devName, name, description }`, while `lite.json` returns `{ id, name }`.

## Individual icons

Every cosmetic with a usable image has a standalone WebP, so you can render a single item without loading an atlas. The id is lowercased in the path.

| Image          | URL                                                                  |
| :------------- | :------------------------------------------------------------------- |
| Base cosmetic  | `/cosmetics/media/icons/{folder}/{id}.webp`                          |
| Variant option | `/cosmetics/media/icons/variants/{folder}/{id}/{channel}/{tag}.webp` |

For example:

```text
https://cdn.fnlb.net/cosmetics/media/icons/outfits/cid_029_athena_commando_f_halloween.webp
https://cdn.fnlb.net/cosmetics/media/icons/variants/outfits/cid_029_athena_commando_f_halloween/clothingcolor/mat0.webp
```

Variant icons split the cosmetic id, channel, and tag into separate path segments. Each part is lowercased, so `ClothingColor` becomes `clothingcolor`.

These icons are lossless WebP at the source image's original dimensions. Placeholder images are skipped, so not every id in a catalog has an icon. The URL stays the same when the image changes, so it is safe to cache.

### Types and media folders

The `{folder}` segment is not always the catalog type.

| Type or group   | Folder           |
| :-------------- | :--------------- |
| `aura`          | `auras`          |
| `backpack`      | `backpacks`      |
| `contrail`      | `contrails`      |
| `emoji`         | `emojis`         |
| `emote`         | `emotes`         |
| `glider`        | `gliders`        |
| `loadingscreen` | `loadingscreens` |
| `music`         | `music`          |
| `outfit`        | `outfits`        |
| `petcarrier`    | `petcarriers`    |
| `pickaxe`       | `pickaxes`       |
| `shoe`          | `shoes`          |
| `sidekick`      | `sidekicks`      |
| `spray`         | `sprays`         |
| `toy`           | `toys`           |
| `track`         | `tracks`         |
| `wrap`          | `wraps`          |
| `banner`        | `banners`        |

## Image atlases

An atlas packs many icons into a few sprite sheets, which is far cheaper than requesting hundreds of individual images. Use atlases for large cosmetic grids and the individual icons above when you need one image on its own.

Reading an atlas is a three-step process: read the manifest for the page grid, read the group index for an item's position, then load the page and crop the tile.

### 1. Read the manifest

```http
GET /cosmetics/media/manifest.json
```

```json
{
  "revision": "…",
  "groups": {
    "outfit": { "pages": [[8, 8]] },
    "backpack": { "pages": [[8, 8]] }
  },
  "variantGroups": {
    "outfit": { "pages": [[8, 8]] }
  }
}
```

Each group lists the grid dimensions of its pages as `[cols, rows]`. The group keys are the type names from the folder table above. `variantGroups` is present when variant options have their own atlases.

### 2. Read a group index

```http
GET /cosmetics/media/atlas/outfit.json
```

```json
{
  "revision": "…",
  "items": {
    "CID_029_Athena_Commando_F_Halloween": [1, 0]
  }
}
```

Each value is `[page, index]`:

- `page` is 1-based.
- `index` is the 0-based position on that page, in row-major order. It only counts items that have an image on the page.
- Some entries can be `[page, index, cols, rows]`, which overrides the grid for that page.

If an id is missing from `items`, it has no image and no tile on the page.

### 3. Load the page and crop the tile

```text
/cosmetics/media/atlas/{folder}/{folder}_{page}.webp
```

For the example above:

```text
https://cdn.fnlb.net/cosmetics/media/atlas/outfits/outfits_1.webp?v=<revision>
```

Each tile is 128×128. To get its position, use the page grid from the manifest:

```ts
const [page, index] = index.items[cosmeticId];
const [cols, rows] = manifest.groups.outfit.pages[page - 1];

const column = index % cols;
const row = Math.floor(index / cols);

const left = column * 128;
const top = row * 128;
```

Append `?v=<index.revision>` to page URLs when caching them. The revision changes when that group's images or item list change.

### Variant atlases

Variant options have their own index and pages:

```text
/cosmetics/media/atlas/variants/{groupKey}.json
/cosmetics/media/atlas/variants/{folder}/{folder}_{page}.webp
```

The variant index uses the same `[page, index]` format. Its keys are lowercased `<cosmeticId>:<channel>:<tag>` strings, for example `cid_029_athena_commando_f_halloween:clothingcolor:mat0`.

<Warning>
Paths under `/cosmetics/media/atlas/_sources/` and `/cosmetics/media/atlas/_tiles/` are internal caches used while generating atlases. They are not part of the public API and can change or disappear without notice. Consume the manifest, the group indexes, the pages, and the individual icons instead.
</Warning>

## Freshness

- Catalogs are rewritten when their contents change and are refreshed about once an hour.
- Individual cosmetic icons keep a stable URL and are replaced only when the source image changes.
- Atlas indexes and pages change with their group revision, so append `?v=<revision>` when caching them.

## Next

<CardGroup cols={2}>
  <Card icon="code" href="/docs/cdn/app-icons" title="Application icons">
    Icons uploaded for applications in the portal.
  </Card>
  <Card icon="http" href="/docs/cdn/overview" title="CDN overview">
    What the CDN serves and how to cache it.
  </Card>
</CardGroup>
