Inventory Reports

A single endpoint generates five report types via the reportType parameter. The response shape changes completely between types — don't assume a shared schema across reports.

Endpoint

Generates a report of the requested type. Requires inventory:read.

reportTypestringrequired

inventory, transfers, adjustments, counts, or valuation.

startDatestring

ISO 8601 start date. Applies to transfers, adjustments, and counts.

endDatestring

ISO 8601 end date.

locationIdstring

Filters by location. Applies to inventory, adjustments, counts, and valuation.

formatstring

json or csv. See the warning below — it has no effect.

The exact shape of data/summary depends on reportType — see the detail for each below. This example uses reportType=adjustments:

200reportType=adjustments (example — see the per-type detail below)
{
  "data": [
    { "id": "adj_01H...", "sku": "CAM-ROJO-M", "adjustmentQuantity": -3, "reason": "damage" }
  ],
  "summary": { "total": 1, "totalQuantityAdjusted": -3, "byReason": { "damage": 1 } }
}
400reportType missing
{ "code": "bad-request/missing-type" }
400invalid reportType
{ "code": "bad-request/invalid-report" }

format is not implemented

The format parameter is accepted (json or csv) but does not change the response: JSON is always returned, regardless of the value sent. Do not build a CSV export flow assuming this parameter produces it.

The entire response follows the { data, summary } shape, where data and summary change structure depending on reportType:

reportType=inventory

Snapshot of current stock, up to 10,000 records, filtered by locationId if sent. Not a paginated report.

200
{
  "data": [
    { "sku": "CAM-ROJO-M", "locationId": "loc_cedis_cdmx", "stock": 137 }
  ],
  "summary": { "totalItems": 1, "locationId": "loc_cedis_cdmx" }
}

reportType=transfers

Transfers filtered by date (startDate/endDate).

200
{
  "data": [
    { "id": "trf_01H...", "originId": "loc_a", "destinationId": "loc_b", "status": "received" }
  ],
  "summary": { "total": 1, "byStatus": { "pending": 0, "approved": 0, "received": 1, "cancelled": 0 } }
}

reportType=adjustments

Adjustments filtered by date and locationId.

200
{
  "data": [
    { "id": "adj_01H...", "sku": "CAM-ROJO-M", "adjustmentQuantity": -3, "reason": "damage" }
  ],
  "summary": { "total": 1, "totalQuantityAdjusted": -3, "byReason": { "damage": 1 } }
}

reportType=counts

Counts filtered by date and locationId.

200
{
  "data": [
    { "id": "cnt_01H...", "name": "Conteo cíclico julio", "status": "completed" }
  ],
  "summary": { "total": 1, "byStatus": { "draft": 0, "in_progress": 0, "completed": 1, "cancelled": 0 } }
}

reportType=valuation

This report is broken in production today

data is built by reading item.unitCost directly from the Inventory document — but the canonical Inventory schema (Lib/Services/Inventory/src/model/schema.ts) has no unitCost field. That value is always undefined, so totalValue and averageUnitCost from this report are effectively 0 for every record. It's dead functionality on the base Inventory collection — not a minor rounding bug.

The correct, active valuation in production lives in the FIFO lots subsystem: use GET /inventory/lots/valuation or GET /inventory/lots/sku/{sku}/valuation instead.

200
{
  "data": [
    { "sku": "CAM-ROJO-M", "productName": "Camisa Roja Talla M", "stock": 137, "unitCost": 0, "totalValue": 0, "locationId": "loc_cedis_cdmx" }
  ],
  "summary": { "totalItems": 1, "totalUnits": 137, "totalValue": 0, "averageUnitCost": 0 }
}

Errors

CodeHTTPCause
bad-request/missing-type400reportType was not sent.
bad-request/invalid-report400reportType is not one of the five valid values.

See the Error Catalog for the errors common to the whole domain.

Examples

curl -G https://api.fenicia.io/inventory/reports \
  -H "Authorization: Bearer fkapi_your_api_key" \
  --data-urlencode "reportType=adjustments" \
  --data-urlencode "startDate=2026-07-01T00:00:00Z" \
  --data-urlencode "endDate=2026-07-21T23:59:59Z"

Next steps