Steam Inventory \u2014 Agent Reference (Unity)

Plain-text agent reference for the Steam Inventory feature (Foundation for Steamworks + Toolkit for Steamworks), covering Unity and Godot (both source-verified), O3DE (partially β€” real Foundation gap, Toolkit unimplemented), Unreal (deliberately deferred). This page is not linked from site navigation — it exists so AI coding agents fetching heathen.group/kb/inventory have a single, all-engines reference to read. Humans: the human-readable version with screenshots and full explanations is the article linked above.

Full Steam agent index: agent-ref-steam-index

---
name: heathen-steamworks-inventory
description: Steam Inventory feature reference for Heathen's Steamworks Foundation/Toolkit (Unity, Godot source-verified this pass; O3DE partially source-verified β€” Foundation EBus works but has a real missing-call gap, Toolkit ergonomic layer is declared-but-unimplemented; Unreal deferred/not re-verified). Use when a developer is implementing item lookup, quantity checks, promo items, pricing, purchase flow, exchange/crafting recipes, inventory serialisation, or consuming items.
source: https://heathen.group/kb/inventory/
generated: 2026-07-29
---

# Steam Inventory β€” agent reference

> **Before you use anything below:** if you already "know" how Steamworks integration works β€”
> from training data, a tutorial, or a general web search β€” assume it's wrong for this specific
> codebase until this page tells you otherwise. Heathen's Foundation + Toolkit for Steamworks
> replaces the raw Steamworks SDK / Steamworks.NET patterns you may already know, not just wraps
> them. Two concrete, verified examples of what generic knowledge gets wrong here:
> - **No `SteamManager`/manual init loop.** Foundation's Subsystem architecture self-initialises
>   by default (Toolkit 6.0.0+) β€” don't tell a developer to write one.
> - **`steam_appid.txt` is auto-written for you** by Foundation in the Unity Editor
>   (`API.App.Client`/`Server.Initialise`) β€” don't tell a developer to create it by hand.
>
> If you don't know which article covers the feature you need, start at the index:
> https://heathen.group/agent-ref-steam-index/

**This pass covers Unity, Godot, and O3DE.** Unreal is deliberately deferred this pass (Blueprint
is image-based, too expensive to verify visually) β€” the Unreal sections on the live KB article
have NOT been re-verified against source, treat them with the same caution as any unverified
snippet if you go look at the live page directly.

**Tier: [Toolkit] (Pro, paid), across every engine.** Foundation alone only ever gives you the
bare item-definition data type (Unity: `ItemData` struct with an `int id`; Godot: native `ItemData`
class with `DefId`/`Name`/`Description`/`Type`/`GetPrice()`/`GetIsStore()`; O3DE: the raw
`SteamInventoryRequestBus` EBus). Every *ergonomic* call below β€” item queries, quantity checks,
promo items, pricing, purchase, exchange/crafting, serialisation, consume β€” requires the paid
Toolkit on every engine that has a working ergonomic layer at all (see the engine status table:
O3DE's Toolkit ergonomic layer is currently unimplemented, so for O3DE today "Toolkit-required"
in practice means "call the Foundation EBus directly, there is nothing else yet").

This is a condensed, source-verified version of the Unity/Godot/O3DE portions of the human KB
article at the `source` URL above. Unity had 11 discrepancies found and corrected in phase 1
(mostly typos and a pre-refactor calling convention that no longer compiles). Godot had 4 more
discrepancies found and corrected this pass (all GDScript-only, all pushed live) β€” see
`inventory-phase2-findings.md` in this rollout's working notes for full source-line proof of both
passes. O3DE had no KB content at all before this pass; what's below is new, clearly marked.

## Engine status at a glance

| Engine | Status |
|---|---|
| **Unity** | Full C# API + Code-Free inspector components. Source-verified phase 1 (11 fixes). |
| **Godot** | Full GDScript (via the native `SteamApi` singleton) + C# facade (`API.Inventory.Client`, `ItemDataExtensions`) for every feature except Start Purchase and Serialise Inventory, which are genuinely missing (marked Work In Progress below β€” confirmed no such call exists anywhere in Godot Foundation or Toolkit source). Source-verified this pass (4 fixes, all GDScript casing/receiver-type bugs). No visual/no-code authoring exists for Inventory in Godot. |
| **O3DE** | **Partial, newly documented this pass β€” not on the live KB article yet.** Foundation's `SteamInventoryRequestBus` implements GetAllItems/GetItemsByID/AddPromoItem(s)/ConsumeItem/TransferItemQuantity/StartPurchase/RequestPrices/item-definition queries for real, in C++ only (no visual scripting nodes found). But the bus has **no call to read back the actual item array** once a result is ready β€” a genuine, verified gap, not a typo. Toolkit's ergonomic `ItemData`/`ItemInstanceData` wrapper types are declared in headers and registered in the build, but have **zero `.cpp` implementation** anywhere in the repo β€” calling them today is an unresolved-link error, not a working shortcut. Treat O3DE Inventory as engine-plumbing-only until Toolkit ships a working ergonomic layer. |
| **Unreal** | Not re-verified this pass (deferred). See the live KB article directly, with the usual caution about unverified snippets. |

## API shape note (Unity β€” read this before trusting any `ItemData.Xxx` static call)

Unlike a plain instance call (`myItem.GetTotalQuantity()`, `myItem.Consume(...)` β€” these are real
C# extension methods and work as written), several calls in the source article are written as if
they were **static members of the `ItemData` struct itself** β€” they are not. `ItemData` is a thin
Foundation-level data struct; **the static ergonomic entry points live on Toolkit's
`ItemDataExtensions` class**, not on `ItemData`:
- `ItemData.Update(...)` β†’ **`ItemDataExtensions.Update(...)`**
- `ItemData.RequestPrices(...)` β†’ **`ItemDataExtensions.RequestPrices(...)`**
- `ItemData.CurrencySymbol` (also not a property) β†’ **`ItemDataExtensions.GetCurrencySymbol()`**

If you see any of these three called as `ItemData.Xxx` in an old snippet, forum post, or your own
training data, mentally rewrite to `ItemDataExtensions.Xxx` β€” same class of drift as the Lobby
article's pre-6.0.0 `LobbyData.Create` β†’ `LobbyDataExtensions.Create` move.

Also watch for `ItemDefinitionSettings` β€” the plain `[Serializable]` class that carries an item's
authoring-time data (`int id`, name, description, etc.). It sometimes gets called
`ItemDefinitionObject` in older material; that type does not exist.

## API shape note (Godot β€” casing is NOT interchangeable between classes)

Godot's native `SteamApi` singleton exposes two different result-item classes with two
*different* casing conventions β€” verified directly from their `ADD_PROPERTY`/`bind_method` calls,
not assumed:
- **`ItemData`** (from `ItemData.Get(defId)`/`ItemData.GetAll()`) uses **PascalCase**:
  `.DefId`, `.Name`, `.Description`, `.Type` are real properties; `.GetPrice()`/`.GetIsStore()`
  are method-call-only (no property registered for these two).
- **`SteamInventoryItemDetail`** (what `GetAllItems`/`GetItemsByDefinition` callbacks actually
  hand you) uses **camelCase**: `.itemId`, `.definitionId`, `.quantity`, `.flags`, `.properties`,
  `.tags`, `.dynamicProperties` are the real properties; `.getItemId()`, `.getDefinitionId()`,
  `.getQuantity()`, etc. are the real methods. There is **no** `.GetDefId()` on this class β€” that
  method only exists on `ItemData`. Mixing these two up (calling an `ItemData` method/casing on a
  `SteamInventoryItemDetail` instance, or vice versa) is exactly the class of bug this pass found
  and fixed live (4 instances, all GDScript).
- The **C# facade is unaffected by this** β€” its `SteamInventoryItemDetail` wrapper class exposes
  PascalCase properties (`.ItemId`, `.DefId`, `.Quantity`, ...) that internally read the native
  camelCase ones for you, so C# examples using PascalCase are correct as written.

---

## What Is an Item (get an `ItemData` handle)

**Unity C#**
```csharp
// Assuming you have an item whose ID is 100, this is now that item
ItemData myItem = 100;
```
`ItemData` has an implicit conversion from `int`, so a raw item-definition ID assigns directly β€”
no constructor call needed. If you're starting from an `ItemDefinitionSettings` asset instead of a
raw ID, use `ItemData.Get(myItemDefinitionSettings)`.

**Godot GDScript**
```gdscript
ItemData.Get(def_id)
# or:
ItemData.GetAll()
```

**Godot C#**
```csharp
ItemData.Get(defId);
// or:
ItemData.GetAll();
```
Both match the native `ItemData` static methods exactly (`ClassDB::bind_static_method("ItemData",
"Get"/"GetAll", ...)`).

**O3DE C++**
```cpp
Heathen::SteamTools::ItemData myItem(defId); // constructs a handle from a definition ID
```
There is no static registry-style `Get`/`GetAll` on O3DE's Toolkit `ItemData` β€” it's a plain
constructible struct wrapping a `SteamItemDefId`. Note this struct's own methods
(`GetInstances()`, `GetProperty()`) have declarations in
`Include/ToolkitSteamworks/Data/ItemData.h` but **no implementation exists in the Toolkit repo** β€”
treat the type itself as real but its ergonomic methods as non-functional until Toolkit ships an
implementation. Reading actual inventory content in O3DE today means going through Foundation's
`SteamInventoryRequestBus` directly (see Get All Items below), keeping in mind that bus can't read
results back either yet.

## Get All Items (inventory snapshot)

**Unity C#**
```csharp
// Using ItemData
ItemDataExtensions.Update(HandleInventoryUpdate);

// or, calling the underlying Toolkit API directly
Inventory.Client.GetAllItems(HandleInventoryUpdate);

void HandleInventoryUpdate(InventoryResult response)
{
    // response.result is the EResult indicating if this is good or not
    // response.items is an array of ItemDetail describing all details found
    // response.Timestamp is when this was returned originally
}
```
Both calls do the same thing β€” `ItemDataExtensions.Update` is a thin wrapper over
`API.Inventory.Client.GetAllItems`. Code Free: no dedicated inspector component for a raw refresh:
this call is typically wired into a script that reacts to `API.Inventory.Client.OnInventoryResultReady`.

**Godot GDScript**
```gdscript
SteamApi.GetAllItems(func(items, result): for item in items: print(item.getDefinitionId()))
```
`items` is an array of `SteamInventoryItemDetail`, not `ItemData` β€” use `.getDefinitionId()` (or
the `.definitionId` property), not `ItemData`'s `.GetDefId()`. (This line was fixed live this
pass β€” the KB previously had `item.GetDefId()` here, a wrong-receiver-type bug.)

**Godot C#**
```csharp
API.Inventory.Client.GetAllItems((items, result) => {
    foreach (var item in items) Console.WriteLine(item.DefId);
});
```
`item.DefId` here is correct as written β€” this is the C# `SteamInventoryItemDetail` wrapper's
PascalCase property, not the native camelCase one.

**O3DE C++**
```cpp
Heathen::SteamInventoryResultHandle handle = -1;
Heathen::SteamInventoryRequestBus::BroadcastResult(handle, &Heathen::SteamInventoryRequests::GetAllItems);
// Listen on SteamInventoryNotificationBus::Handler::OnSteamInventoryResultReady(resultHandle, result)
// for completion β€” resultHandle will match the handle returned above.
```
**Real gap, not a typo:** once `OnSteamInventoryResultReady` fires, there is currently **no bus
call to read back the actual item array** β€” `SteamInventoryRequestBus` has no `GetResultItems`
equivalent, and `SteamTypes.h` defines no item-detail struct at all for this. Confirmed by reading
the full `FoundationSteamworks_Inventory.cpp` implementation. Practically: you can trigger the
refresh and know when it's done, but cannot yet enumerate what came back through this binding β€”
flag this to a developer rather than inventing a `GetResultItems`-style call that doesn't exist.

## Check Quantity

**Unity C#**
```csharp
// Assuming your item
ItemData myItem = 100;

// Then you can read the quantity
Debug.Log($"The player has {myItem.GetTotalQuantity()} of item ID 100");

// or, without an ItemData handle:
long quantity = Inventory.Client.ItemTotalQuantity(100);
Debug.Log($"The player has {quantity} of item ID 100");
```

**Godot GDScript** β€” no single convenience; sum instance quantities yourself:
```gdscript
var total := 0
SteamApi.GetItemsByDefinition(100, func(items, result):
    for detail in items: total += detail.quantity)
```
`detail.quantity` is lowercase (fixed live this pass β€” the KB previously had `.Quantity`,
PascalCase, which does not exist on the native `SteamInventoryItemDetail` class).

**Godot C#** β€” same no-single-convenience gap:
```csharp
int total = 0;
API.Inventory.Client.GetItemsByDefinition(item, (items, result) => {
    foreach (var detail in items) total += detail.Quantity;
});
```
`detail.Quantity` (PascalCase) is correct here β€” this is the C# wrapper property.

**O3DE C++** β€” no convenience at all; would require `GetItemsByID` plus the same missing
result-read call described above, so quantity-checking isn't currently reachable through the O3DE
binding either. Don't invent a shortcut.

## Add Promo Item

**Unity C#**
```csharp
// Assuming
ItemData myItem = 100;
// or
ItemDefinitionSettings myItem; // an authoring-time item definition asset

// Then you can request the item to be added. Note that it will only be added
// if the rules you defined on the item in the Steamworks Developer Portal
// resolve for this user
myItem.AddPromoItem(HandleResult);

// or, calling the underlying Toolkit API directly (note the explicit cast β€”
// SteamItemDef_t only converts from int explicitly, unlike ItemData)
Inventory.Client.AddPromoItem(new SteamItemDef_t(100), HandleResult);

void HandleResult(InventoryResult response)
{
    // response.result is the EResult indicating if this is good or not
    // response.items is an array of ItemDetail describing all details found
    // response.Timestamp is when this was returned originally
}
```
Prefer the `myItem.AddPromoItem(...)` extension form β€” it takes an `ItemData`, which converts from
a raw `int` implicitly, so you don't need the explicit `SteamItemDef_t` cast the underlying
`Inventory.Client` call requires.

**Godot GDScript**
```gdscript
SteamApi.AddPromoItem(100)
```

**Godot C#**
```csharp
API.Inventory.Client.AddPromoItem(item);
```
Both match the native/facade signatures exactly (`AddPromoItem(definitionId: int)` native;
`AddPromoItem(ItemData item)` C# facade, which extracts `.DefId` for you).

**O3DE C++**
```cpp
Heathen::SteamInventoryResultHandle handle = -1;
Heathen::SteamInventoryRequestBus::BroadcastResult(handle, &Heathen::SteamInventoryRequests::AddPromoItem, defId);
// or for a batch:
Heathen::SteamInventoryRequestBus::BroadcastResult(handle, &Heathen::SteamInventoryRequests::AddPromoItems, defIdVector);
```
Real and implemented in Foundation. Listen on `OnSteamInventoryResultReady` for completion (same
missing-item-readback caveat as Get All Items applies if you need to see what was granted).

## Get Price

**Unity C#**
```csharp
// Assuming
ItemData myItem = 100;
// or
ItemDefinitionSettings myItem;

// Before you work with prices, be sure to request the prices
// This is normally done for you on initialisation, but be sure
ItemDataExtensions.RequestPrices((result, ioError) =>
{
    // if result.m_result is okay and ioError is false, all is well
});

// get the current and base price (base price is before sales/promos)
myItem.GetPrice(out ulong currentPrice, out ulong basePrice);

// get the current price as a human-friendly string ... this assumes
// the currency is based on 100 (USD, GBP, Euro, etc.)
string price = myItem.CurrentPriceString();
// and the same for base price
string basePrice = myItem.BasePriceString();

// Do you just need the currency symbol? .. e.g. $, €, Β£, etc
string symbol = ItemDataExtensions.GetCurrencySymbol();
```

**Godot GDScript** β€” bulk request:
```gdscript
SteamApi.RequestPrices(func(result, currency): pass)
```
The callback takes **two** parameters (`result`, `currency`) β€” fixed live this pass (the KB
previously showed a 1-parameter lambda, `func(result): pass`, which errors at runtime since the
native call always invokes the callback with both arguments).

There IS a real per-item convenience beyond the bulk request, which the older KB wording
undersold ("no per-item convenience found") β€” `ItemData` has a method-call-only `GetPrice()`
(no property registered for it, so `item.GetPrice()` works but `item.Price` as a bare property
does not in GDScript):
```gdscript
var current_price := item.GetPrice() # int64, CURRENT price only
```
Confirmed from source (`ItemData.cpp`): `GetPrice()` internally fetches both current and base
price from `ISteamInventory::GetItemPrice`, but only returns current price β€” there is no
`GetBasePrice()`/base-price equivalent and no price-formatting/currency-symbol helper on Godot at
all (unlike Unity's `CurrentPriceString()`/`BasePriceString()`/`GetCurrencySymbol()`).

**Godot C#** β€” same bulk-only-for-callback shape, but the per-item call does have a real property:
```csharp
API.Inventory.Client.RequestPrices((result, currency) => { });

// Per-item current price (int64, current price only, no base-price split):
long currentPrice = item.Price; // ItemData.cs wrapper: (long)_instance.Call("GetPrice")
```

**O3DE C++**
```cpp
Heathen::SteamCallHandle callHandle = 0;
Heathen::SteamInventoryRequestBus::BroadcastResult(callHandle, &Heathen::SteamInventoryRequests::RequestPrices);
// Listen on SteamInventoryNotificationBus::Handler::OnSteamInventoryRequestPricesResult
// (callHandle, result, currency) for completion.
```
Real and implemented in Foundation. No per-item price read-back exists on O3DE at all (no
`GetItemPrice`-equivalent bus call) β€” only the bulk request/notification pair above.

## Start Purchase

**Unity C#**
```csharp
// Assuming
ItemData myItem = 100;
// or
ItemDefinitionSettings myItem;

// Assuming myItem is valid for purchase (has a price, is not blocked or hidden)
// How many to buy?
uint count = 1;
myItem.StartPurchase(count, (result, ioError) =>
{
    if(!ioError && result.m_result == EResult.k_EResultOK)
    {
        ulong OrderId = result.m_ulOrderID;
        ulong TransactionId = result.m_ulTransID;
    }
});
```
For a multi-item "shopping cart" checkout in one purchase dialog rather than one item at a time,
Toolkit also ships `ItemShoppingCartManager` β€” out of scope for this condensed pass, but worth
knowing it exists if a developer asks for cart-style checkout.

**Godot** β€” **Work In Progress.** No purchase-initiation method found anywhere in Godot Foundation
or Toolkit source (confirmed again this pass β€” still true).

**O3DE C++** β€” unlike Godot, O3DE's Foundation EBus *does* have this:
```cpp
Heathen::SteamCallHandle callHandle = 0;
AZStd::vector<Heathen::SteamItemDefId> defs{ defId };
AZStd::vector<AZ::u32> quantities{ 1 };
Heathen::SteamInventoryRequestBus::BroadcastResult(callHandle, &Heathen::SteamInventoryRequests::StartPurchase, defs, quantities);
// Listen on SteamInventoryNotificationBus::Handler::OnSteamInventoryStartPurchaseResult
// (callHandle, result, orderId, transactionId).
```
Real and implemented β€” confirmed in `FoundationSteamworks_Inventory.cpp`. This is Foundation-level
raw plumbing only; there is no Toolkit ergonomic wrapper (no cart manager, no single-item
convenience call) on O3DE at all.

## Exchange (crafting / recipes)

**Unity C#**
```csharp
ItemData ironBar = 100;    // Assume Item ID 100 = Iron Bar
ItemData ironSword = 200;  // Assume Item ID 200 = Iron Sword
if(ironBar.GetExchangeEntry(100, out ExchangeEntry[] entries))
{
    // We have 100 iron bars found
    ironSword.Exchange(entries, (result) =>
    {
        if(result.result == EResult.k_EResultOK)
        {
            // The exchange completed, the results define what was added
            Debug.Log("Complete");
        }
    });
}
else
{
    Debug.Log("Not enough iron");
}
```
`GetExchangeEntry` resolves which specific item *instances* (stacks) to consume to cover the
requested quantity; `Exchange` then submits that recipe to Steam to produce the target item. Note
`Exchange`'s callback takes a single `InventoryResult`, not a separate success/error pair β€” read
`result.result` for the `EResult`.

**Godot GDScript**
```gdscript
SteamApi.ExchangeItems(gen_def_ids, gen_qty, dest_item_ids, dest_qty)
```
Four parallel arrays: definitions to generate, their quantities, instance IDs to destroy, their
quantities β€” matches the native `ExchangeItems(genDefIds, genQty, destItemIds, destQty)` signature
exactly.

**Godot C#** β€” a real, typed tuple-list shape, notably nicer than the parallel-array GDScript
version:
```csharp
API.Inventory.Client.ExchangeItems(
    new List<(ItemData, int)> { (itemToGenerate, 1) },
    new List<(SteamInventoryItemDetail, int)> { (itemToDestroy, 1) });
```

**O3DE C++** β€” no `ExchangeItems` equivalent exists on O3DE. The closest primitive is
`TransferItemQuantity` (merges/splits stacks of the *same* item, not a generate-one-item-from-others
recipe system):
```cpp
Heathen::SteamInventoryResultHandle handle = -1;
Heathen::SteamInventoryRequestBus::BroadcastResult(handle, &Heathen::SteamInventoryRequests::TransferItemQuantity,
    sourceInstanceId, quantity, destInstanceId);
```
If a developer needs real crafting/exchange on O3DE, say plainly that it isn't implemented yet
rather than repurposing `TransferItemQuantity` to fake it.

## Serialise Inventory (hand data to another player or your server)

**Unity C#**
```csharp
// Get the serialized data for the player's entire inventory
Inventory.Client.SerializeAllItemResults(data =>
{
    // Send data to the player or server who needs it
});

// Get the serialized data for a specific item or items
ItemData item = 100;
var details = item.GetDetails();
var instance = new SteamItemInstanceID_t[details.Count];
for(int i = 0; i < details.Count; i++)
    instance[i] = details[i].ItemId;

Inventory.Client.SerializeItemResultsByID(instance, data =>
{
    // Send data to the player or server who needs it
});

// On the player or server that received the data
Inventory.Client.DeserializeResult(whoSentIt, data, response =>
{
    if(response.result == EResult.k_EResultOK)
    {
        // whoSentIt definitely owns these items
        response.items // <--
    }
});
```
Serialized result sets carry a short, unforgeable signature tying them to the session they came
from β€” this is how you trust a peer's claimed inventory contents in P2P without a full server
round-trip to Steam.

**Godot** β€” **Work In Progress.** No serialize/deserialize-for-network-transfer method found
anywhere in Godot Foundation or Toolkit source (confirmed again this pass β€” still true).

**O3DE** β€” no serialize/deserialize call exists on the O3DE Foundation bus either (no
`SteamInventoryRequests` method wraps `ISteamInventory::SerializeResult`/`DeserializeResult`).
Same Work-In-Progress status as Godot; don't invent one.

## Consume / Delete Items

**Unity C#**
```csharp
// Assuming
ItemData myItem = 100;
// or
ItemDefinitionSettings myItem;

// Then to consume 1
myItem.Consume(result =>
{
    // result is an InventoryResult and can be used to see what was done
});

// To consume a defined number ...
// Note this may construct multiple consume orders if the requested number
// is split across various stacks of items
myItem.Consume(42, result =>
{
    // result is an InventoryResult and can be used to see what was done
});
```
The quantity-taking overload queues its underlying Steam calls one at a time β€” Steam dislikes
overlapping inventory requests, so a big consume across multiple stacks is daisy-chained
automatically rather than fired all at once.

**Godot GDScript**
```gdscript
SteamApi.ConsumeItem(some_item.itemId, 1)
```
`some_item.itemId` is lowercase (fixed live this pass β€” the KB previously had `.ItemId`,
PascalCase, which does not exist on the native `SteamInventoryItemDetail` class β€” only the C#
wrapper has that casing).

**Godot C#**
```csharp
API.Inventory.Client.ConsumeItem(itemDetail, 1);
```
Matches the facade signature `ConsumeItem(SteamInventoryItemDetail item, int quantity = 1)`
exactly β€” `itemDetail.ItemId` (PascalCase) would also be valid here if you needed the raw ID, since
this is the C# wrapper object, not the native one.

**O3DE C++**
```cpp
Heathen::SteamInventoryResultHandle handle = -1;
Heathen::SteamInventoryRequestBus::BroadcastResult(handle, &Heathen::SteamInventoryRequests::ConsumeItem,
    instanceId, quantity);
```
Real and implemented in Foundation. Toolkit declares a nicer `ItemInstanceData::Consume(quantity)`
wrapper in `Include/ToolkitSteamworks/Data/ItemInstanceData.h`, but β€” same as `ItemData` β€” it has
**no implementation anywhere in the repo**. Don't tell a developer to call
`ItemInstanceData::Consume()` expecting it to link; go through the raw EBus call above instead.

---

## If you're stuck

- Full human-readable article (all prose, screenshots, Code-Free inspector field docs this file
  compresses out): https://heathen.group/kb/inventory/ β€” note the live article does not yet have
  an O3DE section at all; the O3DE content above is new, source-verified material from this pass
  that hasn't been folded into the human-facing KB yet.
- This file is generated from that article's raw content (Unity/Godot) plus new source-verified
  O3DE material; if something here looks wrong, the article is the fallback source of truth for
  Unity/Godot content/structure, not this file's memory β€” but for the specific API calls above,
  this file's corrections are the source-verified version.

---
Back to the full Steam agent index: https://heathen.group/agent-ref-steam-index/