---
title: "One product's position at a warehouse, decomposed"
method: GET
path: "/api/v1/inventory/position"
tags: ["inventory"]
---

# One product's position at a warehouse, decomposed

`GET /api/v1/inventory/position`

## Request

- Base URL: `https://api.app.tightly.io`
- URL: `https://api.app.tightly.io/api/v1/inventory/position`
- Auth: HTTP bearer

What is there, what is held, what is coming and what is left to sell, for one product at one warehouse: `on_hand`, `reserved` broken down by why each unit is spoken for and by which order holds it, `expected_inbound` broken down by the purchase or transfer order it is coming on, and `atp`.

The identity holds on every answer: `on_hand - reserved.hard + expected_inbound.quantity + expected_inbound.returns_expected = atp`. `atp` is computed from those four and read from nowhere else, so a figure you are shown can always be counted off the rows under it.

`reserved.hard` is what `atp` subtracts and `reserved.soft` warns without consuming anything. At a warehouse the connector masters, the hard figure takes the LARGER of Tightly's own order holds and the source's `committed_quantity`, and the part no Tightly document explains is served as its own `source_committed` row.

`on_hand_basis` says which fact the head figure is. `source_on_hand` is the connector's own physical count; `source_available` is its available figure standing in where it serves no physical one, and that figure is already net of what the store has promised its own orders; `ledger` is the sum of the movements, which is the on hand at a warehouse Tightly masters.

Leave `location_id` off for the total over every warehouse the product is known at, with a row per warehouse under `locations`. The last 20 movements ride along; the whole ledger is `list_movements`.

Scope: `inventory:read`.

## Query parameters

- `variant_id` string, required
- `location_id` string

## Headers

- `Tightly-Version` string

## Response `200`

The position, its decomposition and the last 20 movements behind it.

- object
  - `data` object
    - `as_of` string, date-time: When the read was taken.
    - `atp` integer
    - `expected_inbound` object: The firm outstanding quantity by document, and the returns expected back.
    - `location_id` string, nullable
    - `location_name` string, nullable: The warehouse's own name. Null on the sum over every warehouse.
    - `locations` object[]
    - `movements` object[]
    - `movements_total` integer
    - `on_hand` integer
    - `on_hand_basis` string, nullable: source_on_hand, source_available or ledger. Null on the sum over every warehouse.
    - `reconciliation` object: tied_at, tied_quantity, residual and residual_seen_at.
    - `reserved` object: hard, soft and the by_basis decomposition, whose quantities sum to hard + soft.
    - `stock_master` string, nullable: Who keeps this warehouse's stock, `source` or `tightly`. Null on the sum over every warehouse, where each row carries its own.
    - `variant_id` string
  - `message` object: severity, service and desc.

## Other responses

- `400`: No `variant_id` was sent, or a parameter could not be read.
- `401`: No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them.
- `403`: The key may not reach this operation. `scope_missing` when the key does not hold Stock; `organization_mismatch` when the path names an organisation that is not the key's; `ip_not_allowed` when the caller's address is outside the key's allowlist.
- `404`: No product or no warehouse with that id is on file (`variant_not_on_file`, `location_not_on_file`).
- `429`: The key has spent its allowance for this minute. 600 reads and 120 writes a minute, counted per key, refused calls included. `Retry-After` says how long to wait.
- `503`: The database is briefly out of reach, which is what a failover or a full connection pool looks like from outside. Wait the seconds `Retry-After` names and send the request again. A read is safe to repeat. A write is not, because the connection was lost in the middle of it, so read the object back before sending it a second time. Branch on the status rather than on `code`, which is `POSTGRES_UNAVAILABLE` from one of the two seams that raise this and `SERVICE_UNAVAILABLE` from the other.

---

[API](https://skmtc.dev/tightly/apis/tightly-api.md) · [All operations](https://skmtc.dev/tightly/apis/tightly-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tightly/apis/tightly-api/revisions/199f7eb35afb?raw)
