---
title: Item Ledger
description: "Elements Version 3.8+ ## Overview The [Item](https://namazustudios.com/glossary/item/) Ledger is an append-only audit trail that records every inventory and [Item](https://namazustudios.com/glossary/item/)-catalog lifecycle event in the system. It answers the question: *\"when did this [User](https://namazustudios.com/glossary/user/) acquire, modify, or lose this [Item](https://namazustudios.com/glossary/item/), and who made the change?\"*…"
url: "https://namazustudios.com/docs/namazu-elements-core/item-ledger/"
updated: "2026-08-13"
category: Namazu Elements Core Features
---

# Item Ledger

Elements Version 3.8+

## Overview

The [Item](https://namazustudios.com/glossary/item/) Ledger is an append-only audit trail that records every inventory and [Item](https://namazustudios.com/glossary/item/)-catalog lifecycle event in the system. It answers the question: *"when did this [User](https://namazustudios.com/glossary/user/) acquire, modify, or lose this [Item](https://namazustudios.com/glossary/item/), and who made the change?"*

Every time an inventory [Item](https://namazustudios.com/glossary/item/) or [Item](https://namazustudios.com/glossary/item/)-catalog record is created, modified, or deleted, the responsible [Service](https://namazustudios.com/glossary/service/) writes an immutable `ItemLedgerEntry` to the `item_ledger` MongoDB collection. Records are **never updated or deleted** - the collection grows monotonically and serves as a permanent history.

Reads are superuser-only. Regular users and unauthenticated callers receive a `ForbiddenException`.

---

## What is recorded

| Trigger | Event type |
| --- | --- |
| [Fungible](https://namazustudios.com/glossary/fungible/) inventory [Item](https://namazustudios.com/glossary/item/) created | `CREATED` |
| [Fungible](https://namazustudios.com/glossary/fungible/) inventory quantity adjusted by delta | `QUANTITY_ADJUSTED` |
| [Fungible](https://namazustudios.com/glossary/fungible/) inventory quantity set to absolute value | `QUANTITY_SET` |
| [Fungible](https://namazustudios.com/glossary/fungible/) inventory [Item](https://namazustudios.com/glossary/item/) deleted | `DELETED` |
| [Distinct](https://namazustudios.com/glossary/distinct/) inventory [Item](https://namazustudios.com/glossary/item/) created | `CREATED` |
| [Distinct](https://namazustudios.com/glossary/distinct/) inventory [Item](https://namazustudios.com/glossary/item/) [Metadata](https://namazustudios.com/glossary/metadata/) updated | `METADATA_UPDATED` |
| [Distinct](https://namazustudios.com/glossary/distinct/) inventory [Item](https://namazustudios.com/glossary/item/) deleted | `DELETED` |
| [Item](https://namazustudios.com/glossary/item/) catalog entry created | `ITEM_CREATED` |
| [Item](https://namazustudios.com/glossary/item/) catalog entry updated | `ITEM_UPDATED` |
| [Item](https://namazustudios.com/glossary/item/) catalog entry deleted | `ITEM_DELETED` |

---

## Model

**Class:** `dev.getelements.elements.sdk.model.inventory.ItemLedgerEntry`

| Field | Type | Description |
| --- | --- | --- |
| `id` | `String` | Database [id](https://namazustudios.com/glossary/id/). Assigned on creation; null on write. |
| `inventoryItemId` | `String` | [id](https://namazustudios.com/glossary/id/) of the affected inventory [Item](https://namazustudios.com/glossary/item/). Null for [Item](https://namazustudios.com/glossary/item/)-catalog events (`ITEM_CREATED`, `ITEM_UPDATED`, `ITEM_DELETED`). |
| `itemCategory` | `ItemCategory` | `Fungible` or `Distinct`. Null for [Item](https://namazustudios.com/glossary/item/)-catalog events. |
| `itemId` | `String` | [id](https://namazustudios.com/glossary/id/) of the [Item](https://namazustudios.com/glossary/item/) definition (the `Item` catalog record). |
| `userId` | `String` | [id](https://namazustudios.com/glossary/id/) of the [User](https://namazustudios.com/glossary/user/) who owns the inventory [Item](https://namazustudios.com/glossary/item/). For [Item](https://namazustudios.com/glossary/item/)-catalog events this is set to the `actorId`. |
| `actorId` | `String` | [id](https://namazustudios.com/glossary/id/) of the [User](https://namazustudios.com/glossary/user/) who performed the action. Null when triggered by a system process or plugin without an active [User](https://namazustudios.com/glossary/user/) [Session](https://namazustudios.com/glossary/session/). |
| `eventType` | `ItemLedgerEventType` | Classifies the event. See [event types](http://localhost:63342/markdownPreview/1921507820/markdown-preview-index-586457526.html?_ijt=f9h2j9ehfr933tefp1jka48iqv#event-types) below. |
| `timestamp` | `long` | Epoch milliseconds when the event was recorded. |
| `quantityBefore` | `Integer` | Quantity before the change. Populated only for `QUANTITY_ADJUSTED`. |
| `quantityAfter` | `Integer` | Quantity after the change. Populated for `CREATED`, `QUANTITY_ADJUSTED`, and `QUANTITY_SET`. |
| `metadataBefore` | `Map<String, Object>` | [Metadata](https://namazustudios.com/glossary/metadata/) snapshot before the change. Populated only for `METADATA_UPDATED`. |
| `metadataAfter` | `Map<String, Object>` | [Metadata](https://namazustudios.com/glossary/metadata/) snapshot after the change. Populated only for `METADATA_UPDATED`. |

---

## Event types

**Class:** `dev.getelements.elements.sdk.model.inventory.ItemLedgerEventType`

| Value | Applies to | Description |
| --- | --- | --- |
| `CREATED` | [Fungible](https://namazustudios.com/glossary/fungible/) and [Distinct](https://namazustudios.com/glossary/distinct/) items | [Item](https://namazustudios.com/glossary/item/) was added to a [User](https://namazustudios.com/glossary/user/)'s inventory. |
| `QUANTITY_ADJUSTED` | [Fungible](https://namazustudios.com/glossary/fungible/) items | Quantity changed by a signed delta. |
| `QUANTITY_SET` | [Fungible](https://namazustudios.com/glossary/fungible/) items | Quantity replaced with an absolute value. |
| `DELETED` | [Fungible](https://namazustudios.com/glossary/fungible/) and [Distinct](https://namazustudios.com/glossary/distinct/) items | [Item](https://namazustudios.com/glossary/item/) was removed from inventory. |
| `METADATA_UPDATED` | [Distinct](https://namazustudios.com/glossary/distinct/) items | Per-[Item](https://namazustudios.com/glossary/item/) [Metadata](https://namazustudios.com/glossary/metadata/) was updated. |
| `ITEM_CREATED` | [Item](https://namazustudios.com/glossary/item/) catalog | An `Item` definition was created. |
| `ITEM_UPDATED` | [Item](https://namazustudios.com/glossary/item/) catalog | An `Item` definition was updated. |
| `ITEM_DELETED` | [Item](https://namazustudios.com/glossary/item/) catalog | An `Item` definition was deleted. |

---

## [REST](https://namazustudios.com/glossary/rest/) API

Base path: `/api/REST/inventory/ledger`

### Get ledger entries

```
GET /api/rest/inventory/ledger
```

Returns paginated `ItemLedgerEntry` objects sorted most recent first. Exactly one of `inventoryItemId` or `userId` is required.

**Query parameters**

| Parameter | Required | Description |
| --- | --- | --- |
| `inventoryItemId` | One of these two is required | Filter entries for a specific inventory [Item](https://namazustudios.com/glossary/item/). |
| `userId` | One of these two is required | Filter entries for a specific [User](https://namazustudios.com/glossary/user/) (across all their inventory items). |
| `eventType` | No | Filter by event type. Omit to return all event types. |
| `offset` | No (default `0`) | Zero-based page offset. |
| `count` | No (default `20`) | Number of results per page. |

**Responses**

| Status | Description |
| --- | --- |
| `200 OK` | `Pagination<ItemLedgerEntry>` - paginated results. |
| `400 Bad Request` | Neither `inventoryItemId` nor `userId` was supplied, or a numeric parameter is negative. |
| `403 Forbidden` | Caller is not a superuser. |

**Example - full history for an inventory [Item](https://namazustudios.com/glossary/item/)**

```
GET /api/rest/inventory/ledger?inventoryItemId=6630f1a2b4e3c70012345678
Authorization: Bearer <superuser-token>
```

```
{
  "total": 3,
  "objects": [
    {
      "id": "6630f1a2b4e3c70099999993",
      "inventoryItemId": "6630f1a2b4e3c70012345678",
      "itemCategory": "FUNGIBLE",
      "itemId": "6630f1a2b4e3c70011111111",
      "userId": "6630f1a2b4e3c70022222222",
      "actorId": "6630f1a2b4e3c70033333333",
      "eventType": "QUANTITY_ADJUSTED",
      "timestamp": 1714000800000,
      "quantityBefore": 10,
      "quantityAfter": 15
    },
    {
      "id": "6630f1a2b4e3c70099999992",
      "inventoryItemId": "6630f1a2b4e3c70012345678",
      "itemCategory": "FUNGIBLE",
      "itemId": "6630f1a2b4e3c70011111111",
      "userId": "6630f1a2b4e3c70022222222",
      "actorId": "6630f1a2b4e3c70033333333",
      "eventType": "QUANTITY_SET",
      "timestamp": 1714000700000,
      "quantityAfter": 10
    },
    {
      "id": "6630f1a2b4e3c70099999991",
      "inventoryItemId": "6630f1a2b4e3c70012345678",
      "itemCategory": "FUNGIBLE",
      "itemId": "6630f1a2b4e3c70011111111",
      "userId": "6630f1a2b4e3c70022222222",
      "actorId": "6630f1a2b4e3c70033333333",
      "eventType": "CREATED",
      "timestamp": 1714000600000,
      "quantityBefore": 0,
      "quantityAfter": 5
    }
  ]
}
```

**Example - [User](https://namazustudios.com/glossary/user/) history filtered to creation events**

```
GET /api/rest/inventory/ledger?userId=6630f1a2b4e3c70022222222&eventType=CREATED
Authorization: Bearer <superuser-token>
```

**Example - entries within a timestamp range**

```
GET /api/rest/inventory/ledger?inventoryItemId=6630f1a2b4e3c70012345678&from=1714000000000&to=1714001000000
Authorization: Bearer <superuser-token>
```

---

## [DAO](https://namazustudios.com/glossary/dao/) interface

**Interface:** `dev.getelements.elements.sdk.DAO.ItemLedgerDao` **Annotation:** `@ElementServiceExport`

```
/** Appends an immutable audit record. */
ItemLedgerEntry createLedgerEntry(ItemLedgerEntry entry);

/** Entries for a specific inventory item, most recent first. eventType/from/to may be null. */
Pagination<ItemLedgerEntry> getLedgerEntries(
        String inventoryItemId, int offset, int count,
        ItemLedgerEventType eventType, Long from, Long to);

/** All entries for a specific user across all inventory items, most recent first. eventType/from/to may be null. */
Pagination<ItemLedgerEntry> getLedgerEntriesForUser(
        String userId, int offset, int count,
        ItemLedgerEventType eventType, Long from, Long to);
```

There are intentionally no update or delete methods - the interface enforces immutability.

---

## [Service](https://namazustudios.com/glossary/service/) interface

**Interface:** `dev.getelements.elements.sdk.Service.inventory.ItemLedgerService` **Annotations:** `@ElementPublic`, `@ElementServiceExport`, `@ElementServiceExport(name = UNSCOPED)`

The [Service](https://namazustudios.com/glossary/service/) is read-only and exposes the same two query methods as the [DAO](https://namazustudios.com/glossary/dao/).

| Access level | Behaviour |
| --- | --- |
| `SUPERUSER` | Full read access. |
| Any other level | Throws `ForbiddenException`. |

---

## How writes work

Ledger entries are written automatically by the inventory and [Item](https://namazustudios.com/glossary/item/)-catalog [Service](https://namazustudios.com/glossary/service/) implementations immediately after each successful mutation. No explicit calls are needed by callers of those services.

```
SuperUserSimpleInventoryItemService   ─|
SuperUserAdvancedInventoryItemService ─|
SuperUserDistinctInventoryItemService ─|
SuperuserItemService (catalog)        ─|----> ItemLedgerDao.createLedgerEntry(...)
```

The `actorId` is set to the [id](https://namazustudios.com/glossary/id/) of the currently authenticated [User](https://namazustudios.com/glossary/user/). If the call originates from a plugin or system process without an active [User](https://namazustudios.com/glossary/user/) [Session](https://namazustudios.com/glossary/session/), `actorId` is null.

[Item](https://namazustudios.com/glossary/item/)-catalog events (`ITEM_CREATED`, `ITEM_UPDATED`, `ITEM_DELETED`) set `inventoryItemId`, `itemCategory`, and `userId` to null; `actorId` holds the [id](https://namazustudios.com/glossary/id/) of the superuser who made the change.

---

## Reading the ledger from a plugin

```
@Inject
private ItemLedgerService itemLedgerService;

// Full history for one inventory item
Pagination<ItemLedgerEntry> history = itemLedgerService
        .getLedgerEntries(inventoryItemId, 0, 20, null, null, null);

// Creation events for a user
Pagination<ItemLedgerEntry> created = itemLedgerService
        .getLedgerEntriesForUser(userId, 0, 20, ItemLedgerEventType.CREATED, null, null);

// Entries within a timestamp range
long from = Instant.parse("2024-04-25T00:00:00Z").toEpochMilli();
long to   = Instant.parse("2024-04-26T00:00:00Z").toEpochMilli();
Pagination<ItemLedgerEntry> ranged = itemLedgerService
        .getLedgerEntries(inventoryItemId, 0, 20, null, from, to);
```

The [Service](https://namazustudios.com/glossary/service/) must be injected from a superuser-scoped or `UNSCOPED` context.

---

## Best practices

- The ledger is append-only and grows indefinitely. Add a TTL index to `item_ledger.timestamp` if unbounded growth is a concern for your deployment.
- Use the `eventType` filter to narrow queries - scanning all event types for a high-traffic [User](https://namazustudios.com/glossary/user/) can be expensive at scale.
- For `QUANTITY_ADJUSTED` events, `quantityBefore + delta = quantityAfter`. For `QUANTITY_SET` events, only `quantityAfter` is recorded to avoid an extra [DAO](https://namazustudios.com/glossary/dao/) read.
- `METADATA_UPDATED` entries include full `metadataBefore` and `metadataAfter` snapshots. For items with large [Metadata](https://namazustudios.com/glossary/metadata/) objects this doubles the storage cost per update.
- [Item](https://namazustudios.com/glossary/item/)-catalog events cannot be retrieved via the `inventoryItemId` or `userId` [REST](https://namazustudios.com/glossary/rest/) filters. Query MongoDB directly or use `ItemLedgerDao` in a plugin for catalog-only audit needs.

---

## FAQ

**Q: Why is the ledger superuser-only?** A: Inventory histories can reveal sensitive activity information about other users. Only privileged callers (administrators, backend services) should be able to read them.

**Q: Does deleting an inventory [Item](https://namazustudios.com/glossary/item/) also delete its ledger entries?** A: No. Ledger entries are immutable and are never removed. After deletion a `DELETED` entry is appended; the prior history remains intact.

**Q: Can I query entries for a specific event type across all users?** A: Not directly via the [REST](https://namazustudios.com/glossary/rest/) API. For cross-[User](https://namazustudios.com/glossary/user/) analytics, query MongoDB directly or build a custom aggregation in a plugin using `ItemLedgerDao`.

**Q: Are entries written inside a transaction with the inventory mutation?** A: Not currently. The ledger write happens immediately after the [DAO](https://namazustudios.com/glossary/dao/) call returns. If the ledger write fails the inventory mutation has already been committed - this is an acceptable trade-off for an audit trail where eventual consistency is sufficient.
