tiero

Catalog spec v1

The JSON format for templates: items, groups, tiers, branding and credits.

A catalog (also called a template) is the list of items people rank on tiero. Data providers publish their own catalogs and keep them in sync; every tier list made from a catalog points back at it. The machine-readable source of truth is the @tiero/spec npm package: its zod schemas, the generated JSON Schema files and validateCatalogSpec. When this page and the package disagree, the package wins.

Two formats

FormatspecUsed byImages
Catalog spectiero-catalog/1the body of PUT /api/v1/catalogs/{slug}uploaded media ids only (med_...)
Catalog sourcetiero-catalog-source/1the tiero CLI and partner exportersa media id, a local file or an HTTPS URL

The source format lets you keep a catalog in a repository next to its images. The CLI uploads each {"file": ...} or {"url": ...} image, swaps in the returned media id and sends a catalog spec. Apart from the spec value and the image references, the formats are identical.

Example

catalog.json
{
  "spec": "tiero-catalog/1",
  "slug": "nba-2k27-badges",
  "title": "NBA 2K27 Badges",
  "description": "Every badge in NBA 2K27.",
  "game": "nba-2k27",
  "tags": ["nba-2k27", "badges"],
  "visibility": "public",
  "showNames": true,
  "license": { "note": "Game art (c) 2K", "url": "https://example.com/license" },
  "branding": {
    "name": "lockercodes.io",
    "url": "https://lockercodes.io/nba-2k/tier-lists/badges",
    "logo": "med_abcdefghijklmnop"
  },
  "defaultTiers": [
    { "id": "s", "label": "S", "color": "#e11d48" },
    { "id": "a", "label": "A", "color": "#f97316" }
  ],
  "groups": [{ "id": "finishing", "label": "Finishing" }],
  "items": [
    {
      "id": "posterizer",
      "name": "Posterizer",
      "image": "med_234567abcdefghij",
      "group": "finishing",
      "color": "#22c55e",
      "sourceUrl": "https://example.com/badges/posterizer",
      "credit": { "name": "Photo: Jane Doe", "url": "https://example.com/jane" },
      "attributes": { "category": "Finishing", "tier": 3, "hallOfFame": true }
    }
  ]
}

The smallest valid catalog needs only spec, slug, title and one item with an id and a name.

Catalog fields

Text lengths count characters after trimming, where the field is trimmed.

FieldRequiredRuleDefault
specyestiero-catalog/1 (source: tiero-catalog-source/1)
slugyes3 to 64 chars, lowercase letters, digits and hyphens, starting with a letter or digit; unique per owner
titleyes1 to 100 chars, trimmed, no URLs
descriptionno0 to 500 chars""
gameno1 to 40 chars of a-z, 0-9 and hyphensnone
tagsno0 to 8 tags, each 1 to 32 chars of a-z, 0-9 and hyphens, no repeats[]
visibilitynopublic, unlisted or private (the visibility you request)public
showNamesnoboolean: show item names under imagestrue
licenseno{note?, url?} with at least one key; note up to 200 chars, url HTTPS up to 300 charsnone
brandingno{name, url, logo?}: name 1 to 60 chars, url HTTPS up to 300 chars, logo an image referencenone
defaultTiersno2 to 12 tiers, unique idssix tiers, below
groupsno0 to 40 groups, unique ids[]
itemsyes1 to 2,000 items, unique ids

The whole request body may be at most 1 MB (1,048,576 bytes). The API checks the size before it parses the JSON.

Tiers and groups

FieldRule
Tier id1 to 64 chars of a-z, 0-9 and hyphens, starting with a letter or digit
Tier label1 to 40 chars, trimmed
Tier colorhex color, #rgb or #rrggbb
Group id1 to 64 chars of a-z, 0-9 and hyphens, starting with a letter or digit
Group label1 to 60 chars, trimmed

When defaultTiers is left out, the catalog uses S-Tier #e11d48, A-Tier #f97316, B-Tier #eab308, C-Tier #22c55e, D-Tier #3b82f6 and F-Tier #a855f7 (ids s, a, b, c, d, f).

Item fields

FieldRequiredRule
idyes1 to 80 chars of a-z, 0-9, colon, period, underscore and hyphen, starting with a letter or digit; not starting with tier:; unique in the catalog
nameyes1 to 80 chars, trimmed
imagenoan image reference (see below)
groupnothe id of a group defined in groups
colornohex color, #rgb or #rrggbb
sourceUrlnoHTTPS URL up to 300 chars, where the item comes from
creditno{name, url?}: name 1 to 80 chars, url HTTPS up to 300 chars; needs image
attributesnoup to 12 keys of 1 to 32 chars (a letter, then letters, digits or underscores); values are a string up to 80 chars, a finite number or a boolean

Item ids are stable keys. Tier lists store item ids, so keep an id the same when you rename an item. Ids that start with tier: are reserved for the builder. credit credits the item's image (for example a photographer), so it is only accepted on an item that has an image.

Image references

WhereAllowed
Catalog speca media id: med_ plus 16 characters from a-z and 2-7, for media you uploaded
Catalog sourcea media id, {"file": "<path>"} or {"url": "<https url>"}

A source file path is relative to the source file: 1 to 300 characters, no leading /, no .. segment, no backslash and no :. A source url must be HTTPS and at most 2,048 characters. Each reference object has exactly one key.

Validation

Every object is strict: a field that is not listed here is an error, not ignored, so a typo such as showname cannot silently drop data.

TypeScript
import { validateCatalogSpec } from '@tiero/spec';

const result = validateCatalogSpec(JSON.parse(text));
if (!result.ok) for (const issue of result.issues) console.log(issue.pointer, issue.message);

validateCatalogSpec and validateCatalogSource never throw. They return { ok: true, value } with every default filled in, or a list of issues:

Invalid result
{
  "ok": false,
  "issues": [
    { "pointer": "/items/1/id", "code": "duplicate_id", "message": "Duplicate id" },
    { "pointer": "/items/0/group", "code": "unknown_group", "message": "Unknown group" }
  ],
  "truncated": true
}
  • pointer is a JSON Pointer (RFC 6901) to the field; "" is the whole document.
  • Issues are sorted by pointer and capped at 100. truncated: true means there were more.
  • code is a zod issue code (such as too_big or unrecognized_keys) or a catalog rule:
CodeMeaning
duplicate_ida tier, group or item id is used twice; the pointer is the second use
unknown_groupan item names a group that groups does not define
credit_requires_imagean item has credit but no image
url_in_titlethe title contains a URL
reserved_item_idan item id starts with tier:

The cross-item rules (duplicate_id, unknown_group, credit_requires_image) are skipped while the document has a structural error. Fix those first, then validate again. The API answers an invalid catalog with 422 validation_failed and the same issues in error.details.issues.

JSON Schema

@tiero/spec ships generated JSON Schema files for editors and other languages:

  • @tiero/spec/schema/catalog-spec-v1.json
  • @tiero/spec/schema/catalog-source-v1.json

They check shape, patterns and lengths only. The cross-item rules, the tier: reservation, the 12-attribute cap, unique tags, the license rule, the no-URL title rule, trimming and the HTTPS and file path checks are not in them. validateCatalogSpec is the authoritative check.

Publishing and updates

  • PUT /api/v1/catalogs/{slug} creates the catalog or replaces it as a whole.
  • Add If-None-Match: * to only create: a taken slug answers 412 catalog_exists.
  • tiero hashes the canonical JSON. An unchanged catalog returns changed: false and keeps its version.
  • An item you leave out of a later PUT becomes a tombstone, and lists that ranked it show it as removed. Sending the same id again brings it back.
  • Error codes are listed in @tiero/spec (ERROR_CODES). They are only ever added.