# Edara API v3: Warehouses

Read the [guide](../guide.md) first: authentication, paging, errors, rate limits and the shared `PagedResult`, `BatchResult` and `*Reference` shapes.

- `GET /v3/warehouses`: Lists warehouses.
- `GET /v3/warehouses/{id}`: Gets a warehouse by id.
- `POST /v3/warehouses`: Creates a warehouse.
- `PUT /v3/warehouses/code/{code}`: Updates a warehouse by code.
- `GET /v3/warehouses/rma`: Lists RMA warehouses.
- `GET /v3/warehouses/{id}/balance`: Gets warehouse balance.
- `GET /v3/warehouses/tree/{userId}`: Gets the warehouse tree for a user.
- `GET /v3/warehouses/stock-items-balances`: Gets stock-item warehouse balances.

## Endpoints

### `GET /v3/warehouses`: Lists warehouses.

Operation `GetWarehouses` · permission `read:warehouse`

**Query string**: `GetWarehousesQuery`

| field | type | default | validation | description |
|---|---|---|---|---|
| `offset` | `int?` | 0 | >= 0 | Number of items to skip before returning results. Defaults to 0. |
| `limit` | `int?` | 100 | 1..1000 (out of range → 400) | Maximum number of items to return. Defaults to 100, maximum 1000. |
| `code` | `string` |  |  | Exact code filter. |
| `name` | `string` |  |  | Description contains filter. |
| `updateDate` | `DateTime?` |  |  | Returns warehouses created or updated on or after this date. |

**Responses**: OK `PagedResult<WarehouseResponse>`: Paged list of warehouses.

**Notes**

- The list only includes warehouses that the calling user's data permissions cover. A user with no warehouse data permissions gets `200` with an empty list, not `403`.
- Only active warehouses are returned.
- `code` is trimmed and matches exactly. `name` is trimmed and matches any part of the warehouse description. An empty string means no filter.
- `updateDate` returns warehouses created or updated strictly after the value you send, not on or after it.
- A page past the end of the list returns `totalCount` 0.
- Results are ordered by `id`.

---

### `GET /v3/warehouses/{id}`: Gets a warehouse by id.

Operation `GetWarehouseById` · permission `read:warehouse`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `id` | route | `int` |  | The warehouse id. |

**Responses**: OK `WarehouseResponse`: The warehouse.

**Notes**

- Inactive warehouses are returned too.
- An unknown id returns `404`.

---

### `POST /v3/warehouses`: Creates a warehouse.

Operation `CreateWarehouse` · permission `create:warehouse`

**Body**: `WarehouseUpsertRequest`

| field | type | default | validation | description |
|---|---|---|---|---|
| `classificationId` | `int?` |  |  | Warehouse classification identifier. |
| `code` | `string` |  |  | Warehouse code. |
| `description` | `string` |  | NotEmpty() | Warehouse description. |
| `mobile` | `string` |  |  | Warehouse mobile number. |

**Responses**: Created `WarehouseResponse`: Warehouse created.

**Business errors** (HTTP 409, match on `errorCode`):

- `DupplicatedName`: Another warehouse already uses the same description.

**Notes**

- `code`, `description` and `mobile` are trimmed. A null `mobile` is stored as an empty string.
- When `code` is empty, Edara generates it: the new warehouse's `id` is used as its `code`.
- Special characters are removed from `code` and `description` before they are stored. The response shows the stored values.
- An unknown `classificationId` returns a generic `400`.
- When you create a warehouse without a classification, the creating user is given data permission for it. Other users only see it in the warehouse list (GET /v3/warehouses) if their data permissions cover it.
- A duplicate `code` returns `409` with `errorCode` `DupplicatedCode`. A duplicate `description` returns `409` with `errorCode` `DupplicatedName`.

---

### `PUT /v3/warehouses/code/{code}`: Updates a warehouse by code.

Operation `UpdateWarehouseByCode` · permission `update:warehouse`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `code` | route | `string` |  | The warehouse code. |

**Body**: `WarehouseUpsertRequest`

| field | type | default | validation | description |
|---|---|---|---|---|
| `classificationId` | `int?` |  |  | Warehouse classification identifier. |
| `code` | `string` |  |  | Warehouse code. |
| `description` | `string` |  | NotEmpty() | Warehouse description. |
| `mobile` | `string` |  |  | Warehouse mobile number. |

**Responses**: OK `WarehouseResponse`: Warehouse updated.

**Business errors** (HTTP 409, match on `errorCode`):

- `DupplicatedName`: Another warehouse already uses the same description.

**Notes**

- The `code` in the path is trimmed and must match exactly. An unknown code returns `404`.
- Inactive warehouses can be updated too.
- This is a full replace of `classificationId`, `description` and `mobile`. A null `classificationId` clears the classification, and an omitted `mobile` is cleared to an empty string.
- `code` in the body is ignored, so you cannot change a warehouse's code with this endpoint.
- Warehouse properties that this endpoint does not accept keep their current values.
- Special characters are removed when the warehouse is saved, but the response does not show that, so it can differ from what is stored.
- A `description` that already exists in the same classification returns `409` with `errorCode` `DupplicatedName`.

---

### `GET /v3/warehouses/rma`: Lists RMA warehouses.

Operation `GetRmaWarehouses` · permission `read:warehouse`

**Responses**: OK `List<WarehouseResponse>`: List of RMA warehouses.

**Notes**

- Returns active RMA warehouses only, ordered by `id`. The list is not paged.
- When there are no RMA warehouses, you get `200` with an empty list.

---

### `GET /v3/warehouses/{id}/balance`: Gets warehouse balance.

Operation `GetWarehouseBalance` · permission `read:warehouse`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `id` | route | `int` |  | The warehouse id. |

**Query string**: `GetWarehouseBalanceQuery`

| field | type | default | validation | description |
|---|---|---|---|---|
| `offset` | `int?` | 0 | **no validator**: negative silently becomes 0 | Number of items to skip before returning results. Defaults to 0. |
| `limit` | `int?` | 100 | **no validator**: out of 1..1000 silently becomes 100 | Maximum number of items to return. Defaults to 100, maximum 1000. |

**Responses**: OK `PagedResult<WarehouseBalanceResponse>`: Paged warehouse balance.

**Notes**

- A warehouse id that does not exist returns `404`. An inactive warehouse is not rejected.
- The response has a row for every stock item in the organization, not only the items stocked in this warehouse. Items that were never stocked there come back with `balance` 0.
- Inactive stock items are included.
- A stock item with several batches comes back as one row per batch, and the response has no batch number to tell the rows apart.
- `totalCount` counts all rows, including the rows with a zero balance.
- Response time grows with the number of stock items in the organization, not with the page size, so asking for small pages does not make a call faster.
- The order of rows is not guaranteed, so paging is not stable: the same row can appear on two pages or be missed.
- `balance` is the current on-hand quantity. It is not a balance as of a date, and reserved quantities are not subtracted.

---

### `GET /v3/warehouses/tree/{userId}`: Gets the warehouse tree for a user.

Operation `GetWarehousesTree` · permission `read:warehouse`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `userId` | route | `string` |  | The user id. |

**Responses**: OK `List<WarehouseTreeResponse>`: Warehouse tree.

**Notes**

- The tree is built from the warehouse classifications that the user in `userId` has data permission for. Each classification is followed by the warehouses in it, including inactive ones.
- Warehouses that have no classification do not appear in the tree.
- The result is a flat list, not a nested tree. Each row has either `classificationId` or `warehouseId`, plus `parentId` and `treeLevel`.
- A user with no classification data permissions gets an empty list.

---

### `GET /v3/warehouses/stock-items-balances`: Gets stock-item warehouse balances.

Operation `GetStockItemsWarehousesBalances` · permission `read:warehouse`

**Query string**: `GetWarehouseStockBalancesQuery`

| field | type | default | validation | description |
|---|---|---|---|---|
| `stockItemsIds` | `string` |  | NotEmpty() | Comma-separated stock-item identifiers. Required. |
| `nonZeroBalanceOnly` | `bool` |  |  | When true, excludes zero-balance items. |
| `warehousesIds` | `string` |  |  | Optional comma-separated warehouse identifiers. |

**Responses**: OK `List<WarehouseStockItemBalanceResponse>`: Stock-item warehouse balances.

**Notes**

- Both id lists are comma-separated positive integers. Duplicates are ignored, and any value that is not a positive integer returns `400`.
- Stock-item ids that do not exist return `400` with a message that lists them, not `404`. Inactive stock items are accepted.
- `warehousesIds` accepts only active warehouses that the calling user is permitted to see. An inactive warehouse, or one outside the user's data permissions, returns `400` with a message saying it does not exist.
- When you omit `warehousesIds`, inactive warehouses are included.
- The response always has one row for each requested stock item. `nonZeroBalanceOnly` never removes rows: it only leaves warehouses with a zero or negative balance out of `totalBalance`.
- `totalBalance` and `totalReserved` cover only the warehouses in `warehousesIds`. `globalBalance` and `globalReserved` always cover all warehouses.
- Balances are current values, calculated from opening balances and work orders up to the most recent one.
- `globalBalance` is calculated slightly differently from the per-warehouse balances: it leaves out `RT` and `IT` work orders and replacement lines. Do not expect it to equal the sum over all warehouses.

## Types

### `ClassificationReference`

Lightweight stock-item classification reference (id + code + name).

| field | type | description |
|---|---|---|
| `id` | `int` | The classification id. |
| `code` | `string` | The classification code. |
| `name` | `string` | The classification name. |

### `StockItemReference`

Lightweight stock-item reference (id + code + description).

| field | type | description |
|---|---|---|
| `id` | `int` | The stock-item id. |
| `code` | `string` | The stock-item code. |
| `description` | `string` | The stock-item description. |

### `WarehouseBalanceResponse`

Response describing a stock-item balance at a warehouse.

| field | type | description |
|---|---|---|
| `warehouse` | `WarehouseReference` | Warehouse. |
| `stockItem` | `StockItemReference` | Stock item. |
| `balance` | `decimal` | Stock balance. |

### `WarehouseReference`

Lightweight warehouse reference (id + code + name).

| field | type | description |
|---|---|---|
| `id` | `int` | The warehouse id. |
| `code` | `string` | The warehouse code. |
| `name` | `string` | The warehouse name. |

### `WarehouseResponse`

Response describing a warehouse.

| field | type | description |
|---|---|---|
| `id` | `int` | Warehouse identifier. |
| `classification` | `ClassificationReference` | Warehouse classification. |
| `code` | `string` | Warehouse code. |
| `description` | `string` | Warehouse description. |
| `mobile` | `string` | Warehouse mobile number. |

### `WarehouseStockItemBalanceResponse`

Response describing a stock-item balance summary across warehouses.

| field | type | description |
|---|---|---|
| `stockItem` | `StockItemReference` | Stock item. |
| `globalBalance` | `decimal` | Global balance. |
| `totalBalance` | `decimal` | Total balance. |
| `globalReserved` | `decimal` | Global reserved balance. |
| `totalReserved` | `decimal` | Total reserved balance. |

### `WarehouseTreeResponse`

Response describing a warehouse tree node.

| field | type | description |
|---|---|---|
| `classificationId` | `int` | Classification identifier. |
| `warehouseId` | `int` | Warehouse identifier. |
| `parentId` | `int` | Parent identifier. |
| `treeLevel` | `int` | Tree level. |
| `description` | `string` | Warehouse description. |
