> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arcuserp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Retrieve the reorder report

> Returns products below their configured reorder point, plus a separate list of demand-driven products with no reorder point configured. Accepts an optional `location_id` to scope the report to one location instead of the entity-wide rollup.



## OpenAPI

````yaml /openapi.yaml get /purchasing/reorder-report
openapi: 3.1.0
info:
  title: Arcus ERP Public API
  version: 1.0.0
  description: >
    Arcus ERP public REST API. Designed for external integrations and data
    migration.


    **Authentication.** Bearer token (API key) via the `Authorization` header.

    Format: `Authorization: Bearer ark_live_ent_<code>_<random>` (or
    `ark_test_*` for sandbox).

    API keys are issued per-entity in **Settings > Developers > API Keys**.


    **Entity scoping.** The entity is encoded in the API key prefix; routes are
    flat

    (e.g. `/v1/accounts`, `/v1/orders`, `/v1/products`). A small set of platform
    endpoints

    (migration, reconciliation, events, webhook endpoints, API keys) use the

    `/v1/entities/{entity_id}/...` form -- those are noted in their tags.


    **Key capabilities.**
      - Related-resource hydration via `?expand[]=` (see `x-arcus-expand` on each resource).
      - Cursor-based pagination (`starting_after` / `ending_before` / `limit`).
      - Idempotency via the `Idempotency-Key` header.
      - Webhook events for asynchronous notification.
      - Conditional requests / ETag for cache validation.

    **Changelog.** Entries are dated and name every published contract whose
    MEANING moved, not

    only the ones whose field names changed. The narrative version of the same
    entries, written

    for integrators, is published at https://arcuserp.mintlify.app/changelog.


    **2026-09-22 (planned 2026-09-21), REORDER-BUYER-TRUTH: demand changed what
    it MEANS on three

    published contracts, with no field renamed.** An integrator that pins field
    names sees no

    breakage and different numbers, which is why this entry exists.

      - **Demand now counts build consumption.** `demand_avg_per_day` on the public product, kit
        and inventory-balance objects, and `daily_demand` / `demand_basis` / `net_suggested_qty`
        on `GET /v1/purchasing/reorder-report`, are composed from fulfilled sales lines PLUS
        posted `build_consume` inventory draws: each physical decrement of a product counts
        exactly once. The previous rule adopted an internal-consumption basis only when the sales
        blend was exactly zero, so a product both sold AND consumed into work orders planned as
        if the build draws did not exist. Products drawn into work orders move; on one
        production-shaped dataset five did, the largest from 0.05/day to 14.95/day.
      - **`demand_basis` now carries four values, not two:** `sales`, `sales_and_builds`,
        `builds` and `consumption`. A consumer with a two-branch reader (anything that is not
        `consumption` is `sales`) silently hides the two new ones.
      - **`current_demand_units` on `GET /v1/inventory/balances/:id` moved, by a second rule.**
        It counts committed-but-unshipped CUSTOMER demand, and it now counts a kit component's
        own line rather than its parent kit line, and excludes non-sales documents. On one
        production dataset 269 of 1,043 balance rows changed, 227 of them downward; the largest
        single move was 1,941 to 25, on a product whose open PURCHASE order line had been
        reported as customer demand. Re-baseline anything that alerts or reorders off this field.
      - `run_rate_30/90/180/365` and `blended_daily` on the same balance object move for the
        first reason above.
      - **Added, not changed:** `GET /v1/purchasing/reorder-report` accepts `demand_window` and
        returns the unit, cover-through and reorder-point-provenance fields documented on that
        operation; `order_multiple` is accepted and returned on the product-vendor doors.
      - **Volume note.** The `inventory.low_stock`, `inventory.out_of_stock` and
        `inventory.back_in_stock` webhook events are population-gated on
        `on_hand <= reorder_point`, and reorder points move with the demand above, so
        subscribers should expect a one-time step change in event volume around the release.
servers:
  - url: https://api.arcuserp.com/v1
    description: Arcus ERP API (accepts both live `ark_live_*` and test `ark_test_*` keys)
  - url: https://dev-api.arcuserp.com/v1
    description: >-
      Dev sandbox API (test-only data, accepts `ark_test_*` keys against dev
      RDS)
security: []
paths:
  /purchasing/reorder-report:
    get:
      tags:
        - Purchasing
      summary: Retrieve the reorder report
      description: >-
        Returns products below their configured reorder point, plus a separate
        list of demand-driven products with no reorder point configured. Accepts
        an optional `location_id` to scope the report to one location instead of
        the entity-wide rollup.
      operationId: getReorderReportV1
      parameters:
        - name: location_id
          in: query
          description: >
            Optional UUID. When provided, the report is scoped to the specified
            location's

            inventory_balances row. When omitted, the report is entity-wide with
            per-row

            `locations_below[]` detail.
          schema:
            type: string
            format: uuid
        - name: demand_window
          in: query
          required: false
          description: >
            REORDER-BUYER-TRUTH lane L6 (2026-09-22). Trailing window, in days,
            for the unit

            counts on each row. When omitted the unit columns are NOT computed
            and

            `summary.demand_window_state` is `not_requested`; a value outside
            the enum falls

            back to 30 rather than 400ing a report.
          schema:
            type: integer
            enum:
              - 30
              - 90
              - 180
              - 365
            default: 30
      responses:
        '200':
          description: Reorder report sections (below-rp + configure-recommended).
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        product_id:
                          type: string
                          format: uuid
                        sku:
                          type: string
                          nullable: true
                        product_name:
                          type: string
                        available_qty:
                          type: number
                        on_hand:
                          type: number
                        on_purchase_order:
                          type: number
                        reorder_point:
                          type: number
                        reorder_qty:
                          type: number
                        shortage_qty:
                          type: number
                        no_vendor:
                          type: boolean
                        vendor_id:
                          type: string
                          format: uuid
                          nullable: true
                        vendor_name:
                          type: string
                          nullable: true
                        lead_time_days:
                          type: integer
                          nullable: true
                        best_unit_cost:
                          type: number
                        est_cost:
                          type: number
                        demand_window_days:
                          type: integer
                          nullable: true
                          description: >
                            The window the unit columns below were counted over,
                            echoed back.

                            Null when `demand_window` was not requested.
                        demand_window_units:
                          type: number
                          nullable: true
                          description: >
                            Units of this product that physically left the shelf
                            in

                            `demand_window_days`: fulfilled sales lines PLUS
                            posted

                            `build_consume` draws, each decrement counted
                            exactly once. This is

                            the buyer's first question, how many did we sell,
                            answered beside

                            the rate rather than derived from it. Null unless
                            `demand_window`

                            was requested.
                        demand_window_sales_units:
                          type: number
                          nullable: true
                          description: The sales leg of `demand_window_units`, on its own.
                        demand_window_build_units:
                          type: number
                          nullable: true
                          description: >
                            The `build_consume` leg of `demand_window_units`, on
                            its own. The

                            two legs sum to `demand_window_units`.
                        demand_window_rate_per_day:
                          type: number
                          nullable: true
                          description: >
                            `demand_window_units` divided by the window's own
                            length, to four

                            decimals. Null when unanswerable. It is THE rate for
                            the selected

                            window and is not interchangeable with
                            `daily_demand`, which is the

                            blended cache figure.
                        cover_through_date:
                          type: string
                          format: date
                          nullable: true
                          description: >
                            The date today's position runs out at the current
                            rate. Null when

                            the rate is zero or unanswerable.
                        reorder_point_source:
                          type: string
                          enum:
                            - cache
                            - default
                          description: >
                            REORDER-BUYER-TRUTH decision D-6 (2026-09-22). The
                            PROVENANCE of the

                            `reorder_point` on this row, because the same
                            product could read 22

                            on one screen and 14 on another and nothing said
                            which was which.

                            `cache` means a value is stored for this product,
                            INCLUDING a stored

                            `0`, which is a deliberate setting (an exempt
                            product, or one set to

                            zero) and not an absence. `default` means nothing
                            has ever been

                            stored, and `reorder_point` is then `0`. Treat
                            `default` as not set

                            rather than as a planning number.
                        reorder_point_recomputed:
                          type: number
                          nullable: true
                          description: >
                            A live recomputation of the reorder point, present
                            only when one is

                            available AND differs from the cached value. It does
                            not take effect

                            until the next refresh; the cached number is what
                            the row and its

                            status chip print.
                        reorder_point_recomputed_at:
                          type: string
                          format: date-time
                          nullable: true
                          description: When `reorder_point_recomputed` was computed.
                        order_multiple:
                          type: integer
                          minimum: 1
                          nullable: true
                          description: >
                            The preferred vendor's purchase INCREMENT (case /
                            pack qty) for this

                            product, echoed beside `min_order_qty` so a buyer
                            can see the floor

                            and the step together. Null when not configured.
                            REORDER-BUYER-TRUTH

                            decision D-7 (2026-09-22).
                        suggestion_basis:
                          type: string
                          enum:
                            - moq_floor
                            - pallet
                            - vendor_multiple
                            - none
                          description: >
                            Which vendor rule actually MOVED the suggested
                            quantity, set only by

                            a rule that really applied. `none` means the
                            economic batch stood as

                            computed, and in that case no chip should claim a
                            rounding.
                        rounded_to_multiple:
                          type: boolean
                          description: >
                            True only when the pack size RAISED the quantity. It
                            gates the

                            "Rounded" chip, and is deliberately not true for a
                            quantity that

                            already sat on a multiple.
                        multiple_applied:
                          type: number
                          nullable: true
                          description: >
                            The FACTOR, not the pack size: 2, for a suggestion
                            of 70 at a pack of

                            35. It is reported for a real pack size WHETHER OR
                            NOT the rounding

                            bound, because "this is 2 cases" is true and useful
                            either way;

                            `rounded_to_multiple` is the boolean that gates the
                            chip. Null when

                            the pack size is absent or 1, where a factor would
                            just restate the

                            quantity (`utils/reorder-helpers.mjs:453`).
                        vendor_multiple:
                          type: integer
                          minimum: 1
                          nullable: true
                          description: >
                            The pack size itself (35 in the example above), or
                            null. This is the

                            value `order_multiple` held on the preferred vendor
                            row at the moment

                            the suggestion was computed.
                        suggested_qty_before_rounding:
                          type: number
                          description: >
                            What the engine wanted before any vendor rule
                            applied: the left side

                            of the "49 to 70" the row prints. It is `max(1,
                            ceil(raw_shortfall))`

                            and is ALWAYS present, equal to `suggested_qty` when
                            no rule moved the

                            number, so a consumer compares the two rather than
                            testing for null

                            (`utils/reorder-helpers.mjs:422`).
                        days_supply_before_rounding:
                          type: number
                          nullable: true
                          description: >
                            The cover the un-rounded quantity would have bought,
                            the left side of

                            the "86d to 118d" delta beside it. Null only when
                            daily demand is

                            zero, which is the one case the division is
                            unanswerable, NOT when a

                            rule failed to apply
                            (`utils/reorder-helpers.mjs:472`).
                        dependent_demand_units:
                          type: integer
                          description: >
                            Released/in-progress work-order demand for this
                            product

                            (sum of GREATEST(required - issued, 0)). Surfaced so
                            the

                            caller can explain a dependent_demand_exceeds_supply
                            trigger.
                        trigger_reason:
                          type: string
                          enum:
                            - below_aggregate_no_pipeline
                            - below_aggregate_with_pipeline
                            - dependent_demand_exceeds_supply
                            - location_specific_below
                            - unknown
                          description: >
                            HOTFIX-REORDER-REPORT-READINESS Fix 3
                            (ERP-CORRECTNESS-RULES §C11).

                            Names which inclusion branch put this product on the
                            report so the

                            operator/caller can see WHY each row appears.
                            on_purchase_order is

                            netted out of every branch before this is computed.
                        all_vendors:
                          type: array
                          items:
                            type: object
                        locations_below:
                          type: array
                          description: >
                            Per-location shortfall detail. Empty array when no
                            locations

                            are below their own reorder point.
                            HOTFIX-REORDER-INTELLIGENCE

                            Phase 4B Fix 3 -- prevents the masking pattern where

                            SUM(available) > MAX(reorder_point) hides
                            per-location shortages.

                            HOTFIX-REORDER-REPORT-READINESS Fix 2 -- `shortfall`
                            is net of

                            each location's `on_purchase_order`.
                          items:
                            type: object
                            properties:
                              location_id:
                                type: string
                                format: uuid
                              location_name:
                                type: string
                                nullable: true
                              available:
                                type: number
                              on_purchase_order:
                                type: number
                              reorder_point:
                                type: number
                              shortfall:
                                type: number
                  configure_recommended:
                    type: array
                    description: >
                      Products with `demand_avg_per_day > 0` but no configured
                      reorder_point.

                      Industry pattern: Cin7 "Configure Recommended" + NetSuite
                      Auto-Replenishment.

                      Each row includes a system-recommended reorder_point
                      computed from

                      demand x entity default lead time.
                    items:
                      type: object
                      properties:
                        product_id:
                          type: string
                          format: uuid
                        sku:
                          type: string
                          nullable: true
                        product_name:
                          type: string
                        demand_avg_per_day:
                          type: number
                        demand_stddev_per_day:
                          type: number
                        demand_last_computed_at:
                          type: string
                          format: date-time
                          nullable: true
                        total_available:
                          type: number
                        recommended_reorder_point:
                          type: integer
                        recommended_reorder_qty:
                          type: integer
                        min_pallet_qty:
                          type: integer
                          nullable: true
                        current_reorder_point:
                          type: integer
                          nullable: true
                  summary:
                    type: object
                    properties:
                      total_products:
                        type: integer
                      no_vendor_count:
                        type: integer
                      vendor_count:
                        type: integer
                      est_total_cost:
                        type: number
                      configure_recommended_count:
                        type: integer
                      demand_window_days:
                        type: integer
                        nullable: true
                        description: >
                          The window the row unit columns were counted over.
                          Null when

                          `demand_window` was not requested.
                      demand_windows_available:
                        type: array
                        items:
                          type: integer
                        description: >
                          The windows this endpoint accepts, so a caller can
                          build the selector

                          without hardcoding the list.
                      demand_window_state:
                        type: string
                        enum:
                          - ok
                          - ok_exact
                          - not_requested
                          - unavailable
                        description: >
                          Whether the unit columns were computed, and how.

                          `ok`: computed from a window anchored on today.

                          `ok_exact`: a sales document is fulfilled with a
                          FUTURE date, so the

                          server computed each product's own anchor instead of
                          withholding the

                          column; the numbers and the basis are identical to
                          `ok`.

                          `not_requested`: `demand_window` was omitted and the
                          unit columns are

                          null by construction.

                          `unavailable`: the read failed.

                          **Test `state.startsWith("ok")` rather than `state ===
                          "ok"`.**
                      location_id:
                        type: string
                        format: uuid
                        nullable: true
                        description: >
                          The location_id query param if scoped; null when
                          entity-wide.
        '400':
          description: Invalid location_id (must be UUID).
        '401':
          description: Unauthorized (missing or invalid API key).
        '403':
          description: Forbidden (missing `purchasing:read` scope).
      security:
        - ApiKeyAuth: []
components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: |
        API key issued per entity via Settings > Developers > API Keys.
        Each key carries scopes (e.g. orders:read, products:write).
        Bearer token format: Authorization: Bearer ark_live_ent_<code>_<random>
        Test keys use ark_test_ent_<code>_<random>. Both are issued per entity
        via Settings > Developers > API Keys.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.