Items

The Items resource is the catalog — one row per distinct item the game knows about. Player ownership lives in a separate table (item_instances) under /v1/users/@me/inventory.

Catalog is global and read-mostly: admins author rows, everyone reads. Reads need only an authenticated user; every write (POST, PATCH, DELETE, uploads) requires the items.write permission.


The Item object

  • Name
    id
    Type
    string
    Description

    Snowflake.

  • Name
    name
    Type
    string
    Description

    1–100 chars.

  • Name
    description
    Type
    string
    Description

    Up to 2000 chars. Defaults to "".

  • Name
    type
    Type
    ItemType
    Description

    1 DEFAULT, 2 EQUIPMENT, 3 DECOR, 4 FISH, 5 SCENERY.

  • Name
    rarity
    Type
    Rarity
    Description

    1 COMMON → 9 UNIDENTIFIED. Drives roll bias on randomization + UI tint.

  • Name
    bind
    Type
    BindType
    Description

    1 NONE, 2 BIND_ON_PICKUP, 3 BIND_ON_EQUIP.

  • Name
    icon
    Type
    string
    Description

    sha256[0:16] hash of the inventory thumbnail PNG. URL is ${CDN_BASE_URL}/icons/${icon}/${icon}.png. Defaults to "".

  • Name
    flags
    Type
    integer
    Description

    Bitfield. 1 << 0 = DELUX. Leave room for new bits — never reuse.

  • Name
    properties
    Type
    BaseItemProperties | null
    Description

    Per-type bag of rollable property descriptors. See Properties.

  • Name
    prefab
    Type
    any | null
    Description

    Rendering hints. Non-randomizable items with a single sheet stash the spritesheet hash here as { spritesheet: "<hash>" }.

  • Name
    layers
    Type
    ItemLayers | null
    Description

    Opts the item into per-grant randomization. Null = the catalog defaults are reused as-is.

  • Name
    created_at
    Type
    string
    Description

List

Filter by type, rarity, and a left-anchored name prefix q. Keyset-paginated.

  • Name
    type
    Type
    integer
    Description

    Optional ItemType filter.

  • Name
    rarity
    Type
    integer
    Description

    Optional Rarity filter.

  • Name
    q
    Type
    string
    Description

    Case-insensitive prefix on name.

  • Name
    before
    Type
    string
    Description

    Snowflake id for keyset pagination.

  • Name
    limit
    Type
    integer
    Description

    1–200. Default 50.

Request

GET
/v1/items
curl "https://api.localuniverse.io/v1/items?type=4&limit=20" \
  -H "authorization: <token>"

Response

[
  { "id": "3091...", "name": "Cosmic Bass", "type": 4, "rarity": 3, ... },
  { "id": "3092...", "name": "Sapphire Hat", "type": 2, "rarity": 4, ... }
]

Get, create, update, delete

MethodPathPermissionNotes
GET/v1/items/:itemIdnoneOpen to any authed user.
POST/v1/itemsitems.writeBody is a CreateItem shape — see below.
PATCH/v1/items/:itemIditems.writePATCH semantics. properties, prefab, layers accept explicit null to clear.
DELETE/v1/items/:itemIditems.writeCascades into every item_instance referencing the row.

CreateItem body

  • Name
    name
    Type
    string
    Description

    1–100 chars.

  • Name
    description
    Type
    ?string
    Description
  • Name
    type
    Type
    ItemType
    Description

    Required.

  • Name
    rarity
    Type
    Rarity
    Description

    Required.

  • Name
    bind
    Type
    ?BindType
    Description

    Defaults to NONE.

  • Name
    icon
    Type
    ?string
    Description

    Hash returned from /v1/items/uploads/icon.

  • Name
    flags
    Type
    ?integer
    Description
  • Name
    properties
    Type
    ?any
    Description
  • Name
    prefab
    Type
    ?any
    Description

    For non-randomizable items with a spritesheet, set { spritesheet: "<hash>" }.

  • Name
    layers
    Type
    ?ItemLayers
    Description

    See Randomization.


Asset uploads

Three multipart endpoints. Each returns the content hash; the admin client stamps it onto the create-item payload.

Icon

PNG only. ≤ 8 MB. Server validates magic-bytes, hashes (sha256[0:16]), and stores at icons/<hash>/<hash>.png in CDN_BUCKET. Each asset lives in its own per-hash directory so PNG + sibling descriptors stay grouped.

Request

POST
/v1/items/uploads/icon
curl -X POST https://api.localuniverse.io/v1/items/uploads/icon \
  -H "authorization: <token>" \
  -F "file=@./icon.png"

Response

{ "hash": "a1b2c3d4e5f60123" }

Spritesheet

PNG only. ≤ 8 MB. Stored at textures/<hash>/<hash>.png.

The hash returned here is what you pass to /uploads/manifest to attach the manifest as a sibling in the same per-hash directory.

Request

POST
/v1/items/uploads/spritesheet
curl -X POST https://api.localuniverse.io/v1/items/uploads/spritesheet \
  -H "authorization: <token>" \
  -F "file=@./spritesheet.png"

Manifest

JSON only. ≤ 1 MB. The server parses it as JSON for sanity but does not enforce shape — manifests are consumed by the client / game engine, not the API.

Stored at textures/<spritesheet_hash>/<spritesheet_hash>.spritesheet.json — same per-hash directory as the PNG, so the sprite-render service's copySiblingDescriptors walk picks it up when an instance composite is rendered.

Request

POST
/v1/items/uploads/manifest
curl -X POST https://api.localuniverse.io/v1/items/uploads/manifest \
  -H "authorization: <token>" \
  -F "spritesheet_hash=a1b2c3d4e5f60123" \
  -F "file=@./spritesheet.json"

Randomization

When layers is set on a catalog row, and the grant call opts in with randomize: true, the grant flow composites a unique spritesheet + icon per instance and stamps the resulting hashes onto the item_instance row.

items.layers shape

{
  layers: number,            // dye-zone count
  hue_centers: number[],     // original hues for roll bias
  textures: string[],        // texture names in the prefab that get tinted
  icon: string               // master icon name
}

The sprite renderer reads master _layer1.png_layerN.png (plus optional _outlines.png) under textures/<master>/ in CDN_BUCKET, multiplies each by a rolled tint, composites, hashes the result, and writes it to textures/<hash>.png. Every sibling under the master directory matching <master>.<ext> (manifest, atlas, etc.) gets copied next to the hashed output. See packages/sprite-tools for the splitter that authors layer assets.


Properties

The catalog row's properties column is a Record<string, PropertyDescriptor> map. At grant time each descriptor rolls into a concrete value and the result is stamped onto item_instances.properties so it stays stable for the lifetime of the instance.

items.properties (catalog spec)

{
  "equipment_type": { "value": 1, "resolve": "fixed" },
  "durability":     { "min": 50, "max": 100, "resolve": "range" },
  "rarity_bonus":   {
    "min": 0, "max": 1, "mean": 0.3, "stddev": 0.15,
    "precision": 2, "resolve": "curve"
  },
  "skin_variant":   {
    "values": ["red", "blue", "green"],
    "weights": [3, 1, 1],
    "resolve": "pool"
  },
  "blessed":        { "value": true, "resolve": "fixed", "required": false, "probability": 0.05 }
}

item_instances.properties (rolled result)

{
  "equipment_type": 1,
  "durability": 87,
  "rarity_bonus": 0.42,
  "skin_variant": "red"
}

blessed was skipped in this roll — required: false plus probability: 0.05 means it lands on roughly 5% of grants and otherwise doesn't appear in the result at all.

Resolvers

  • Name
    fixed
    Description

    Literal pass-through. { value, resolve: 'fixed' }. Most catalog properties use this — even "random" stats are usually authored as a fixed integer first and switched to a roll later.

  • Name
    range
    Description

    Uniform pick [min, max]. Integer-only when both bounds are integers and precision is omitted; otherwise float, optionally rounded to precision decimal places.

  • Name
    pool
    Description

    Pick one of values uniformly, or weighted by weights (same length). Strings, numbers, booleans all accepted.

  • Name
    curve
    Description

    Normal distribution centered on mean (default: midpoint), with stddev (default: range / 6), clamped to [min, max]. Use for stat rolls where most instances should land near a centroid with rare outliers.

Every descriptor optionally carries required: false + probability to make the key itself probabilistic — useful for chance-of-drop modifiers like blessed, cursed, lucky, etc.

The whole roll lives in @localuniverse/common/property-resolver and is called once at grant time. Unknown resolve values throw — catalogs author against known strategies and a typo should fail loudly rather than silently dropping a property.


Inventory

Owned instances of catalog items live on the user resource:

MethodPathNotes
GET/v1/users/@me/inventoryThe caller's instances.
GET/v1/users/@me/inventory/:instanceIdSingle instance.
DELETE/v1/users/@me/inventory/:instanceIdDiscard.
POST/v1/users/:userId/inventoryAdmin grant. Body: { item_id, randomize? }. Requires items.write.

Gameplay drops (fish caught, NPC reward) call itemInstanceService.grant() directly — they don't round-trip through HTTP.

Was this page helpful?