Shop It Docs
Developer Resourcesinventory

Inventory Module API & Integration Guide

Inventory API contracts for admin operations and order-driven stock lifecycle integration.

Audience: Admin frontend developers, backend integrators, and QA engineers
Scope: Admin inventory routes, DTO contracts, response envelopes, and queue/outbox integration points

Inventory Module - API & Integration Guide

1. How to Read / Quick Metadata

  • Module: Inventory
  • Auth model: JwtAuthGuard + RoleGuard + @Permissions(...)
  • Primary base URL: /api/admin/inventory
  • Response envelope: successful endpoints return ResponseDto<T>
  • Swagger tag used by controller: Inventory (Admin)
  • Route owner: InventoryAdminController

2. High-Level Overview

Inventory API is split into:

  • synchronous admin reads (list, low-stock, product inventory detail)
  • synchronous admin mutations (adjust, bulk-sync)
  • asynchronous worker-side lifecycle handlers (reserve/finalize/release) triggered by order outbox events

Inventory ownership model:

  • Inventory owns: stock numbers (total, reserved, available) and stock mutation rules
  • Order owns: business event timing (create, payment success, cancel/failure/expire) and outbox emission
  • BullMQ owns: async execution and retry delivery

3. Core Concepts and Terminology

  • inventory row: one product_inventory row per product (UNIQUE(product_id))
  • reserve: move quantity from available to reserved
  • finalize: permanently deduct sold quantity from total + reserved
  • release: return reserved quantity back to available
  • adjust: manual admin correction (positive/negative delta)
  • sync: reconciliation operation that re-derives available = total - reserved
  • dedupeKey: deterministic idempotency fingerprint carried by outbox payloads
  • low stock: availableQuantity <= lowStockThreshold

4. Route Summary

MethodPathPermission
GET/api/admin/inventoryInventory_READ
GET/api/admin/inventory/low-stockInventory_READ
GET/api/admin/inventory/:productIdInventory_READ
POST/api/admin/inventory/:productId/adjustInventory_UPDATE
POST/api/admin/inventory/bulk-syncInventory_UPDATE

5. Route Details

5.1 List inventory

AspectDetails
EndpointGET /api/admin/inventory
AuthJwtAuthGuard + RoleGuard
PermissionInventory_READ
Query DTOInventoryListQueryDto
ResponseResponseDto<InventoryResponseDto[]>
PaginationQueryDto driven (pagination, page, size)

Behavior notes:

  • ordered by updatedAt DESC
  • supports optional lowStockOnly=true + optional threshold
  • returns count/page/size metadata only when pagination=true

5.2 List low-stock inventory

AspectDetails
EndpointGET /api/admin/inventory/low-stock
AuthJwtAuthGuard + RoleGuard
PermissionInventory_READ
Querythreshold?: number
ResponseResponseDto<InventoryResponseDto[]>

Behavior notes:

  • low-stock predicate delegates to shared service: trackInventory=true AND available <= threshold
  • default threshold inside service is 5 when not provided

5.3 Get inventory by product ID

AspectDetails
EndpointGET /api/admin/inventory/:productId
AuthJwtAuthGuard + RoleGuard
PermissionInventory_READ
ParamproductId (int)
ResponseResponseDto<InventoryResponseDto | null>

Behavior notes:

  • returns "Inventory not found" with data: null envelope when row is missing

5.4 Adjust stock

AspectDetails
EndpointPOST /api/admin/inventory/:productId/adjust
AuthJwtAuthGuard + RoleGuard
PermissionInventory_UPDATE
ParamproductId (int)
Body DTOStockAdjustDto
ResponseResponseDto<InventoryResponseDto>

Behavior notes:

  • positive adjustment increases stock
  • negative adjustment decreases stock
  • if product has no inventory row, service auto-creates row with normalized values
  • rejects when adjustment would produce negative quantities

5.5 Bulk sync inventory

AspectDetails
EndpointPOST /api/admin/inventory/bulk-sync
AuthJwtAuthGuard + RoleGuard
PermissionInventory_UPDATE
Body DTOBulkSyncDto
ResponseResponseDto<InventoryResponseDto[]>

Behavior notes:

  • current controller accepts trigger: string but runtime logic ignores trigger semantics
  • reconciles all inventory rows where available != (total - reserved)

6. Query Parameters

6.1 InventoryListQueryDto (extends QueryDto)

ParamTypeDefaultNotes
paginationbooleantrueif false, no pagination metadata
pagenumber1minimum 1
sizenumber20minimum 1
sortstringupdatedAtcurrently normalized upstream, list query uses updatedAt DESC
orderasc/descdescsame as above
searchstringoptionalcurrently not applied in inventory query
lowStockOnlybooleanoptionaltransforms from string ("true")
thresholdnumberoptionalmin 1

7. Request DTO Contracts

7.1 StockAdjustDto

FieldTypeRequiredValidation
adjustmentnumberyes@IsInt()
reasonstringyes@IsString(), @MinLength(3)
actorIdstringnodefaults to "admin" in admin service

7.2 BulkSyncDto

FieldTypeRequiredValidation
triggerstringyes@IsString()

8. Response DTO Contracts

8.1 InventoryResponseDto

FieldTypeNotes
idnumberinventory row ID
productIdnumberlinked product
totalQuantitynumbertracked total units
reservedQuantitynumberunits reserved by pending lifecycle
availableQuantitynumberunits currently sellable
lowStockThresholdnumberlow-stock alert threshold
trackInventorybooleaninventory tracking toggle
allowOversellbooleanoversell policy flag
isLowStockbooleancomputed: available <= threshold
createdAtdatecreation timestamp
updatedAtdatelast mutation timestamp

9. Payload Examples

9.1 Adjust stock request

{
  "adjustment": 5,
  "reason": "restock",
  "actorId": "admin-user-1"
}

9.2 Single inventory response item

{
  "id": 11,
  "productId": 101,
  "totalQuantity": 25,
  "reservedQuantity": 3,
  "availableQuantity": 22,
  "lowStockThreshold": 5,
  "trackInventory": true,
  "allowOversell": false,
  "isLowStock": false,
  "createdAt": "2026-04-16T02:05:44.000Z",
  "updatedAt": "2026-04-16T02:15:44.000Z"
}

9.3 Paginated list response

{
  "message": "Inventory fetched",
  "data": [
    {
      "id": 11,
      "productId": 101,
      "totalQuantity": 25,
      "reservedQuantity": 3,
      "availableQuantity": 22,
      "lowStockThreshold": 5,
      "trackInventory": true,
      "allowOversell": false,
      "isLowStock": false,
      "createdAt": "2026-04-16T02:05:44.000Z",
      "updatedAt": "2026-04-16T02:15:44.000Z"
    }
  ],
  "count": 1,
  "currentPage": 1,
  "totalPage": 1,
  "nextCursor": null
}

9.4 Bulk sync response

{
  "message": "2 inventory records synced",
  "data": [
    {
      "id": 11,
      "productId": 101,
      "totalQuantity": 25,
      "reservedQuantity": 3,
      "availableQuantity": 22,
      "lowStockThreshold": 5,
      "trackInventory": true,
      "allowOversell": false,
      "isLowStock": false,
      "createdAt": "2026-04-16T02:05:44.000Z",
      "updatedAt": "2026-04-16T02:15:44.000Z"
    },
    {
      "id": 12,
      "productId": 102,
      "totalQuantity": 10,
      "reservedQuantity": 0,
      "availableQuantity": 10,
      "lowStockThreshold": 5,
      "trackInventory": true,
      "allowOversell": false,
      "isLowStock": false,
      "createdAt": "2026-04-16T02:07:44.000Z",
      "updatedAt": "2026-04-16T02:15:44.000Z"
    }
  ]
}

10. Queue/Outbox Integration Contracts

Inventory jobs are consumed by inventory processors using @nomor/jobs contracts.

10.1 Job name + queue mapping

JobQueuePayload
stock.reserveorders_reservationsStockReservePayload
stock.finalizeorders_stock_finalizeStockFinalizePayload
stock.releaseorders_stock_releaseStockReleasePayload
stock.adjustinventoryStockAdjustPayload
stock.syncinventoryStockSyncPayload

10.2 Payload contract summary

PayloadRequired fieldsOptional fields
StockReservePayloadorderId, productId, quantity, dedupeKeyrequestId, correlationId
StockFinalizePayloadorderId, productId, quantity, dedupeKeypaymentReference, requestId, correlationId
StockReleasePayloadorderId, productId, quantity, reason, dedupeKeyrequestId, correlationId
StockAdjustPayloadproductId, adjustment, reason, actorId, dedupeKeyrequestId, correlationId
StockSyncPayloadnone required (productId optional)productId, requestId, correlationId

10.3 Outbox event emission points (order-owned)

Order lifecycle pointOutbox eventQueue target
order created (createFromCart, createBuyNow)stock.reserveorders_reservations
payment successstock.finalizeorders_stock_finalize
cancel/payment_failed/expiredstock.releaseorders_stock_release

11. Error Handling

Representative API/domain errors for inventory flows:

HTTPerrorCodeMessage
400INVENTORY_INSUFFICIENT_STOCKInsufficient stock.
400INVENTORY_INVALID_OPERATIONInvalid stock operation.
400INVENTORY_INVALID_ADJUSTMENTAdjustment would result in negative stock.
404INVENTORY_NOT_FOUNDInventory not found.

12. Integration Sequence Diagrams

12.1 Admin adjustment flow

12.2 Order reserve flow

12.3 Payment success finalize flow

12.4 Cancel/failure release flow

13. Endpoint Reference Cheatsheet

EndpointPurposeNotes
GET /api/admin/inventoryfull inventory listsupports pagination and optional low-stock filtering
GET /api/admin/inventory/low-stocklow-stock listthreshold override via query
GET /api/admin/inventory/:productIdproduct-level inventory detailreturns data: null when missing
POST /api/admin/inventory/:productId/adjustmanual stock mutationvalidates non-negative outcomes
POST /api/admin/inventory/bulk-syncreconciliationcurrent body field trigger is informational

14. Current Known Gaps (API-Level)

  • orders_stock queue is registered/owned by dispatcher but currently has no processor consumer.
  • Inventory cache TTL env keys are defined but not yet applied by inventory service.
  • Mongo AuditLog mutation writes for inventory are planned in docs/plans but not implemented in current inventory service.
  • INVENTORY_ALREADY_FINALIZED and INVENTORY_ALREADY_RELEASED constants exist but are not currently emitted by service logic.

15. QA / Release Checklist

  • Admin routes enforce Inventory_READ / Inventory_UPDATE correctly.
  • List endpoint pagination metadata matches ResponseDto contract.
  • Reserve/finalize/release jobs reach expected queues and processors.
  • Stock adjustment rejects negative resulting quantities.
  • Bulk sync reconciles inconsistent rows (available != total - reserved).
  • Order cancellation and payment-failure flows emit stock.release outbox events.
  • Payment-success flow emits stock.finalize outbox events.
  • No stale queue name mismatch between docs and QueueName enum.

16. File Map

  • apps/api/src/modules/inventory/admin/inventory-admin.controller.ts
  • apps/api/src/modules/inventory/admin/inventory-admin.service.ts
  • apps/api/src/modules/inventory/admin/dto/*
  • apps/api/src/modules/inventory/shared/inventory.service.ts
  • apps/api/src/modules/inventory/processors/inventory.processor.ts
  • apps/api/src/modules/order/order.service.ts (reserve/release outbox emitters)
  • apps/api/src/modules/order/processing/order.processor.ts (finalize/release emitters)
  • apps/api/src/modules/order/processing/outbox-dispatcher.service.ts
  • packages/jobs/src/index.ts

See Also

17. Endpoint Payload Cheatsheet

17.1 GET /api/admin/inventory

Request

GET /api/admin/inventory?page=1&size=20&pagination=true&lowStockOnly=false HTTP/1.1
Authorization: Bearer <jwt>

Success response (200)

{
  "message": "Inventory fetched",
  "data": [
    {
      "id": 15,
      "productId": 205,
      "totalQuantity": 40,
      "reservedQuantity": 4,
      "availableQuantity": 36,
      "lowStockThreshold": 5,
      "trackInventory": true,
      "allowOversell": false,
      "isLowStock": false,
      "createdAt": "2026-04-16T05:00:00.000Z",
      "updatedAt": "2026-04-16T05:05:00.000Z"
    }
  ],
  "count": 1,
  "currentPage": 1,
  "totalPage": 1,
  "nextCursor": null
}

17.2 GET /api/admin/inventory/low-stock

Request

GET /api/admin/inventory/low-stock?threshold=3 HTTP/1.1
Authorization: Bearer <jwt>

Success response (200)

{
  "message": "Low stock inventory",
  "data": [
    {
      "id": 21,
      "productId": 310,
      "totalQuantity": 8,
      "reservedQuantity": 1,
      "availableQuantity": 2,
      "lowStockThreshold": 5,
      "trackInventory": true,
      "allowOversell": false,
      "isLowStock": true,
      "createdAt": "2026-04-16T05:00:00.000Z",
      "updatedAt": "2026-04-16T06:00:00.000Z"
    }
  ]
}

17.3 GET /api/admin/inventory/:productId

Request

GET /api/admin/inventory/205 HTTP/1.1
Authorization: Bearer <jwt>

Success response (200, found)

{
  "message": "Inventory fetched",
  "data": {
    "id": 15,
    "productId": 205,
    "totalQuantity": 40,
    "reservedQuantity": 4,
    "availableQuantity": 36,
    "lowStockThreshold": 5,
    "trackInventory": true,
    "allowOversell": false,
    "isLowStock": false,
    "createdAt": "2026-04-16T05:00:00.000Z",
    "updatedAt": "2026-04-16T05:05:00.000Z"
  }
}

Success response (200, missing)

{
  "message": "Inventory not found",
  "data": null
}

17.4 POST /api/admin/inventory/:productId/adjust

Request

POST /api/admin/inventory/205/adjust HTTP/1.1
Authorization: Bearer <jwt>
Content-Type: application/json

{
  "adjustment": -2,
  "reason": "manual_count_correction",
  "actorId": "admin-user-1"
}

Success response (200)

{
  "message": "Stock adjusted",
  "data": {
    "id": 15,
    "productId": 205,
    "totalQuantity": 38,
    "reservedQuantity": 4,
    "availableQuantity": 34,
    "lowStockThreshold": 5,
    "trackInventory": true,
    "allowOversell": false,
    "isLowStock": false,
    "createdAt": "2026-04-16T05:00:00.000Z",
    "updatedAt": "2026-04-16T06:30:00.000Z"
  }
}

17.5 POST /api/admin/inventory/bulk-sync

Request

POST /api/admin/inventory/bulk-sync HTTP/1.1
Authorization: Bearer <jwt>
Content-Type: application/json

{
  "trigger": "admin_reconcile"
}

Success response (200)

{
  "message": "1 inventory records synced",
  "data": [
    {
      "id": 22,
      "productId": 415,
      "totalQuantity": 20,
      "reservedQuantity": 5,
      "availableQuantity": 15,
      "lowStockThreshold": 5,
      "trackInventory": true,
      "allowOversell": false,
      "isLowStock": false,
      "createdAt": "2026-04-16T05:00:00.000Z",
      "updatedAt": "2026-04-16T06:35:00.000Z"
    }
  ]
}

18. Outbox Payload Examples

18.1 Reserve payload (stock.reserve)

{
  "orderId": 9001,
  "productId": 205,
  "quantity": 2,
  "dedupeKey": "stock.reserve:9001:205",
  "requestId": "checkout_6de8b7",
  "correlationId": "order_checkout_user_42_6de8b7"
}

18.2 Finalize payload (stock.finalize)

{
  "orderId": 9001,
  "productId": 205,
  "quantity": 2,
  "dedupeKey": "stock.finalize:9001:205",
  "paymentReference": "esewa_txn_39f",
  "requestId": "payment_success_9001",
  "correlationId": "order_payment_success_9001"
}

18.3 Release payload (stock.release)

{
  "orderId": 9001,
  "productId": 205,
  "quantity": 2,
  "reason": "cancel",
  "dedupeKey": "stock.release:9001:205:cancel",
  "requestId": "cancel_1ab4",
  "correlationId": "order_cancel_9001"
}

18.4 Adjust payload (stock.adjust)

{
  "productId": 205,
  "adjustment": 7,
  "reason": "warehouse_received",
  "actorId": "admin-user-1",
  "dedupeKey": "stock.adjust:205:warehouse_received",
  "requestId": "inventory_adjust_205",
  "correlationId": "inventory_admin_adjust_205"
}

18.5 Sync payload (stock.sync)

{
  "productId": 205,
  "requestId": "inventory_sync_205",
  "correlationId": "inventory_admin_sync_205"
}

19. HTTP Error Reference

EndpointConditionHTTPerrorCode
POST /:productId/adjustnegative resulting quantity400INVENTORY_INVALID_ADJUSTMENT
lifecycle worker pathinsufficient stock during reserve400INVENTORY_INSUFFICIENT_STOCK
lifecycle worker pathinvalid reserve/finalize math400INVENTORY_INVALID_OPERATION
lifecycle worker pathfinalize on missing row400/404 surface-dependentINVENTORY_NOT_FOUND
guarded admin routesmissing/invalid auth401/403auth/permission errors

20. Troubleshooting Guide

20.1 Stock seems deducted twice

Check in order:

  1. outbox dedupe key uniqueness in outbox_events
  2. job id dedupe behavior in outbox dispatcher
  3. whether replay happened after state already finalized
  4. quantity values in order_item.quantity

20.2 Reserve keeps failing with insufficient stock

Check:

  1. trackInventory and allowOversell flags for that product row
  2. current availableQuantity vs requested quantity
  3. concurrent reservations causing latest row lock result to fail

20.3 Low-stock endpoint returns no data

Check:

  1. trackInventory=true for candidate products
  2. threshold value (query or default)
  3. whether available quantity is already above threshold after sync/adjust

20.4 Sync endpoint returns empty list

This means all rows already satisfy:

  • availableQuantity === totalQuantity - reservedQuantity

21. cURL Examples

21.1 List inventory

curl -X GET "http://localhost:5002/api/admin/inventory?page=1&size=20" \
  -H "Authorization: Bearer <token>"

21.2 Adjust stock

curl -X POST "http://localhost:5002/api/admin/inventory/205/adjust" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"adjustment":3,"reason":"restock","actorId":"admin-1"}'

21.3 Bulk sync

curl -X POST "http://localhost:5002/api/admin/inventory/bulk-sync" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"trigger":"scheduled_reconcile"}'

22. Environment Variables (API Surface Context)

VariableDefaultUsed by
INVENTORY_STOCK_CACHE_TTL_SECONDSunsetnot yet wired
INVENTORY_LIST_CACHE_TTL_SECONDSunsetnot yet wired
INVENTORY_CONFIG_CACHE_TTL_SECONDSunsetnot yet wired
INVENTORY_HISTORY_CACHE_TTL_SECONDSunsetnot yet wired
INVENTORY_RESERVATIONS_CACHE_TTL_SECONDSunsetnot yet wired

Implementation note:

  • these keys are present in env validation but currently not consumed by inventory service/controller logic.

On this page

Inventory Module - API & Integration Guide1. How to Read / Quick Metadata2. High-Level Overview3. Core Concepts and Terminology4. Route Summary5. Route Details5.1 List inventory5.2 List low-stock inventory5.3 Get inventory by product ID5.4 Adjust stock5.5 Bulk sync inventory6. Query Parameters6.1 InventoryListQueryDto (extends QueryDto)7. Request DTO Contracts7.1 StockAdjustDto7.2 BulkSyncDto8. Response DTO Contracts8.1 InventoryResponseDto9. Payload Examples9.1 Adjust stock request9.2 Single inventory response item9.3 Paginated list response9.4 Bulk sync response10. Queue/Outbox Integration Contracts10.1 Job name + queue mapping10.2 Payload contract summary10.3 Outbox event emission points (order-owned)11. Error Handling12. Integration Sequence Diagrams12.1 Admin adjustment flow12.2 Order reserve flow12.3 Payment success finalize flow12.4 Cancel/failure release flow13. Endpoint Reference Cheatsheet14. Current Known Gaps (API-Level)15. QA / Release Checklist16. File MapSee Also17. Endpoint Payload Cheatsheet17.1 GET /api/admin/inventoryRequestSuccess response (200)17.2 GET /api/admin/inventory/low-stockRequestSuccess response (200)17.3 GET /api/admin/inventory/:productIdRequestSuccess response (200, found)Success response (200, missing)17.4 POST /api/admin/inventory/:productId/adjustRequestSuccess response (200)17.5 POST /api/admin/inventory/bulk-syncRequestSuccess response (200)18. Outbox Payload Examples18.1 Reserve payload (stock.reserve)18.2 Finalize payload (stock.finalize)18.3 Release payload (stock.release)18.4 Adjust payload (stock.adjust)18.5 Sync payload (stock.sync)19. HTTP Error Reference20. Troubleshooting Guide20.1 Stock seems deducted twice20.2 Reserve keeps failing with insufficient stock20.3 Low-stock endpoint returns no data20.4 Sync endpoint returns empty list21. cURL Examples21.1 List inventory21.2 Adjust stock21.3 Bulk sync22. Environment Variables (API Surface Context)