Inventory counts

A count (InventoryCount) records the process of physically counting the stock of a location — full, partial, or cyclic — and applies the variance found as inventory adjustments when completed. All endpoints for this resource require the inventory:count permission.

Stock is only touched on completion, and only if variance was recorded

Creating, starting, or cancelling a count does not move stock. The movement happens exclusively on complete, and only for items whose variance was calculated through a prior call to PUT .../items. If you complete a count without having recorded counted quantities, no adjustment is generated.

List counts

Lists the tenant's counts, with optional filters

pagenumber

Page, 1-indexed. Default: 1.

limitnumber

Items per page. Default: 20.

statusstring

Filter by status: draft, in_progress, completed, cancelled.

locationIdstring

Filter by location.

countTypestring

Filter by type: full, partial, cycle.

200
{
  "counts": [
    { "_id": "671b2c3d4e5f6a7b8c9d0e1f", "name": "Conteo cíclico julio", "locationId": "loc_abc123", "countType": "cycle", "status": "draft" }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 4, "pages": 1 }
}

Query a count

Queries a count by ID

idstringrequired

Count ID.

200
{ "_id": "671b2c3d4e5f6a7b8c9d0e1f", "name": "Conteo cíclico julio", "status": "in_progress" }
404
{ "code": "not-found" }

Variance report

Per-item variance detail for a count

idstringrequired

Count ID.

200
{
  "count": { "_id": "671b2c3d4e5f6a7b8c9d0e1f", "status": "in_progress" },
  "totalItemsCounted": 30,
  "itemsWithVariance": 4,
  "totalVariance": -7,
  "positiveVarianceCount": 1,
  "negativeVarianceCount": 3,
  "items": [
    { "sku": "CAM-ROJO-M", "systemQuantity": 42, "countedQuantity": 40, "variance": -2 }
  ]
}
404
{ "code": "not-found" }

Create a count

Creates a count in 'draft' status

namestringrequired

Descriptive name for the count.

locationIdstringrequired

Location to count.

countTypestringrequired

full | partial | cycle.

descriptionstring

Free-text description.

countItemsarray

Initial items to count. Default: [].

includeZeroStockboolean

Include SKUs with zero stock.

categoryFilterstring

Filter by product category.

skuPatternstring

Filter by SKU pattern.

{ "name": "Conteo cíclico julio", "locationId": "loc_abc123", "countType": "cycle" }
201
{ "_id": "671b2c3d4e5f6a7b8c9d0e1f", "name": "Conteo cíclico julio", "locationId": "loc_abc123", "countType": "cycle", "status": "draft", "countItems": [] }
400
{ "code": "bad-request/missing-name" }

Required permission: inventory:count

Record counted quantities

Records counted quantities and calculates the variance per item

idstringrequired

Count ID.

countItemsarrayrequired

List of { sku, systemQuantity, countedQuantity?, ... }.

{
  "countItems": [
    { "sku": "CAM-ROJO-M", "systemQuantity": 42, "countedQuantity": 40 }
  ]
}
200
{
  "_id": "671b2c3d4e5f6a7b8c9d0e1f",
  "countItems": [
    { "sku": "CAM-ROJO-M", "systemQuantity": 42, "countedQuantity": 40, "variance": -2 }
  ]
}
400
{ "code": "bad-request/invalid-items" }

Required permission: inventory:count

The variance (countedQuantity − systemQuantity) is calculated server-side for each item. An item without countedQuantity is left with variance: undefined — and therefore does not generate an adjustment when the count is completed.

Tip

You can call this endpoint multiple times as the physical count progresses; each call recalculates the variance for the items included in that request.

Start

Marks the count as in progress

idstringrequired

Count ID.

No body required — the handler never reads event.body at all.

{}
200
{ "_id": "671b2c3d4e5f6a7b8c9d0e1f", "status": "in_progress", "startedAt": "2026-07-01T09:00:00.000Z" }
404
{ "code": "not-found" }

Complete (moves the stock)

Closes the count and creates automatic adjustments for items with variance

idstringrequired

Count ID.

No body required — the handler never reads event.body at all.

{}
200
{ "_id": "671b2c3d4e5f6a7b8c9d0e1f", "status": "completed" }
404
{ "code": "not-found" }

Required permission: inventory:count

For each count item whose variance is different from 0 and different from undefined, the system:

  1. Automatically creates an InventoryAdjustment (reason: 'count_correction', status: 'approved', sourceType: 'count', sourceId = the count's ID).
  2. Writes the stock directly, in the same operation.

'complete' does NOT emit the 'Inventory Updated' event

Just like transfer receiving, complete moves stock through a path that bypasses the standard stock write — so it does not trigger Inventory Updated nor the low-stock check, even though it does internally create InventoryAdjustment records. A consumer that only listens to Inventory Updated will not learn about stock changes produced by a completed count.

No state-transition guard

start, complete, and cancel do not verify the current state before applying — just like with transfers, it's an unconditional findOneAndUpdate. Completing a count twice re-evaluates the variance of its countItems and can generate duplicate adjustments if you don't control the state from your integration.

Cancel

Marks the count as cancelled

idstringrequired

Count ID.

cancellationReasonstring

Reason (or the alternate field `reason`). Default: 'Cancelled by user'.

{ "cancellationReason": "Conteo iniciado por error" }
200
{ "_id": "671b2c3d4e5f6a7b8c9d0e1f", "status": "cancelled" }
404
{ "code": "not-found" }

Example — full cycle

# 1. Create
curl -X POST 'https://api.fenicia.io/inventory/counts' \
  -H 'Authorization: Bearer fkapi_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{"name": "Conteo cíclico julio", "locationId": "loc_abc123", "countType": "cycle"}'
 
# 2. Record counted quantities
curl -X PUT 'https://api.fenicia.io/inventory/counts/671b2c3d4e5f6a7b8c9d0e1f/items' \
  -H 'Authorization: Bearer fkapi_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{"countItems": [{"sku": "CAM-ROJO-M", "systemQuantity": 42, "countedQuantity": 40}]}'
 
# 3. Complete (applies the variance as an adjustment)
curl -X POST 'https://api.fenicia.io/inventory/counts/671b2c3d4e5f6a7b8c9d0e1f/complete' \
  -H 'Authorization: Bearer fkapi_your_api_key'

Errors

CodeStatusDescription
bad-request/missing-tenant401The tenant could not be resolved from the API key.
bad-request/missing-name400name is missing on create.
bad-request/missing-location400locationId is missing on create.
bad-request/missing-type400countType is missing on create.
bad-request/invalid-items400countItems is malformed when recording quantities.
not-found404No count exists with that ID.
internal-error500Internal error.

Next steps