# Edara API v3: Units of Measure

Read the [guide](../guide.md) first: authentication, paging, errors, rate limits and the shared `PagedResult`, `BatchResult` and `*Reference` shapes.

- `GET /v3/units-of-measure/standard`: Lists standard units of measure.
- `GET /v3/units-of-measure`: Lists unit-of-measure chains.
- `GET /v3/units-of-measure/{id}`: Gets a unit-of-measure chain by id.
- `POST /v3/units-of-measure`: Creates a unit of measure.

## Endpoints

### `GET /v3/units-of-measure/standard`: Lists standard units of measure.

Operation `GetStandardUnitOfMeasures` · permission `read:unit-of-measure`

**Responses**: OK `List<StandardUnitOfMeasureResponse>`: Standard units of measure.

**Notes**

- Returns the full list of single units, which applies when the organization does not use unit-of-measure chains. There is no filtering or paging, and the order is not guaranteed.
- These ids are separate from the chain ids returned by GET /v3/units-of-measure. Do not use one in place of the other.

---

### `GET /v3/units-of-measure`: Lists unit-of-measure chains.

Operation `GetUnitsOfMeasures` · permission `read:unit-of-measure` · ⚠️ **in code, NOT yet in production**

**Query string**: `GetUnitsOfMeasuresQuery`

| 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. |

**Responses**: OK `PagedResult<UnitOfMeasureResponse>`: Paged list of unit-of-measure chains.

**Notes**

- Returns all unit-of-measure chains, ordered by id. There are no filters and no active flag.
- A page past the end returns an empty list with `totalCount` 0, not the real total.

---

### `GET /v3/units-of-measure/{id}`: Gets a unit-of-measure chain by id.

Operation `GetUnitOfMeasureById` · permission `read:unit-of-measure` · ⚠️ **in code, NOT yet in production**

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `id` | route | `int` |  | The chain id. |

**Responses**: OK `UnitOfMeasureResponse`: The unit-of-measure chain.

**Notes**

- `id` is a chain id, not a unit id. The `unitOfMeasure` on a work order line refers to a unit.

---

### `POST /v3/units-of-measure`: Creates a unit of measure.

Operation `CreateUnitOfMeasure` · permission `create:unit-of-measure`

**Body**: `CreateUnitOfMeasureRequest`

| field | type | default | validation | description |
|---|---|---|---|---|
| `name` | `string` |  | NotEmpty() .MaximumLength(500) | Display name (e.g. "Piece-Box-Carton"). |
| `notes` | `string` |  | MaximumLength(500) .When(x => !string.IsNullOrEmpty(x.Notes)) | Optional notes. |
| `unitOfMeasureDetails` | `List<CreateUnitOfMeasureDetailRequest>` |  | NotNull() .Must(d => d != null && d.Count > 0); Must(d => d != null && d.Count(x => x.IsPrimary) == 1)  .When(x => x.UnitOfMeasureDetails != null && x.UnitOfMeasureDetails.Count > 0); each: SetValidator(new CreateUnitOfMeasureDetailRequestValidator()) .When(x => x.UnitOfMeasureDetails != null) | Ordered list of units in the chain; position assigns display order. |

**Responses**: Created `UnitOfMeasureResponse`: Unit of measure created.

**Business errors** (HTTP 409, match on `errorCode`):

- `DupplicatedCode`: The chain name or a detail code is already in use.

**Notes**

- Send at least one unit and mark exactly one of them with `isPrimary`.
- The `ratioToPrimary` of the primary unit is stored as you send it. It is not forced to 1.
- `displayOrder` is set from each unit's position in the request, starting at 1.
- The unit of measure and all its units are created together. If any part fails, nothing is saved.
- A duplicate unit of measure name or a duplicate unit `code` returns `409` with `errorCode` `DupplicatedCode`.

## Types

### `CreateUnitOfMeasureDetailRequest`

Request to create a unit within a unit-of-measure chain.

| field | type | validation | description |
|---|---|---|---|
| `name` | `string` | NotEmpty() .MaximumLength(100) | Display name (e.g. "Piece", "Box"). |
| `code` | `string` | NotEmpty() .MaximumLength(50) | Unique short code (e.g. "PCS", "BOX"). |
| `isPrimary` | `bool` |  | Whether this unit is the primary (base) unit of the chain. |
| `ratioToPrimary` | `decimal` | GreaterThan(0m) | Conversion ratio to the primary unit. |
| `notes` | `string` | MaximumLength(500) .When(x => !string.IsNullOrEmpty(x.Notes)) | Optional notes. |

### `StandardUnitOfMeasureResponse`

Response describing a standard unit of measure.

| field | type | description |
|---|---|---|
| `id` | `int` | Identifier. |
| `code` | `string` | Unit code. |
| `description` | `string` | Description. |
| `otherLangDescription` | `string` | Other-language description. |

### `UnitOfMeasureDetailResponse`

Response describing a row in a unit-of-measure chain.

| field | type | description |
|---|---|---|
| `id` | `int` | Detail identifier. |
| `name` | `string` | Display name. |
| `code` | `string` | Unit code. |
| `isPrimary` | `bool` | Whether this row is the primary unit. |
| `ratioToPrimary` | `decimal` | Conversion ratio to the primary unit. |
| `displayOrder` | `int` | Display order. |
| `notes` | `string` | Notes. |

### `UnitOfMeasureResponse`

Response describing a unit-of-measure chain.

| field | type | description |
|---|---|---|
| `id` | `int` | Chain identifier. |
| `name` | `string` | Chain name. |
| `notes` | `string` | Chain notes. |
| `isFree` | `bool` | Whether this is the tenant's auto-created free-UOM-mode chain. |
| `unitOfMeasureDetails` | `List<UnitOfMeasureDetailResponse>` | Ordered chain details. |
